Socket.getOutputStream: примеры (JAVA)

OutputStream сокета: примеры и пояснения
Раздел: Сокеты (сетевые)
Socket.getOutputStream: OutputStream

Описание метода Socket.getOutputStream()

Метод Socket.getOutputStream() возвращает объект java.io.OutputStream, через который отправляются байты на удалённую сторону соединения. Подпись метода в классе java.net.Socket выглядит так: public OutputStream getOutputStream() throws java.io.IOException. Аргументов у метода нет.

Когда используется: после установления соединения (например, после вызова connect() на клиенте или после ServerSocket.accept() на сервере). Через возвращённый поток выполняется запись данных, которые будут переданы через сетевой сокет.

Возвращаемое значение: объект OutputStream. Повторные вызовы метода обычно возвращают один и тот же экземпляр потока, связанный с сокетом.

Исключения и особые состояния:

  • IOException - если произошла общая сетевая ошибка или поток не может быть получен.
  • SocketException (подкласс IOException) - если сокет закрыт или соединение разорвано.

Важные замечания по использованию:

  • Закрытие возвращённого потока приводит к закрытию соответствующего сокета. Точно так же закрытие сокета закроет связанные потоки.
  • Метод не принимает параметров и не выполняет блокировок для установления соединения; если сокет ещё не подключён, вызов может привести к IOException.
  • Параллельная запись в один и тот же OutputStream из нескольких потоков требует внешней синхронизации.
  • Для защищённых соединений используется SSLSocket.getOutputStream(), поведение схожее, но данные проходят через SSL-обёртку.
  • Для неблокирующих каналов предпочтительнее использовать SocketChannel и методы чтения/записи ByteBuffer.

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

1) Простой клиент, отправляющий строку; сервер читает и печатает.

// Сервер
import java.net.*;
import java.io.*;

ServerSocket ss = new ServerSocket(12345);
Socket s = ss.accept();
InputStream in = s.getInputStream();
byte[] buf = new byte[1024];
int r = in.read(buf);
System.out.println(new String(buf, 0, r));
s.close();
ss.close();

// Клиент
Socket c = new Socket("localhost", 12345);
OutputStream out = c.getOutputStream();
out.write("Hello Server".getBytes());
out.flush();
c.close();
Server output:
Hello Server

2) Использование PrintWriter для удобной отправки строк с автофлашем.

Socket s = new Socket("example.com", 80);
PrintWriter pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream()), true);
pw.println("GET / HTTP/1.1");
pw.println("Host: example.com");
pw.println();
// далее чтение ответа
s.close();
(Перечень строк, отправленных на сервер HTTP; далее сервер вернёт HTTP-ответ)

3) ObjectOutputStream для отправки сериализуемого объекта.

Socket s = new Socket("localhost", 54321);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
oos.writeObject("Привет");
oos.flush();
s.close();
На стороне приёма ObjectInputStream.readObject() вернёт строку "Привет"

Похожие методы в Java и особенности

  • Socket.getInputStream() - противоположный метод для чтения данных. Используется вместе с getOutputStream() при двунаправленном обмене.
  • SocketChannel (NIO) - альтернативный механизм для неблокирующего ввода/вывода. Предпочтителен при высоких требованиях к производительности и большом числе соединений.
  • SSLSocket.getOutputStream() - для защищённых каналов; добавляет шифрование и проверки сертификатов.
  • Channels.newOutputStream(socketChannel) - преобразование NIO-канала в OutputStream при необходимости интеграции со старым кодом.

Когда использовать: при обычной синхронной работе с сокетами достаточно getOutputStream(). Для неблокирующего ввода/вывода и сложных серверов лучше применять SocketChannel и селекторы.

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

  • Python: модуль socket, методы socket.send() и socket.makefile('wb'). makefile возвращает объект, похожий на поток, пригодный для записи текстовых или бинарных данных.
    # Клиент
    import socket
    s = socket.create_connection(('localhost', 12345))
    s.send(b'Hello')
    s.close()
    На сервере придут байты b'Hello'
  • Node.js / JavaScript: модуль net, объект socket имеет метод write().
    const net = require('net');
    const s = net.createConnection(12345, 'localhost', () => {
      s.write('Hello');
      s.end();
    });
    На стороне сервера произойдёт событие 'data' с буфером 'Hello'
  • PHP: функции fsockopen и fwrite.
    $s = fsockopen('127.0.0.1', 12345);
    fwrite($s, "Hello");close($s);
    На сервере будут получены байты "Hello"
  • C#: TcpClient.GetStream() возвращает NetworkStream, можно вызвать Write().
    var client = new TcpClient("localhost", 12345);
    var ns = client.GetStream();
    var buf = System.Text.Encoding.UTF8.GetBytes("Hello");
    ns.Write(buf, 0, buf.Length);
    client.Close();
    Сервер получит строку "Hello"
  • Go: интерфейс net.Conn, метод Write().
    conn, _ := net.Dial("tcp", "localhost:12345")
    conn.Write([]byte("Hello"))
    conn.Close()
    Данные "Hello" поступят на сервер
  • Kotlin: использует тот же JVM API, что и Java, поэтому Socket.getOutputStream() идентичен.
  • Lua: библиотека LuaSocket, функция socket.connect и метод .
    local socket = require('socket')
    local c = assert(socket.connect('localhost', 12345))
    c:send('Hello')
    c:close()
    На стороне приёма байты 'Hello'

Отличия от Java: синтаксис и модель потоков различаются, некоторые языки предоставляют более прямые методы отправки байтов (send, write) без обёртки OutputStream. В Python и PHP удобны текстовые файловые обёртки, в C# и Go используются собственные stream-интерфейсы, совместимые с экосистемой языка.

Типичные ошибки и примеры

1) Попытка получить поток после закрытия сокета.

Socket s = new Socket("localhost", 12345);
s.close();
OutputStream out = s.getOutputStream();
java.net.SocketException: Socket is closed
    at java.net.Socket.getOutputStream(Socket.java:...)

2) Неправильная работа при одновременной записи из нескольких потоков без синхронизации.

// Два потока пишут одновременно в один OutputStream, данные могут перемешаться
OutputStream out = s.getOutputStream();
new Thread(() -> { out.write("AAA".getBytes()); out.flush(); }).start();
new Thread(() -> { out.write("BBB".getBytes()); out.flush(); }).start();
На стороне приёма возможны смешанные последовательности вроде "AABABB" вместо чистых "AAABBB"

3) Игнорирование flush() при использовании буферизующих обёрток (например, BufferedOutputStream, PrintWriter без автофлаша).

PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.println("Line");
// если не вызвать pw.flush(), данные могут не уйти сразу
Получатель не увидит строки до тех пор, пока буфер не будет сброшен или поток не закроется

4) Попытка использовать getOutputStream() на несвязанном Socket (например, после создания Socket без подключения) может привести к IOException.

Изменения и совместимость

Метод Socket.getOutputStream() является частью API с ранних версий Java и существенно не изменялся. В современных версиях JVM корректность работы зависит прежде всего от реализации сокета (обычный TCP, SSLSocket или кастомные провайдеры). Для новых проектов практичнее рассматривать NIO (SocketChannel) или асинхронный API (AsynchronousSocketChannel) при необходимости неблокирующего ввода-вывода.

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

1) Полузакрытие канала (half-close) с shutdownOutput для уведомления удалённой стороны об окончании записи.

Пример java
Socket s = new Socket("localhost", 12345);
OutputStream out = s.getOutputStream();
out.write("Data before half-close".getBytes());
out.flush();
s.shutdownOutput(); // отправляет eof на другую сторону для чтения
// после shutdownOutput попытка записать приведёт к IOException
На стороне приёма InputStream.read() вернёт -1 после чтения всех данных, если другая сторона вызвала shutdownOutput()

2) Использование ObjectOutputStream с последующим reset(), чтобы избежать накопления ссылок при отправке большого числа объектов.

Пример java
Socket s = new Socket("localhost", 6000);
ObjectOutputStream oos = new ObjectOutputStream(s.getOutputStream());
for (int i = 0; i < 1000; i++) {
  oos.writeObject(new java.util.Date());
  if (i % 50 == 0) oos.reset(); // сбрасывает таблицу ссылок
}
oos.flush();
s.close();
На приёмной стороне ObjectInputStream корректно десериализует 1000 объектов без чрезмерного роста памяти в sender

3) Интеграция с NIO: преобразование SocketChannel в OutputStream при необходимости вызова библиотечного кода, работающего с OutputStream.

Пример java
SocketChannel ch = SocketChannel.open(new InetSocketAddress("host", 80));
ch.configureBlocking(true);
OutputStream os = java.nio.channels.Channels.newOutputStream(ch);
os.write("GET / HTTP/1.1\r\nHost: host\r\n\r\n".getBytes());
os.flush();
// далее чтение с помощью Channels.newInputStream(ch)
Сетевой запрос отправлен через NIO-канал, получен HTTP-ответ

4) Использование SSLSocket для отправки данных по TLS/SSL. Поведение getOutputStream схоже, но перед записью выполняется рукопожатие и шифрование.

Пример java
SSLSocketFactory f = (SSLSocketFactory) SSLSocketFactory.getDefault();
SSLSocket s = (SSLSocket) f.createSocket("example.com", 443);
OutputStream out = s.getOutputStream();
out.write("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n".getBytes());
out.flush();
// чтение зашифрованного ответа через getInputStream()
Получен зашифрованный HTTP-ответ, расшифрованный SSLSocket

5) Реализация таймаута записи через SocketChannel: стандартный Socket не поддерживает таймаут записи, поэтому требуется использовать неблокирующий канал и селектор.

Пример java
// Упрощённый пример: открытие неблокирующего канала и попытка записи с проверкой через селектор
SocketChannel ch = SocketChannel.open();
ch.configureBlocking(false);
ch.connect(new InetSocketAddress("host", 12345));
while (!ch.finishConnect()) { /* ожидание */ }
ByteBuffer buf = ByteBuffer.wrap("LargeData".getBytes());
Selector sel = Selector.open();
ch.register(sel, SelectionKey.OP_WRITE);
if (sel.select(2000) == 0) {
  throw new java.io.IOException("write timeout");
}
ch.write(buf);
ch.close();
Если за 2 секунды запись не стала возможной, возникнет исключение с текстом "write timeout"

6) Потенциальная интеграция с протоколами верхнего уровня: реализация простого текстового протокола с фреймингом, где сначала посылается длина блока, затем данные.

Пример java
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
byte[] payload = ...;
dos.writeInt(payload.length);
dos.write(payload);
dos.flush();
На стороне приёма DataInputStream.readInt() даст длину, затем можно считать ровно это число байт

джава Socket.getOutputStream function comments

En
Socket.getOutputStream Returns an output stream for this socket