Socket: примеры (JAVA)
Socket(String host, int port)Общее описание класса Socket
Класс java.net.Socket представляет собой TCP-клиентский сокет, используемый для установления соединения с удалённым хостом по протоколу TCP. Применяется при реализации клиентской части сетевых приложений, обмене потоковыми данными и создании протоколов поверх TCP.
Конструкторами и методами задаются адреса, параметры соединения и поведение сокета. Класс обычно работает вместе с ServerSocket для серверной части и с потоками InputStream/OutputStream для ввода-вывода.
Параметры, конструкторы и возвращаемые значения
- Конструкторы:
Socket()- создаёт не подключённый сокет (позволяет затем вызватьconnectилиbind).Socket(String host, int port)- устанавливает соединение с указанным хостом и портом; выбрасываетUnknownHostExceptionилиIOExceptionпри ошибках.Socket(InetAddress address, int port)- аналогично, по InetAddress.Socket(String host, int port, InetAddress localAddr, int localPort)- устанавливает соединение и привязывает к локальному адресу и порту.Socket(Proxy proxy)- создаёт сокет с указанным прокси (например, HTTP или SOCKS).
- Ключевые методы:
connect(SocketAddress endpoint)иconnect(SocketAddress endpoint, int timeout)- подключение; возвращаетvoid, при превышении таймаута выбрасываетSocketTimeoutException, при других ошибках -IOException.bind(SocketAddress bindpoint)- привязка к локальному адресу; возвращаетvoid, может вызватьIOExceptionилиBindException.close()- закрытие сокета и связанных потоков; возвращаетvoid, многократно вызывать безопасно.getInputStream()- возвращаетInputStreamдля чтения; при закрытом сокете бросаетIOException.getOutputStream()- возвращаетOutputStreamдля записи.isConnected(),isClosed()- булевы состояния соединения.- Параметры сокета (устанавливаются и читаются):
setSoTimeout(int timeout)/getSoTimeout()- таймаут чтения в миллисекундах (0 - ожидание бесконечно).setTcpNoDelay(boolean)/getTcpNoDelay()- включение/выключение Nagle.setKeepAlive(boolean)- включение периодического keep-alive.setSoLinger(boolean on, int linger)- поведение при закрытии при наличии данных в буфере.setReceiveBufferSize(int),setSendBufferSize(int)- размер буферов сокета.
- Возвращаемые значения и исключения:
- Большинство сетевых операций возвращают
voidили потоковые объекты; при ошибках выбрасываются исключения семействаIOException(включаяUnknownHostException,SocketTimeoutException,BindException,ConnectException). - Методы состояния возвращают примитивы:
booleanилиint.
- Большинство сетевых операций возвращают
Класс является Closeable, что позволяет использовать try-with-resources для автоматического закрытия.
Простые варианты использования
Ниже представлены компактные примеры основных сценариев: подключение к серверу, чтение и запись, обработка таймаута и привязка к локальному адресу.
1) Клиент: простой запрос и ответ
import java.io.*;
import java.net.*;
public class SimpleClient {
public static void main(String[] args) throws Exception {
try (Socket socket = new Socket("example.com", 80)) {
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n".getBytes());
out.flush();
BufferedReader reader = new BufferedReader(new InputStreamReader(in));
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}
HTTP/1.1 200 OK ... (заголовки и HTML-страница)
2) Таймаут при подключении
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress("10.255.255.1", 80), 2000); // 2 секунды
} catch (SocketTimeoutException e) {
System.out.println("connect timed out");
} finally {
socket.close();
}
connect timed out
3) Привязка к локальному адресу
Socket s = new Socket();
s.bind(new InetSocketAddress("192.168.1.100", 0)); // выбрать локальный порт автоматически
s.connect(new InetSocketAddress("remote.host", 8080));
// использование потоков...
s.close();
(нет вывода; подключение к удаленному хосту с указанного локального адреса)
Сопоставимые классы в Java
Внутри Java имеются классы, служащие для схожих задач, с отличиями по модели и возможностям:
- DatagramSocket - для UDP: без установления соединения, без гарантий доставки, применяется для простых, быстрых и некритичных по порядку сообщений.
- SocketChannel (NIO) - неблокирующий ввод-вывод, лучше подходит для большого числа одновременных соединений и асинхронных серверов.
- SSLSocket - надстройка над Socket для TLS/SSL: требуется для защищённых соединений.
- HttpClient (java.net.http) - более высокий уровень для HTTP, скрывает детали сокетов и обеспечивает асинхронность и удобство.
Выбор зависит от требований: для простых TCP-соединений - Socket; для высокой масштабируемости - SocketChannel; для шифрования - SSLSocket или TLS-конфигурации; для HTTP - HttpClient.
Альтернативы в других языках
Схожая функциональность доступна во многих языках. Ниже примеры и основные отличия от Java:
Python
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("example.com", 80))
s.sendall(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
print(s.recv(1024))
s.close()
b'HTTP/1.1 200 OK\r\n...'
Отличие: более компактный синтаксис, прямой доступ к байтовым операциям, есть синхронный и асинхронный API (asyncio).
Node.js (JavaScript)
const net = require('net');
const client = net.createConnection({ host: 'example.com', port: 80 }, () => {
client.write('GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n');
});
client.on('data', (data) => { console.log(data.toString()); });
client.on('end', () => {});
HTTP/1.1 200 OK ...
Отличие: событийная модель, неблокирующий ввод-вывод по умолчанию.
PHP
$fp = fsockopen('example.com', 80, $errno, $errstr, 5);
fwrite($fp, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n");
echo fgets($fp);
fclose($fp);
HTTP/1.1 200 OK ...
Отличие: часто используется в контексте веб-приложений, удобные обёртки для потоков.
C#
using System.Net.Sockets;
var client = new TcpClient("example.com", 80);
var stream = client.GetStream();
// запись/чтение
client.Close();
(похожие результаты HTTP)
Отличие: .NET предлагает TcpClient/TcpListener и асинхронные методы Task-based.
Go
package main
import (
"fmt"
"net"
)
func main() {
conn, _ := net.Dial("tcp", "example.com:80")
fmt.Fprintf(conn, "GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n")
buf := make([]byte, 1024)
n, _ := conn.Read(buf)
fmt.Println(string(buf[:n]))
}
HTTP/1.1 200 OK ...
Отличие: встроенная простота сетевого API, лёгкая работа с горутинами для параллелизма.
Kotlin
В Kotlin используется тот же Java API, код компактнее из-за языка, при необходимости применяются корутины для асинхронности.
Lua (LuaSocket)
local socket = require('socket')
local tcp = assert(socket.tcp())
tcp:connect('example.com', 80)
tcp:send('GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n')
print(tcp:receive('*l'))
tcp:close()
HTTP/1.1 200 OK
Итог: синтаксис и модель выполнения различаются, но концепция сокета как абстракции TCP-потока сохраняется; Java выделяется строгой типизацией и интеграцией с JVM-моделями (потоки, NIO, SSL).
Типичные ошибки при работе с Socket
- Не закрытие сокета и утечки ресурсов: отсутствие close() приводит к исчерпанию дескрипторов.
- Блокировки при чтении без таймаута: вызов
read()может блокировать поток навсегда при отсутствии данных. - Неправильная обработка потоков: использование getInputStream()/getOutputStream() после close вызывает
IOException. - Игнорирование исключений при connect:
UnknownHostExceptionиConnectExceptionтребуют обработки для информативности о проблеме. - Использование порта, уже занятого другим процессом:
BindException. - Неправильное чтение протокола: чтение фиксированного числа байт вместо обработки окончания потока или заголовков приводит к разрывам протокола.
Пример: блокировка без таймаута
try (Socket s = new Socket("10.255.255.1", 12345)) {
InputStream in = s.getInputStream();
int b = in.read(); // может заблокировать надолго
System.out.println(b);
} catch (IOException e) {
e.printStackTrace();
}
(программа зависает или длительно ждёт ответа)
Пример: ошибка привязки к занятому порту
ServerSocket srv = new ServerSocket(8080);
// ... в другом месте попытка создать ещё один ServerSocket на 8080
ServerSocket srv2 = new ServerSocket(8080);
java.net.BindException: Address already in use: bind
Рекомендация по диагностике: логирование исключений, установка таймаутов через setSoTimeout и connect(..., timeout), использование try-with-resources и профилирование дескрипторов.
Изменения и эволюция API
API сокетов в Java сохраняет стабильность с ранних версий. Основные изменения и смежные улучшения в экосистеме:
- Переход к модульной системе в Java 9 затронул доступность пакетов, но функциональность
java.net.Socketосталась доступной в стандартной библиотеке. - Развитие NIO и NIO.2 (SocketChannel, AsynchronousSocketChannel) добавило неблокирующие и асинхронные модели, рекомендованные для высоконагруженных приложений.
- Интеграция с TLS через
SSLSocket/SSLEngineи улучшенные средства управления сертификатами в современных версиях JDK. - Мелкие улучшения и исправления стабильности в реализации платформы, серьезных изменений в сигнатурах методов не происходило.
В результате для новой разработки часто рассматриваются NIO/ASYNC вариации и более высокоуровневые клиентские библиотеки (например, java.net.http для HTTP), в то время как классический Socket остаётся рабочим выбором для простых TCP-сценариев.
Расширенные и нетривиальные примеры
Несколько продвинутых сценариев: многопоточный echo-сервер и клиент, использование SSLSocket, SocketChannel (NIO) и подключение через SOCKS-прокси.
1) Многопоточный эхо-сервер и клиент
// EchoServer.java
import java.io.*;
import java.net.*;
public class EchoServer {
public static void main(String[] args) throws Exception {
try (ServerSocket server = new ServerSocket(9000)) {
while (true) {
Socket client = server.accept();
new Thread(() -> {
try (Socket s = client;
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(), true)) {
String line;
while ((line = in.readLine()) != null) {
out.println("Echo: " + line);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
}
// EchoClient.java
import java.net.*;
import java.io.*;
public class EchoClient {
public static void main(String[] args) throws Exception {
try (Socket s = new Socket("localhost", 9000);
BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
PrintWriter out = new PrintWriter(s.getOutputStream(), true)) {
out.println("Hello");
System.out.println(in.readLine());
}
}
}
(сервер запускается, клиент выводит) Echo: Hello
2) SSL-соединение (клиент)
import javax.net.ssl.*;
import java.io.*;
public class SSLClient {
public static void main(String[] args) throws Exception {
SSLSocketFactory factory = (SSLSocketFactory) SSLSocketFactory.getDefault();
try (SSLSocket socket = (SSLSocket) factory.createSocket("www.google.com", 443)) {
socket.startHandshake();
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out.println("GET / HTTP/1.1\r\nHost: www.google.com\r\nConnection: close\r\n\r\n");
String line;
while ((line = in.readLine()) != null) System.out.println(line);
}
}
}
HTTP/1.1 200 OK ... (зашифрованный ответ, расшифрованный SSLSocket)
3) Неблокирующий клиент через SocketChannel
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NioClient {
public static void main(String[] args) throws Exception {
SocketChannel sc = SocketChannel.open();
sc.configureBlocking(false);
sc.connect(new InetSocketAddress("example.com", 80));
while (!sc.finishConnect()) {
// можно выполнять другие задачи
}
ByteBuffer buf = ByteBuffer.wrap("GET / HTTP/1.1\r\nHost: example.com\r\nConnection: close\r\n\r\n".getBytes());
sc.write(buf);
ByteBuffer resp = ByteBuffer.allocate(1024);
int read = sc.read(resp);
if (read > 0) {
resp.flip();
byte[] b = new byte[resp.remaining()];
resp.get(b);
System.out.println(new String(b));
}
sc.close();
}
}
HTTP/1.1 200 OK ...
4) Подключение через SOCKS-прокси
SocketAddress addr = new InetSocketAddress("socks-proxy.local", 1080);
Proxy proxy = new Proxy(Proxy.Type.SOCKS, addr);
try (Socket s = new Socket(proxy)) {
s.connect(new InetSocketAddress("example.com", 80));
// обмен данными через прокси
}
(соединение устанавливается через указанный SOCKS-прокси)
5) Передача файла с индикатором прогресса
import java.io.*;
import java.net.*;
// Отправитель
try (Socket s = new Socket("server", 9001);
OutputStream out = s.getOutputStream();
FileInputStream fis = new FileInputStream("large.bin")) {
byte[] buf = new byte[8192];
long total = 0;
int r;
while ((r = fis.read(buf)) != -1) {
out.write(buf, 0, r);
total += r;
System.out.println("Sent: " + total);
}
}
Sent: 8192 Sent: 16384 ... (прогресс передачи)
Пояснения: в примерах показаны разные уровни абстракции - от обычных блокирующих потоков до асинхронных каналов и SSL. Для производительных серверов предпочтительнее NIO/AsynchronousChannel; для защищённых соединений - SSLSocket/SSLEngine.