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

Java入門

Javaソケット通信とは?TCP/IPをわかりやすく解説【コード付】

CATEGORY
REV.
DRAWN BY
TOM
SHEETS
11枚
Socket(TCP/IP通信をコードで)

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Javaの「ソケット通信」とは、ネットワーク越しに2つのプログラムがデータをやり取りする仕組みのことです。Javaでは標準ライブラリの Socket/ServerSocket クラスを使い、TCP/IPによる通信を数十行のコードで実装できます。

この記事では、Socket・ServerSocketクラスの役割の違いから、TCP接続が確立する3ウェイ・ハンドシェイクの流れまでを解説します。さらに、実際に動くサーバー/クライアントのサンプルコードも、初心者にもわかりやすく紹介します。

読み終えるころには、以下ができます。

  • Java Socket/ServerSocketクラスでTCP通信を実装できる
  • 3ウェイ・ハンドシェイクの流れを自分の言葉で説明できる
  • try-with-resourcesで安全にソケットを閉じられる
  • Java 21以降の仮想スレッドで複数クライアントに対応する方法が分かる

前提はJava 21以降(2026年9月時点の最新LTSはJava 25。最新のフィーチャーリリースはJava 27〔2026年9月15日リリース〕だが、長期運用にはLTS版のJava 25以降を推奨)とJDKの動作環境のみ。ネットワーク初心者でも追体験しやすいサンプルコード付きで解説します。

Javaソケット通信とは?

ソケット通信とは、ネットワーク上の2つのプログラムが、ソケットと呼ばれる通信口を使ってやりとりする仕組みです。

具体的には、以下のような構成です。

ポイント

  • サーバー:通信を待ち受ける側
  • クライアント:接続してデータを送る側

この2者が1対1でつながり、データをやり取りします。

ソケットとは?

「ソケット」とは、通信を行うための“窓口”のようなものです。

送る側(クライアント)と受け取る側(サーバー)が、ソケットという窓口を通じてデータをやり取りします。

Javaでは、クライアントは Socket クラスを使って接続し、サーバーは ServerSocket クラスを使って接続を待ち受けます。

ポート番号とは|12345は空いてる?

ポート番号は、1台のマシン内で複数の通信を区別するための識別子(0〜65535)です。Javaでサーバーを立てる際は、以下のレンジを意識してください。

  • 0〜1023(Well-Known Ports): HTTP(80)、HTTPS(443) などOS予約。Linuxなど多くのOSで管理者権限が必要なため避ける(macOS 10.14以降は不要な場合もあるが、環境依存なので避けるのが無難)
  • 1024〜49151(Registered Ports): アプリ用途。後述の 12345 もここに含まれる
  • 49152〜65535(Dynamic Ports): 一時利用。クライアント側が自動割り当て

既にそのポートが使われていると BindException: Address already in use が発生します。使用中のポートは lsof -i :12345(macOS/Linux)や netstat -ano | findstr :12345(Windows)で確認できます。

SimpleServer を起動した状態で lsof -P -i :12345(-P を付けるとポート番号がサービス名に変換されず数字のまま表示されます)を実行すると、次のような出力になり、SimpleServerプロセスがポートを掴んでいることが分かります(PIDなどの値は実行ごとに変わります)。

$ lsof -P -i :12345
COMMAND   PID     USER   FD   TYPE             DEVICE SIZE/OFF NODE NAME
java    69398 suzuki   5u  IPv6 0x8736310458a4dfb5      0t0  TCP *:12345 (LISTEN)

ソケット通信はどうやって実現してるの?

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ウェイ・ハンドシェイクで接続が確立するまで
1
CLIENT → SERVERSYN(接続要求)「これから通信したい」と伝える
2
SERVER → CLIENTSYN-ACK(接続許可+応答)「準備できている」と返事する
3
CLIENT → SERVERACK(確認応答)返事を確認して「始めます」と返す
ESTABLISHED / 接続確立通信開始

※ 3ウェイ・ハンドシェイクの仕様詳細は RFC 9293(TCP)(2022年発行)で定義されています。

私の体験談

実際にtcpdump -i lo0 port 12345(macOS)でSimpleServer/SimpleClientの通信をキャプチャすると、接続開始からSYN→SYN-ACK→ACKの3パケットがミリ秒単位で完了するのが確認できました。ローカルホスト通信のため実測でも1ms未満で3ウェイ・ハンドシェイクが終わり、教科書の図解が実際のパケットとして目に見えると理解が一気に深まります。

Javaソケット通信の使いどころ|どんな場面で使われる?

「基本はわかったけれど、実務でどう使うの?」という疑問に答えます。Javaのソケット通信は、以下のような場面で使われています。

  • チャットアプリ・リアルタイム通信:メッセージングアプリの土台となる技術
  • 社内システムのプロセス間通信:マイクロサービス間で独自プロトコルによる通信を行う場合
  • IoT・組み込み機器の制御:工場の製造ラインの機械制御など、独自プロトコルで低レイテンシが求められる場面
  • オンラインゲームのサーバー間通信:プレイヤーの操作をリアルタイムに同期する処理

「HTTPで十分では?」と思うかもしれませんが、HTTPもソケット通信の上に成り立つ一プロトコルにすぎません。独自の通信ルールが必要な場面や、HTTPのオーバーヘッドを避けたい場面では、SocketやServerSocketを直接使います。

トム
トム

結局、素のSocketで書くべき?それともライブラリに任せるべき?

ただし、いつでも生の Socket/ServerSocket を直接書けばよいわけではありません。学習目的や小規模な独自プロトコルの試作ならこの記事のようにSocket/ServerSocketを直接使うのが分かりやすい一方、本番運用の大規模チャットサーバーやマイクロサービス基盤のように大量接続・再接続・プロトコル拡張への対応が必要になると、コネクション管理やプロトコル実装を自前で書き続けるコストが割に合わなくなります。そのタイミングでNettyのようなNIOベースの高レベルライブラリへの移行を検討するのが現実的な判断基準です。

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クラスとして実装します。最初の基本形(SimpleServer/SimpleClient)はJava 8のAPIだけで書けるのでJava 8以降でも動かせますが、後半の仮想スレッド版はJava 21以降が必要です。2026年9月時点の最新LTSであるJava 25でも同様に動作します。

コードを読む前に、サーバーとクライアントがどの順番でメソッドを呼び、どこで処理が待たされるのかを押さえておきましょう。accept() と readLine() は相手の動きがあるまで止まる(ブロッキングする)点がポイントです。

SimpleServer と SimpleClient のメソッド呼び出し順
1
SERVERnew ServerSocket(12345) → accept()接続が来るまで accept() で止まる(ブロック)
2
CLIENT → SERVERnew Socket("localhost", 12345)3ウェイ・ハンドシェイク(SYN / SYN-ACK / ACK)はOSが自動で行う
3
SERVERaccept() が Socket を返す
4
CLIENT → SERVERout.println("hello")
5
SERVERin.readLine() で受信1行届くまで readLine() で止まる(ブロック)
6
SERVER → CLIENTout.println("サーバーからの応答: hello")
7
CLIENTin.readLine() で受信して表示
8
CLIENT → SERVERsocket.close()(切断)
9
SERVERreadLine() が null を返し、ループ終了clientSocket と serverSocket を close() する

図のとおり、TCPの接続確立(3ウェイ・ハンドシェイク)はOSが自動で行うため、Java側は accept() と new Socket() を呼ぶだけです。以降はストリームを使って1行ずつ送受信し、クライアントが切断すると readLine() が null を返して終了します。

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 が返ると(=クライアント切断)、ループ終了。

実務Tips: TCPソケットはデフォルトで「Nagleアルゴリズム」が有効になっており、送信済みのデータへのACK(確認応答)が戻るまで、小さいデータをバッファしてまとめて送信する仕組みが働きます。チャットのように1行ずつ低遅延で即時送信したい用途では、socket.setTcpNoDelay(true) を呼んでNagleアルゴリズムを無効化することで送信遅延を減らせます(Socket公式Javadoc)。

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() が自動実行されます。これで例外が起きても確実にリソースが解放されます。

クライアント側も同じ考え方で書き直すと、次のようになります。送受信の文字コードを StandardCharsets.UTF_8 で明示し、サーバー未起動時の ConnectException は分かりやすいメッセージで表示するようにしました(Java 21でコンパイル・実行を確認済み)。

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;

public class SafeClient {
    public static void main(String[] args) {
        try (Socket socket = new Socket("localhost", 12345);
             BufferedReader in = new BufferedReader(
                     new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
             PrintWriter out = new PrintWriter(
                     new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8), true);
             BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {

            System.out.println("メッセージを入力してください:");
            String userInput;
            while ((userInput = stdIn.readLine()) != null) {
                out.println(userInput);
                System.out.println("サーバーから: " + in.readLine());
            }
        } catch (ConnectException e) {
            System.err.println("サーバーに接続できません: " + e.getMessage());
        } catch (IOException e) {
            System.err.println("通信エラー: " + e.getMessage());
        }
    }
}

サーバーを起動せずに実行すると、スタックトレースではなく「サーバーに接続できません: Connection refused」と1行で表示されて終了します。ConnectException は IOException のサブクラスなので、catch は必ず ConnectException を先に書きます(逆順はコンパイルエラーになります)。

あわせて読む

複数クライアントに対応するには?(スレッド化)

現状のサンプルは 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 17以前でも動く従来の書き方です。Java 21以降(2026年9月時点の最新LTS Java 25を含む)であれば、Executors.newVirtualThreadPerTaskExecutor() で仮想スレッドを使うのが現在の第一候補です。完全なコードは後述の「Java 21以降は仮想スレッドで1接続1スレッドにする」で紹介します。

参考: OpenJDKの JEP 444: Virtual Threads(Java 21で正式機能化)では、1リクエスト(1接続)に1スレッドを割り当てる素直な書き方のまま、サーバーのスループットを高められることが仮想スレッドの目的として説明されています。大量同時接続が必要なチャット/ゲームサーバーでは検討の価値があります。

私の体験談

固定スレッドプールを10で試したとき、11人目のクライアントが接続待ちのままタイムアウトする不具合に遭遇しました。原因はnewFixedThreadPool(10)のキューが無制限のため、接続自体はaccept()できてもタスク実行が詰まっていたことでした。プールサイズは同時接続数の見込みより余裕を持たせるか、仮想スレッドに切り替えることで解消できます。

Java 21以降は仮想スレッドで1接続1スレッドにする

仮想スレッドはJVMが管理する軽量スレッドで、OSスレッドより生成コストがはるかに小さいため、プールで本数を絞らず「1接続に1本」割り当てても問題になりにくいのが特徴です。ここまでの内容(try-with-resources・文字コード指定・タイムアウト・ポート再利用)をすべて反映した、そのまま動くサーバーの完全版は以下のとおりです。

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class VirtualThreadServer {
    public static void main(String[] args) throws IOException {
        try (ServerSocket serverSocket = new ServerSocket();
             ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
            serverSocket.setReuseAddress(true); // 環境によらず再起動しやすくする(macOS/Linuxは既定で有効)
            serverSocket.bind(new InetSocketAddress(12345));
            System.out.println("サーバーがポート12345で待機中(仮想スレッド)...");

            while (true) {
                Socket client = serverSocket.accept();
                executor.submit(() -> handleClient(client)); // 1接続につき仮想スレッド1本
            }
        }
    }

    private static void handleClient(Socket client) {
        try (client;
             BufferedReader in = new BufferedReader(
                     new InputStreamReader(client.getInputStream(), StandardCharsets.UTF_8));
             PrintWriter out = new PrintWriter(
                     new OutputStreamWriter(client.getOutputStream(), StandardCharsets.UTF_8), true)) {
            client.setSoTimeout(30_000); // 30秒無通信なら SocketTimeoutException
            String line;
            while ((line = in.readLine()) != null) {
                System.out.println(Thread.currentThread() + " 受信: " + line);
                out.println("サーバーからの応答: " + line);
            }
        } catch (IOException e) {
            System.err.println("通信エラー: " + e.getMessage());
        }
    }
}

ポイント

  • newVirtualThreadPerTaskExecutor():タスク(接続)ごとに新しい仮想スレッドを作るExecutor。ExecutorService はJava 19以降 AutoCloseable なので try-with-resources で閉じられる
  • try (client; ...):Java 9以降は、実質的にfinalな変数をそのまま try-with-resources に書ける
  • setReuseAddress(true):bind() より前に呼ぶ必要があるため、引数なしの new ServerSocket() で生成してから bind する
  • setSoTimeout(30_000):無応答の相手で readLine() が永久に止まるのを防ぐ

このサーバーを起動し、前述の SafeClient を2つのターミナルから同時に接続した実行結果です(macOS/Amazon Corretto 21.0.5で確認)。受信ログの VirtualThread[#22] と VirtualThread[#20] のように、接続ごとに別の仮想スレッドが処理していることが分かります。

# [ターミナル1] サーバー側(Java 21で実行)
$ java VirtualThreadServer
サーバーがポート12345で待機中(仮想スレッド)...
VirtualThread[#22]/runnable@ForkJoinPool-1-worker-2 受信: hello
VirtualThread[#20]/runnable@ForkJoinPool-1-worker-1 受信: java socket
VirtualThread[#22]/runnable@ForkJoinPool-1-worker-2 受信: こんにちは

# [ターミナル2] クライアント1(別ターミナル3でもう1つ起動し「java socket」を送信)
$ java SafeClient
メッセージを入力してください:
hello
サーバーから: サーバーからの応答: hello
こんにちは
サーバーから: サーバーからの応答: こんにちは

バージョンによる注意点: Java 21〜23では、synchronized ブロックの中でI/O待ちが発生すると仮想スレッドがOSスレッド(キャリアスレッド)に固定される「ピン留め」が起き、並列性が落ちる制約がありました。この制約は JEP 491 によりJava 24で解消されています。Java 21で仮想スレッドを使う場合は、ソケットの読み書きを synchronized で囲まず、必要なら ReentrantLock を使うのが安全です。

Javaソケット通信でよくあるエラーと対処法

エラー原因対処
java.net.ConnectException: Connection refusedサーバー未起動/ポート違い/ファイアウォールサーバープロセスの起動確認、ポート番号の一致確認
java.net.BindException: Address already in use同じポートを別プロセスが使用中lsof -i :12345 で特定し kill するか、別ポートへ変更
java.net.SocketTimeoutExceptionsetSoTimeout() で設定したタイムアウト超過タイムアウト時間の見直し、ネットワーク状態の確認
java.net.SocketException: Connection reset相手が接続を異常終了(RST)した例外をキャッチして再接続処理を追加。正常な切断(FIN)は例外にならず readLine() が null を返すので、null 判定でループを抜ける

実務Tips: setSoTimeout() を設定しない場合、相手が応答を返さずに接続を切らないと read() が無期限にブロックし続け、サーバーやクライアントがフリーズしたように見える不具合につながります。本番運用では必ず妥当なタイムアウト値(数秒〜数十秒程度)を設定しておくのが安全です。

私の体験談

私が初めてソケット通信を書いた際、テスト後に Ctrl+C でサーバーを落とし再起動したら BindException: Address already in use が30秒〜2分ほど消えず混乱しました。TCPの TIME_WAIT 状態が原因の場合は、ServerSocket#setReuseAddress(true) をバインド前に呼ぶことで再利用しやすくなります(Javaの ServerSocket は macOS・Linux では SO_REUSEADDR が既定で有効なため、通常はこの1行がなくても再 bind できます。既定値は環境によって異なるので、明示しておくと安全です)。この1行を入れてから、再起動のたびに待たされることがなくなりました。

実行結果(macOS/Java 21)

手元で SimpleServer と SimpleClient を別ターミナルで起動した際の出力は以下のとおりです。

# [ターミナル1] サーバー側
$ java SimpleServer
サーバーがポート12345で待機中...
クライアントが接続しました。
受信: hello
受信: java socket

# [ターミナル2] クライアント側
$ java SimpleClient
メッセージを入力してください:
hello
サーバーから: サーバーからの応答: hello
java socket
サーバーから: サーバーからの応答: java socket
実行結果の裏で、クライアントとサーバーは何をしているか
段階
CLIENT / クライアント
SERVER / サーバー
1. 待ち受け
未起動
accept() で待機中12345番ポートで接続を待つ(ブロッキング)
2. 接続
new Socket("localhost", 12345)
accept() が戻る双方向通信用の Socket ができる
3. 送信
out.println("hello")入力した1行をサーバーへ送る
in.readLine() で受信
4. 応答
in.readLine() で待つ
out.println(応答)「受信: hello」を表示し、応答を付けて返す
5. 表示
「サーバーから: サーバーからの応答: hello」を表示1行ごとに 3〜5 の往復をくり返す
次の行を待つ
6. 切断
入力終了 → close()
readLine() が null
7. 終了
終了
ループを抜けて close()ストリームとソケットを閉じる
NOTEポートは12345、送信文字列は上の実行結果の「hello」。接続時の3ウェイ・ハンドシェイクは省略しています。

Javaソケット通信のポイント

実装してみると、以下の点が重要だとわかります。

ポイント

  • ServerSocketはクライアントからの接続を受け入れる役割
  • Socketは接続を行い、InputStream/OutputStreamを使って通信する
  • 通信は一方通行ではなく、双方向(送る・受け取る)が可能
  • 必ず入出力ストリームを閉じて、リソースを開放すること

通信の流れは「クライアントが入力 → サーバーへ送信 → サーバーが応答 → クライアントが表示」の繰り返しです。メソッドの呼び出し順は、実装パートの冒頭にある図で確認できます。

Socket/ServerSocketの主要メソッド

ここまで登場した主要メソッドを一覧にまとめました。実装時のリファレンスとして活用してください。

クラスメソッド役割
ServerSocketnew ServerSocket(port)指定ポートで接続を待ち受ける準備をする
ServerSocketaccept()クライアントからの接続を待機し、確立後にSocketを返す(ブロッキング)
ServerSocketsetReuseAddress(true)TIME_WAIT状態でも即座にポートを再利用できるようにする(macOS/Linuxでは既定で有効)
Socketnew Socket(host, port)指定ホスト・ポートへTCP接続を確立する(クライアント側)
SocketgetInputStream() / getOutputStream()受信・送信用のストリームを取得する
Socketclose()接続を閉じてリソースを解放する(try-with-resourcesで自動化推奨)

よくある質問(FAQ)

Q1. SocketとServerSocketの違いは?

A1. ServerSocketはサーバー側でクライアント接続を「待ち受ける」専用クラスで、accept() が返す Socket が実際の双方向通信チャネルです。クライアント側は直接 new Socket(host, port) で接続します。

Q2. Java NIOのSocketChannelとの違いは?

A2. この記事の Socket/ServerSocket はブロッキングI/Oで、1接続1スレッドが基本です。大量接続が必要なら java.nio の SocketChannel + Selector(ノンブロッキングI/O)を検討します。Java 21以降の仮想スレッドを使えば従来型でも大量接続が現実的になり、Java 24以降は synchronized によるピン留めの制約も解消されています。

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

A3. InputStreamReader/OutputStreamWriter の第2引数で明示的に StandardCharsets.UTF_8 を指定します。Java 18以降は JEP 400 でデフォルト文字セットがUTF-8に統一されましたが、Java 17以前の環境(Windowsでは従来MS932がデフォルト)や、別言語・別OSで動く相手との通信に備えて明示しておくのが安全です。なお、コンソール(標準入出力)の文字コードはJEP 400の対象外です。

Q4. SSL/TLSで暗号化通信したい

A4. SSLSocketFactory.getDefault().createSocket(host, port) で SSLSocket を生成できます。サーバー側は SSLServerSocket を使い、keystore を設定します。本番運用では必須です。

あわせて読む

Q5. bind()やServerSocket生成でエラーになるときの対処は?

A5. ほとんどの場合、指定したポートを既に別プロセスが使用中で BindException: Address already in use が発生しています。lsof -i :<ポート番号>(macOS/Linux)で該当プロセスを特定してください。Javaの ServerSocket は macOS・Linux では SO_REUSEADDR が既定で有効なため、通常はサーバー側のTIME_WAITが残っていても再 bind できます(Corretto 21 / macOS で確認)。SO_REUSEADDR が無効な環境で TIME_WAIT が原因になる場合に備えるなら、バインド前に ServerSocket#setReuseAddress(true) を明示します。

まとめ:Javaでソケット通信は難しくない

JavaでTCP/IPソケット通信を実装する手順を、基本概念からサンプルコード・よくあるエラーまで見てきました。

TCPプロトコルの仕組みや3ウェイ・ハンドシェイクの流れ、JavaのSocket・ServerSocketクラスの使い方を一貫して学ぶことで、ネットワーク通信の基礎が自然に身につきます。

【要点まとめ】

  • ソケット通信とはネットワーク越しにプログラム同士が通信する仕組み
  • サーバーはServerSocketで待ち受け、クライアントはSocketで接続する
  • Javaのソケット通信はTCPプロトコルを利用している
  • TCPは順序通り・確実にデータを届ける特徴がある
  • 接続開始時は3ウェイ・ハンドシェイクが行われる
  • サーバー側はaccept()でクライアントの接続を待つ
  • クライアント側はユーザー入力を送信し、サーバーからの応答を受信する
  • 通信は双方向であり、入力と出力のストリームをそれぞれ使う
  • 通信終了時はtry-with-resourcesでソケットとストリームを確実にcloseすることが重要
  • 複数クライアント対応はJava 21以降なら仮想スレッド(newVirtualThreadPerTaskExecutor)が第一候補
  • サンプルコードを理解することで自作アプリ開発の基礎が築ける

Javaでのソケット通信は、一見とっつきにくく感じるかもしれませんが、仕組みさえ理解してしまえばシンプルです。

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

REVISIONS / 改訂履歴
REV.DATE内容
62026-10-15図解を描き直し(図面のデザインにそろえた)
52026-10-14まとめの言い回しを見直し。雰囲気だけの挿絵を外した
42026-10-04記載内容の事実確認と修正
32026-08-222026年8月時点の情報で全体を見直し
… ほか1回の改訂
12025-04-16初版
NEXT READ本棚から1本

AIで開発を組み直すなら、まずこの5冊から

入門・実践・設計まで、Claude Codeの本をレベル別に5冊選んで比べました。

→ おすすめ5冊を見る

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

トム

SESで約8年、環境を変えられないもどかしさの中にいました。32歳で自社開発企業に転職し、今はClaude Codeと自動化で、仕事も時間も組み直している途中です。
・Javaバックエンドエンジニア(Java歴12年以上)
・資格:基本情報技術者/応用情報技術者/Java Silver/Python3エンジニア認定基礎
詳細なプロフィール

𝕏

Java入門