本ページはプロモーションが含まれています

Java入門

【2026年版】Javaソケット通信サンプルコード|Socket/ServerSocketでTCP/IP通信を実装する手順

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

「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(確認応答)」です。

sequenceDiagram participant クライアント participant サーバー クライアント->>サーバー: SYN(接続要求) サーバー-->>クライアント: SYN-ACK(接続許可+応答) クライアント->>サーバー: ACK(応答) Note right of サーバー: 接続確立(通信開始)

※ 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 / ServerSocketDatagramSocket / 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で待機中...");

ポイント

  • ServerSocket:サーバー側の待ち受け口。
  • 12345:通信に使うポート番号(自由に選べるが、重複に注意)。
  • serverSocket.accept():クライアントからの接続を待つ(ブロッキング処理)。
Socket clientSocket = serverSocket.accept();
System.out.println("クライアントが接続しました。");

ポイント

  • クライアントからの接続を待つ
  • クライアントからの接続を受け入れ、双方向通信用の Socket オブジェクトを取得。
BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);

ポイント

  • BufferedReader:クライアントから送られてくるテキストを1行ずつ読むための準備。
  • PrintWriter:クライアントへテキストを送るための出力。
String inputLine;
while ((inputLine = in.readLine()) != null) {
    System.out.println("受信: " + inputLine);
    out.println("サーバーからの応答: " + inputLine);
}

ポイント

  • クライアントからのメッセージを1行ずつ読み込む。
  • 読み込んだ内容を System.out.println() で表示。
  • クライアントへ「サーバーからの応答:」を付けて返信。
  • null が返ると(=クライアント切断)、ループ終了。

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);

ポイント

  • localhost: 自分のPC(ループバックアドレス)。
  • 12345: サーバーが待ち受けているポート。
  • リモート接続時は IPアドレスまたはホスト名を指定(例: new Socket("192.168.1.10", 12345))。
  • new Socket(...) で TCP 接続を開始。
    • サーバーが動いていないと ConnectException が発生。
BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in));
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);

ポイント

名前用途
stdInユーザーからの標準入力取得(キーボード入力)
inサーバーからのメッセージを受信
outサーバーへのメッセージ送信(true で自動フラッシュ)
System.out.println("メッセージを入力してください:");
while ((userInput = stdIn.readLine()) != null) {
    out.println(userInput);
    System.out.println("サーバーから: " + in.readLine());
}

ポイント

  • ユーザーが1行入力するたびに:
    • サーバーへ送信(out.println()
    • サーバーからの返信を受け取り(in.readLine())、表示
in.close();
out.close();
stdIn.close();
socket.close();

ポイント

  • 入出力ストリームとソケットを明示的に閉じてリソースを解放。
  • 必ず 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.SocketTimeoutExceptionsetSoTimeout() で設定したタイムアウト超過タイムアウト時間の見直し、ネットワーク状態の確認
java.io.EOFException相手側が突然切断readLine() の戻り値 null をハンドリング、再接続処理を追加

筆者の体験談

筆者が初めてソケット通信を書いた際、テスト後に Ctrl+C でサーバーを落とし再起動したら BindException: Address already in use が30秒〜2分ほど消えず混乱しました。これはTCPの TIME_WAIT 状態が残っているためで、ServerSocket#setReuseAddress(true) をバインド前に呼ぶことで即再利用できるようになります。この1行で開発時の試行回数が体感3倍速くなりました。

実行結果(筆者環境: 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.nioSocketChannel + Selector(ノンブロッキングI/O)を検討します。Java 21以降の仮想スレッドを使えば従来型でも大量接続が現実的になりました。

Q3. 文字化けが発生する場合の対処は?

InputStreamReaderOutputStreamWriter の第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でのソケット通信は、一見とっつきにくく感じるかもしれませんが、仕組みさえ理解してしまえば非常にシンプルです。

サンプルコードを実行しながら、通信の流れを体感することで、ネットワークプログラミングの土台がしっかりと身につきます。

  • この記事を書いた人
  • 最新記事

トム

・都内自社開発企業勤務/Javaバックエンドエンジニア
/Java歴10年以上 ・首都圏在住30代
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎 詳細なプロフィール

-Java入門