概要
TCP ソケット通信は HTTP や SMTP といった上位プロトコルの基盤であり、ネットワークプログラミングの出発点です。Java では ServerSocket でサーバーを待ち受け、Socket でクライアントから接続するという明確な構造が用意されています。この記事では、最もシンプルなエコーサーバーを例に、TCP の接続確立からメッセージの送受信、切断までの一連の流れを実装します。Java 8 では匿名クラスと明示的な型宣言で書いていた処理が、Java 17 では record や var で簡潔になり、Java 21 では仮想スレッドで接続ごとのスレッド管理から解放されるという進化も確認できます。
使いどころ
社内ツール間のプロセス間通信を TCP ソケットで実装し、コマンドを送受信する
既存のテキストベースプロトコル(POP3、FTP など)のクライアントを Java で実装する
テスト用のモックサーバーを ServerSocket で構築し、クライアント側の通信処理を検証する
コード例
import java.io.*;
public class TcpSocketSample {
record ClientMessage(String text) {}
public static void startEchoServer(int port, ExecutorService executor)
throws IOException {
var serverSocket = new ServerSocket(port);
executor.submit(() -> {
try {
var client = serverSocket.accept();
try (var in = new BufferedReader(
new InputStreamReader(client.getInputStream()));
var out = new PrintWriter(
client.getOutputStream(), true)) {
String line;
while ((line = in.readLine()) != null) {
out.println("ECHO: " + line);
if ("EXIT".equals(line)) break;
}
}
serverSocket.close();
} catch (IOException e) {
}
});
}
public static void runClient(int port) throws IOException {
var messages = List.of(
new ClientMessage("Hello, Server!"),
new ClientMessage("EXIT")
);
try (var socket = new Socket("localhost", port);
var out = new PrintWriter(socket.getOutputStream(), true);
var in = new BufferedReader(
new InputStreamReader(socket.getInputStream()))) {
for (var msg : messages) {
out.println(msg.text());
System.out.println("受信: " + in.readLine());
}
}
}
public static void main(String[] args) throws Exception {
int port = 9000;
var executor = Executors.newSingleThreadExecutor();
startEchoServer(port, executor);
Thread.sleep(100);
runClient(port);
executor.shutdown();
}
}Version Coverage
record でメッセージを型安全に表現し、var で記述を簡潔にできる。テキストブロックも活用可能。
// Java 17: record + var で簡潔に
record ClientMessage(String text) {}
var messages = List.of(
new ClientMessage("Hello"),
new ClientMessage("EXIT"));
try (var socket = new Socket("localhost", port);
var out = new PrintWriter(
socket.getOutputStream(), true);
var in = new BufferedReader(
new InputStreamReader(
socket.getInputStream()))) {
for (var msg : messages) {
out.println(msg.text());
System.out.println(in.readLine());
}
}Library Comparison
注意点
ServerSocket.accept() はブロッキング呼び出し。メインスレッドで呼ぶと他の処理が止まるため、別スレッドで実行すること
クライアント側の Socket は try-with-resources で確実にクローズする。クローズし忘れるとポート枯渇の原因になる
BufferedReader.readLine() は改行文字を待つ。送信側が println() ではなく print() を使うと受信側が永久にブロックする
ループバック(localhost)では動作するが、異なるマシン間ではファイアウォールやポート開放の設定が必要
FAQ
信頼性が必要な通信(ファイル転送、API 呼び出し)は TCP、速度優先で多少のパケットロスが許容される場合(ログ配信、ストリーミング)は UDP を選びます。
accept() で受け取った Socket を ExecutorService に渡してスレッドプールで処理します。Java 21 なら Virtual Thread で接続ごとにスレッドを生成できます。
Wireshark でパケットキャプチャするか、telnet / nc コマンドで手動接続してプロトコルの動作を確認するのが効果的です。