「JavaでTCP/IPソケット通信を書きたいけれど、Socket・ServerSocketクラスの使い分けや3ウェイ・ハンドシェイクの流れが掴めない」とお悩みではありませんか。
本記事では、Java標準APIだけを使って動くチャットサーバー/クライアントを実装しながら、TCP/IPの基本と接続確立の仕組みを実務レベルで理解できる構成にまとめました。
読み終えるころには、以下ができるようになります。
- Java Socket/ServerSocketクラスでTCP通信を実装できる
- 3ウェイ・ハンドシェイクの流れを自分の言葉で説明できる
- try-with-resourcesで安全にソケットを閉じられる
- 複数クライアント接続に対応する方法が分かる
前提はJava 21(LTS)とJDKの動作環境のみ。ネットワーク初心者でも追体験しやすいサンプルコード付きで解説します。
ソケット通信とは?

ソケット通信とは、ネットワーク上の2つのプログラムが、ソケットと呼ばれる通信口を使ってやりとりする仕組みです。
具体的には、以下のような構成になります。
この2者が1対1でつながることで、データのやり取りができるようになります。
ソケットとは?
「ソケット」とは、通信を行うための“窓口”のようなものです。
送る側(クライアント)と受け取る側(サーバー)が、ソケットという窓口を通じてデータをやり取りします。
Javaでは、クライアントは Socket クラスを使って接続し、サーバーは ServerSocket クラスを使って接続を待ち受けます。
ポート番号とは|12345は空いてる?
ポート番号は、1台のマシン内で複数の通信を区別するための識別子(0〜65535)です。Javaでサーバーを立てる際は、以下のレンジを意識してください。
- 0〜1023(Well-Known Ports): HTTP(80)、HTTPS(443) などOS予約。管理者権限が必要なため避ける
- 1024〜49151(Registered Ports): アプリ用途。後述の 12345 もここに含まれる
- 49152〜65535(Dynamic Ports): 一時利用。クライアント側が自動割り当て
既にそのポートが使われていると BindException: Address already in use が発生します。使用中のポートは lsof -i :12345(macOS/Linux)や netstat -ano | findstr :12345(Windows)で確認できます。
ソケット通信はどうやって実現してるの?
Javaのソケット通信は、裏側でTCP(Transmission Control Protocol)という通信プロトコルを使っています。
TCPは、以下のような特徴を持っています。
- データが順番通りに届く
- 途中でデータが欠けたり壊れたりしないよう保証する
- 通信の開始・終了を丁寧に行う(3ウェイ・ハンドシェイク)
つまりTCPは、確実に・順番どおり・欠けずにデータを届けてくれる「書留郵便」のような仕組みです。
JavaでSocketやServerSocketを使った通信は、基本的にこのTCP通信の仕組みにのっとって動いています。
ネットワークの知識が浅くても、Javaのソケット通信を使えば「正しく届く通信」を自然に実現できます。
3ウェイ・ハンドシェイク
3ウェイ・ハンドシェイクの流れを3ステップで見ていきましょう。
手順1:クライアント → サーバーへ「SYN」(接続要求)
クライアントが「これから通信したいよ!」とサーバーにリクエストを送ります。
このとき送られるのが「SYN(シンクロナイズ)」という信号。
手順2:サーバー → クライアントへ「SYN-ACK」(許可と応答)
サーバーはそれを受け取って、「いいよ、準備できてるよ」と返事します。
これが「SYN-ACK(シンクロナイズ+応答)」。
手順3:クライアント → サーバーへ「ACK」(最終確認)
クライアントは、サーバーからの返事を確認して「じゃあ始めますね!」と返します。
これが「ACK(確認応答)」です。
※ 3ウェイ・ハンドシェイクの仕様詳細は RFC 9293(TCP)(2022年発行)で定義されています。
TCPとUDPの違い|Javaでどちらを選ぶべきか
ソケット通信にはTCP(Socket/ServerSocket)とUDP(DatagramSocket)の2種類があります。用途が大きく異なるため、Javaで実装する前に違いを押さえておきましょう。
| 項目 | TCP(Socket) | UDP(DatagramSocket) |
|---|---|---|
| 接続 | 3ウェイ・ハンドシェイクで確立 | 接続レス(いきなり送信) |
| 信頼性 | 順序保証・再送あり | 保証なし(欠損許容) |
| 速度 | UDPより遅い | 高速・軽量 |
| 代表用途 | HTTP/SSH/チャット/ファイル転送 | DNS/動画ストリーミング/ゲーム |
| Javaクラス | Socket / ServerSocket | DatagramSocket / DatagramPacket |
本記事で扱うのはTCPです。「データが確実に順番どおりに届いてほしい」アプリケーション(チャット・ログ転送など)はTCPを選びます。逆に、多少の欠損より速度を優先したい場合はUDPを検討してください。
Javaでソケット通信を実装してみよう
実際にコードを書いてみるとイメージが掴みやすくなります。ここでは、Java 21(LTS)環境を前提に、サーバーとクライアントを別々のJavaクラスとして実装します(Java 8以降であればそのまま動作確認できます)。
Javaのサーバー側のコード
import java.io.*;
import java.net.*;
public class SimpleServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("サーバーがポート12345で待機中...");
Socket clientSocket = serverSocket.accept();
System.out.println("クライアントが接続しました。");
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("受信: " + inputLine);
out.println("サーバーからの応答: " + inputLine);
}
in.close();
out.close();
clientSocket.close();
serverSocket.close();
}
}⚠️ 注意: 上記は説明用の基本形です。例外が発生すると close() がスキップされリソースリークするため、実務では後述の「try-with-resourcesで安全にソケットを閉じる」の書き方を使ってください。
コードのポイント解説
ServerSocket serverSocket = new ServerSocket(12345);
System.out.println("サーバーがポート12345で待機中...");Socket clientSocket = serverSocket.accept();
System.out.println("クライアントが接続しました。");BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);String inputLine;
while ((inputLine = in.readLine()) != null) {
System.out.println("受信: " + inputLine);
out.println("サーバーからの応答: " + inputLine);
}Javaのクライアント側のコード
import java.io.*;
import java.net.*;
public class SimpleClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 12345);
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String userInput;
System.out.println("メッセージを入力してください:");
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("サーバーから: " + in.readLine());
}
in.close();
out.close();
stdIn.close();
socket.close();
}
}⚠️ 注意: クライアント側も同様に、例外時のリソースリーク対策として実務では try-with-resources を使ってください。
コードのポイント解説
Socket socket = new Socket("localhost", 12345);BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);System.out.println("メッセージを入力してください:");
while ((userInput = stdIn.readLine()) != null) {
out.println(userInput);
System.out.println("サーバーから: " + in.readLine());
}in.close();
out.close();
stdIn.close();
socket.close();try-with-resourcesで安全にソケットを閉じる
先ほどのサンプルコードは基本を掴むためにあえて明示的な close() を書きましたが、実務では例外発生時のリソースリークを防ぐためtry-with-resources(Java 7以降)を使うのが必須です。
// サーバー側(try-with-resources版)
try (ServerSocket serverSocket = new ServerSocket(12345);
Socket clientSocket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
String inputLine;
while ((inputLine = in.readLine()) != null) {
out.println("サーバーからの応答: " + inputLine);
}
} catch (IOException e) {
e.printStackTrace();
}Socket・ServerSocket・BufferedReader・PrintWriter はすべて AutoCloseable を実装しているため、tryの括弧に並べるだけで逆順に close() が自動実行されます。これで例外が起きても確実にリソースが解放されます。
複数クライアントに対応する(スレッド化)
現状のサンプルは accept() が1回だけ呼ばれ、クライアント切断後にサーバーも終了します。チャットサーバーのように複数人を同時に受け入れるには、accept() をループし、受理ごとに新スレッドで処理させます。
try (ServerSocket serverSocket = new ServerSocket(12345)) {
ExecutorService pool = Executors.newFixedThreadPool(10);
while (true) {
Socket client = serverSocket.accept();
pool.submit(() -> handleClient(client));
}
}ExecutorService で並列度を制御することで、スレッド無制限生成による OOM を防げます。Java 21 以降であれば Executors.newVirtualThreadPerTaskExecutor() で仮想スレッドを使うのも選択肢です。
参考: Oracle公式の JEP 444: Virtual Threads では、1万接続を超えるソケットI/O待機において仮想スレッドはプラットフォームスレッドより大幅に高いスループットを示します。大量同時接続が必要なチャット/ゲームサーバーでは検討の価値があります。
Javaソケット通信でよくあるエラーと対処法
| エラー | 原因 | 対処 |
|---|---|---|
| java.net.ConnectException: Connection refused | サーバー未起動/ポート違い/ファイアウォール | サーバープロセスの起動確認、ポート番号の一致確認 |
| java.net.BindException: Address already in use | 同じポートを別プロセスが使用中 | lsof -i :12345 で特定し kill するか、別ポートへ変更 |
| java.net.SocketTimeoutException | setSoTimeout() で設定したタイムアウト超過 | タイムアウト時間の見直し、ネットワーク状態の確認 |
| java.io.EOFException | 相手側が突然切断 | readLine() の戻り値 null をハンドリング、再接続処理を追加 |
実行結果(筆者環境: macOS 14 / Java 21)
手元で SimpleServer と SimpleClient を別ターミナルで起動した際の出力は以下のとおりです。
# [ターミナル1] サーバー側
$ java SimpleServer
サーバーがポート12345で待機中...
クライアントが接続しました。
受信: hello
受信: java socket
# [ターミナル2] クライアント側
$ java SimpleClient
メッセージを入力してください:
hello
サーバーから: サーバーからの応答: hello
java socket
サーバーから: サーバーからの応答: java socketソケット通信のポイント
実装してみると、以下の点が重要であることが分かります。
ポイント
- ServerSocketはクライアントからの接続を受け入れる役割
- Socketは接続を行い、InputStream/OutputStreamを使って通信する
- 通信は一方通行ではなく、双方向(送る・受け取る)が可能
- 必ず入出力ストリームを閉じて、リソースを開放すること
ソケットの流れは以下の通りになります。
[ユーザー入力] → [SimpleClient] →→→ [SimpleServer]
(クライアントが入力 → サーバーへ送信 → サーバーが応答 → クライアントが表示)
よくある質問(FAQ)
Q1. SocketとServerSocketの違いは?
ServerSocketはサーバー側でクライアント接続を「待ち受ける」専用クラスで、accept() が返す Socket が実際の双方向通信チャネルです。クライアント側は直接 new Socket(host, port) で接続します。
Q2. Java NIO(SocketChannel)との違いは?
本記事の Socket/ServerSocket はブロッキングI/Oで、1接続1スレッドが基本です。大量接続が必要なら java.nio の SocketChannel + Selector(ノンブロッキングI/O)を検討します。Java 21以降の仮想スレッドを使えば従来型でも大量接続が現実的になりました。
Q3. 文字化けが発生する場合の対処は?
InputStreamReader/OutputStreamWriter の第2引数で明示的に StandardCharsets.UTF_8 を指定します。デフォルトはプラットフォーム依存のため、OS間連携で文字化けの原因になります。
Q4. SSL/TLSで暗号化通信したい
SSLSocketFactory.getDefault().createSocket(host, port) で SSLSocket を生成できます。サーバー側は SSLServerSocket を使い、keystore を設定します。本番運用では必須です。
まとめ:Javaでソケット通信は難しくない
本記事では、JavaでTCP/IPソケット通信を実装する手順を、基本概念からサンプルコード・よくあるエラーまで体系的に解説しました。
TCPプロトコルの仕組みや3ウェイ・ハンドシェイクの流れ、JavaのSocket・ServerSocketクラスの使い方を一貫して学ぶことで、ネットワーク通信の基礎が自然に身につきます。
【要点まとめ】
- ソケット通信とはネットワーク越しにプログラム同士が通信する仕組み
- サーバーはServerSocketで待ち受け、クライアントはSocketで接続する
- Javaのソケット通信はTCPプロトコルを利用している
- TCPは順序通り・確実にデータを届ける特徴がある
- 接続開始時は3ウェイ・ハンドシェイクが行われる
- サーバー側はaccept()でクライアントの接続を待つ
- クライアント側はユーザー入力を送信し、サーバーからの応答を受信する
- 通信は双方向であり、入力と出力のストリームをそれぞれ使う
- 通信終了時はすべてのリソースを明示的にcloseすることが重要
- サンプルコードを理解することで自作アプリ開発の基礎が築ける
Javaでのソケット通信は、一見とっつきにくく感じるかもしれませんが、仕組みさえ理解してしまえば非常にシンプルです。
サンプルコードを実行しながら、通信の流れを体感することで、ネットワークプログラミングの土台がしっかりと身につきます。