ServerSocket: примеры (JAVA)

Примеры и разбор работы ServerSocket
Раздел: Сокеты (сетевые)
ServerSocket(int port)

Общее описание ServerSocket

Класс java.net.ServerSocket предназначен для создания серверной стороны TCP-соединения. Типичный сценарий применения - прослушивание указанного порта и принятие входящих соединений с созданием объектов Socket для дальнейшего обмена данными.

Основные конструкторы:

  • ServerSocket() - создаёт неинициализированный сокет; требуется вызов bind для связывания.
  • ServerSocket(int port) - создаёт и привязывает сервер к указанному порту на всех интерфейсах. port в диапазоне 0–65535; 0 означает выбор свободного порта ОС.
  • ServerSocket(int port, int backlog) - как предыдущий, дополнительно указывает размер очереди входящих соединений backlog (платформа может игнорировать значение).
  • ServerSocket(int port, int backlog, InetAddress bindAddr) - привязка к конкретному адресному интерфейсу bindAddr.

Ключевые методы и их параметры/возвращаемые значения:

  • bind(SocketAddress endpoint) / bind(SocketAddress endpoint, int backlog) - связывание с адресом. Параметр endpoint типа InetSocketAddress. Возвращаемое значение отсутствует (void). Возможны исключения IOException и BindException.
  • accept() - ожидание и принятие входящего соединения; возвращает объект Socket. Ожидание блокирующее; при истечении таймаута выбрасывается SocketTimeoutException.
  • close() - закрытие серверного сокета; последующие вызовы accept выбросят исключение.
  • setSoTimeout(int timeout) / getSoTimeout() - установка таймаута в миллисекундах для блокировки accept. 0 означает бесконечное ожидание.
  • setReuseAddress(boolean on) / getReuseAddress() - флаг повторного использования адреса; полезно при быстром рестарте сервера.
  • getLocalPort(), getInetAddress(), getLocalSocketAddress() - получение данных о локальном адресе и порте. getLocalPort возвращает int; getInetAddress - InetAddress или null, если не связан.
  • isBound(), isClosed() - флаги состояния.
  • setReceiveBufferSize(int size) / getReceiveBufferSize() - подсказка ОС о размере буфера приема для создаваемых сокетов.

Типичные исключения и условия:

  • BindException - порт уже используется или привязка невозможна.
  • SocketException - проблемы на уровне сокетов (например, закрытие из другого потока).
  • IllegalArgumentException - неверный номер порта (за пределами 0–65535) или отрицательный таймаут.
  • SecurityException - политика безопасности не позволяет слушать указанный порт.

Поведение может зависеть от ОС и реализующей сетевой подсистемы: значение backlog и возможность повторного использования порта могут быть ограничены платформой.

Короткие примеры использования

Примеры демонстрируют основные варианты работы с серверным сокетом: однократное принятие соединения, цикл обработки клиентов, настройка таймаута и привязка к интерфейсу.

Пример 1: Простой однопоточный сервер, принимающий одно соединение

import java.net.*;
import java.io.*;

public class SimpleServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket server = new ServerSocket(12345)) {
            Socket client = server.accept();
            try (BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
                 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()))) {
                String line = in.readLine();
                out.write("Echo: " + line + "\n");
                out.flush();
            }
        }
    }
}
Результат (сервер печатает ничего, клиент получает):
Client -> "Hello"
Server -> "Echo: Hello"

Пример 2: Сервер с таймаутом accept

import java.net.*;
import java.io.*;

public class TimeoutServer {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(0); // ОС выдаст свободный порт
        System.out.println("Listening on port " + server.getLocalPort());
        server.setSoTimeout(2000); // 2 секунды
        try {
            server.accept();
        } catch (java.net.SocketTimeoutException e) {
            System.out.println("Accept timed out");
        } finally {
            server.close();
        }
    }
}
Ожидаемый вывод:
Listening on port 51432
Accept timed out

Пример 3: Привязка к конкретному интерфейсу и указание backlog

import java.net.*;
import java.io.*;

public class BindToIf {
    public static void main(String[] args) throws IOException {
        InetAddress local = InetAddress.getByName("192.168.1.10");
        ServerSocket server = new ServerSocket(8080, 50, local);
        System.out.println("Bound: " + server.getLocalSocketAddress());
        server.close();
    }
}
Если интерфейс 192.168.1.10 присутствует, вывод примерно:
Bound: /192.168.1.10:8080
Иначе будет выброшено IOException

Похожие API внутри Java

В Java присутствуют несколько альтернатив и близких по назначению средств:

  • java.nio.channels.ServerSocketChannel - неблокирующий и селектор-ориентированный API. Подходит при необходимости обслуживания большого числа соединений с одним потоком. Отличается работой через селекторы и буферы NIO.
  • DatagramSocket / DatagramChannel - для UDP. Не предоставляет соединений accept; вместо этого используется отправка/приём пакетов.
  • com.sun.net.httpserver.HttpServer - упрощённый HTTP-сервер на базе сокетов, полезен для быстрого развёртывания HTTP-обработчиков без внешних библиотек.
  • ServerSocketFactory и SSLServerSocketFactory - фабрики для создания обычных или SSL-серверных сокетов; рекомендуется при необходимости абстрагировать создание сокетов или использовать SSL.

Выбор: для простых TCP-сервисов подойдёт ServerSocket. Для высокой нагрузки и масштабируемости предпочтителен ServerSocketChannel. Для UDP применяется DatagramSocket. Для HTTPS применяется SSL-слой через фабрики.

Аналоги в других языках

Ниже краткие примеры создания TCP-сервера на популярных языках и отличия от Java.

Python (socket)

import socket
s = socket.socket()
s.bind(('0.0.0.0', 12345))
s.listen(5)
conn, addr = s.accept()
print('Connected by', addr)
data = conn.recv(1024)
conn.sendall(b'Echo: ' + data)
conn.close()
s.close()
Результат:
Connected by ('127.0.0.1', 54321)
(клиент получает 'Echo: ...')

Отличие: синтаксис проще, сокеты в Python более динамичные, но производительность и контроль буферов уступают Java NIO.

Node.js (JavaScript, модуль net)

const net = require('net');
const server = net.createServer((socket) => {
  socket.on('data', (data) => socket.write('Echo: ' + data));
});
server.listen(12345, () => console.log('Listening'));
Результат:
Listening
(клиенты получают ответ 'Echo: ...')

Отличие: событийно-ориентированная модель, неблокирующая по умолчанию, простота разработки I/O-ориентированных приложений.

C# (.NET) - TcpListener

using System.Net;
using System.Net.Sockets;

var listener = new TcpListener(IPAddress.Any, 12345);
listener.Start();
using var client = listener.AcceptTcpClient();
// обработка
listener.Stop();
Результат: сервер принимает соединение аналогично Java.

Go (net)

package main
import (
  "net"
  "fmt"
)
func main() {
  ln, _ := net.Listen("tcp", ":12345")
  conn, _ := ln.Accept()
  fmt.Println("accepted", conn.RemoteAddr())
  conn.Close()
}
Результат:
accepted 127.0.0.1:54321

PHP (stream_socket_server)

$sock = stream_socket_server("tcp://0.0.0.0:12345", $errno, $errstr);
$conn = stream_socket_accept($sock);
fwrite($conn, "Echo:\n");
fclose($conn);
Результат: клиент получает данные аналогично.

Краткие отличия по языкам: модель конкурентности (потоки, события, корутины), поддержка NIO/асинхронных примитивов и встроенные SSL-фабрики различаются. Java даёт гибкость между классическим блокирующим API и NIO/асинхронным через ServerSocketChannel.

Типичные ошибки и их проявления

Ниже распространённые ошибки при работе с ServerSocket и ожидаемые симптомы.

BindException: Address already in use

public class BindFail {
    public static void main(String[] args) throws Exception {
        new ServerSocket(80); // если порт занят, выбросится исключение
    }
}
java.net.BindException: Address already in use (Bind failed)
	at java.net.PlainSocketImpl.bind(PlainSocketImpl.java:...) ...

Причины: другой процесс слушает порт, или предыдущий экземпляр сервера не был корректно закрыт. При длительном ожидании освобождения порта помогает флаг setReuseAddress(true), но это не всегда решает конфликт.

IllegalArgumentException при неверном номере порта

new ServerSocket(70000);
java.lang.IllegalArgumentException: port out of range: 70000

SocketTimeoutException при accept с таймаутом

ServerSocket s = new ServerSocket(0);
s.setSoTimeout(1000);
s.accept();
java.net.SocketTimeoutException: Accept timed out

Работа после close()

ServerSocket s = new ServerSocket(0);
s.close();
s.accept();
java.net.SocketException: Socket is closed

Рекомендации по отладке: проверять значение порта, логи ОС, правильность закрытия ресурсов и наличие прав на прослушивание привилегированных портов (<1024) в Unix-системах.

Изменения и примечания по версиям

Класс ServerSocket сохраняет стабильный API в течение длительного времени. Некоторые важные моменты:

  • Поддержка конструкций try-with-resources появилась благодаря тому, что класс реализует интерфейс Closeable. Это делает закрытие сервера более безопасным (совместимо с Java 7 и выше).
  • В Java 1.4 был расширен набор неблокирующих возможностей через пакет NIO, куда вошёл ServerSocketChannel. С тех пор рекомендуемые практики по масштабируемости смещаются в сторону NIO/асинхронных API.
  • В новых версиях JVM улучшалась поддержка IPv6 и контроль опций сокетов через системные свойства и API каналов. Непосредственных устареваний публичного API ServerSocket не происходило.

Резюмирующее замечание: при разработке новых высоконагруженных серверов стоит рассмотреть NIO/AsynchronousChannel, в то время как классический ServerSocket остаётся удобным для простых и средненагруженных задач.

Расширенные и редкие сценарии использования

Несколько детализированных примеров с пояснениями: SSL, неблокирующий режим, пул потоков и быстрое восстановление сервера.

Пример A: SSL-сервер с SSLServerSocketFactory

Пример java
import javax.net.ssl.*;
import java.io.*;

public class SSLServer {
    public static void main(String[] args) throws Exception {
        SSLServerSocketFactory factory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
        try (SSLServerSocket server = (SSLServerSocket) factory.createServerSocket(8443)) {
            System.out.println("SSL server on " + server.getLocalPort());
            try (SSLSocket s = (SSLSocket) server.accept();
                 BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()))) {
                System.out.println("Client connected");
                System.out.println(in.readLine());
            }
        }
    }
}
Ожидаемый вывод (при наличии корректного keystore):
SSL server on 8443
Client connected
(первая строка от клиента)

Пояснение: для продакшена требуется настроить keystore/TrustStore, иначе фабрика использует системные настройки или выдаст ошибки.

Пример B: Неблокирующий сервер через ServerSocketChannel и селектор

Пример java
import java.net.*;
import java.nio.*;
import java.nio.channels.*;
import java.util.*;

public class NioServer {
    public static void main(String[] args) throws Exception {
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);
        ssc.socket().bind(new InetSocketAddress(12345));
        Selector selector = Selector.open();
        ssc.register(selector, SelectionKey.OP_ACCEPT);
        while (true) {
            selector.select(500);
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()) {
                SelectionKey key = it.next();
                it.remove();
                if (key.isAcceptable()) {
                    SocketChannel sc = ssc.accept();
                    sc.configureBlocking(false);
                    sc.register(selector, SelectionKey.OP_READ);
                } else if (key.isReadable()) {
                    SocketChannel sc = (SocketChannel) key.channel();
                    ByteBuffer buf = ByteBuffer.allocate(256);
                    int r = sc.read(buf);
                    if (r <= 0) { sc.close(); }
                    else { buf.flip(); sc.write(buf); }
                }
            }
        }
    }
}
Результат: сервер обслуживает множество подключений в одном потоке, эхо-ответы отправляются клиентам.

Пояснение: неблокирующий режим уменьшает число потоков и накладные расходы на переключения контекста.

Пример C: Сервер с пулом потоков (ExecutorService)

Пример java
import java.net.*;
import java.io.*;
import java.util.concurrent.*;

public class ThreadPoolServer {
    public static void main(String[] args) throws Exception {
        try (ServerSocket server = new ServerSocket(12345)) {
            ExecutorService pool = Executors.newFixedThreadPool(10);
            while (!server.isClosed()) {
                Socket client = server.accept();
                pool.submit(() -> {
                    try (BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
                         BufferedWriter out = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()))) {
                        String s = in.readLine();
                        out.write("Echo: " + s + "\n");
                        out.flush();
                    } catch (IOException e) { /* логирование */ }
                });
            }
            pool.shutdown();
        }
    }
}
Результат: одновременно обрабатывается до 10 клиентов; остальные ожидают в очереди.

Пример D: Быстрый рестарт сервера с setReuseAddress

Пример java
public class ReuseExample {
    public static void main(String[] args) throws Exception {
        ServerSocket server = new ServerSocket();
        server.setReuseAddress(true);
        server.bind(new InetSocketAddress(12345));
        System.out.println("Bound");
        server.close();
    }
}
Если порт ранее был в TIME_WAIT, установка reuseAddress иногда позволяет связать порт сразу, результат:
Bound

Пояснение: поведение зависит от ОС и настроек стека TCP. Иногда флаг не снимает ограничений для уже существующих активных слушателей.

джава ServerSocket function comments

En
ServerSocket A server socket that waits for requests to come in over the network