[Java] 27. ネットワーク通信(Socket)をする方法


Study / Java    作成日付 : 2019/09/16 23:42:46   修正日付 : 2021/03/09 19:09:21

こんにちは。明月です。


この投稿はJavaでネットワーク通信(Socket)をする方法に関する説明です。


プログラムでソケットと言えばプログラムとプログラムまたはPCとPC間に通信をするという意味です。

簡単に思えば通信する時に伝送するパケット(データ)がパソコンのLANカードによってランケーブルに伝送します。ランケーブルに伝送したデータはDNSとルータなどを通って到達しようとPCのLANカードによって最終に目標したプログラムでパケット(データ)を読み込みます。端末と端末の間にデータを通信します。

この時、我々は各端末間にデータ変換や装置間のプロトコール、規約などに関して実装してないです。この通信規約に関してはすべてOS側で設定して(OSI7階層)、我々はその上で差し込んで使うという意味でSocket通信という言います。

link - OSI参照モデル


Socket通信規約は処理プロシージャが決まっています。

先に、通信接続を待つ側をサーバという言います。サーバはPortを開いてクライアントの接続を待ちます。そして接続する側をクライアントという言います。クライアントがサーバのIPとPortに接続したら通信が開始します。

サーバとクライアント間の通信はSend、Receiveの形式でデータを送信、受信します。そして通信が終わればcloseで接続を切ります。



そのSocketの概念でJavaでソケット(Socket)通信を作成みましょう。

先にはサーバを作成してWindowのTelentプログラムを利用して接続を確認します。確認できたらその仕様に合わせてClientを作成しましょう。

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// サーバクラス
public class Server {
  // バッファーサイズ設定
  private final static int BUFFER_SIZE = 1024;
  // 実行関数
  public static void main(String[] args) {
    // サーバインスタンス生成(プログラムが終了する時に自動にcloseが呼び出す。)
    try (ServerSocket server = new ServerSocket()) {
      // 9999ポートでサーバを待つ。
      InetSocketAddress ipep = new InetSocketAddress(9999);
      // サーバインスタンスにソケット情報をbind
      server.bind(ipep);
      // コンソール出力
      System.out.println("Initialize complate");
      // クライアントからメッセージを待つスレッドプール
      ExecutorService receiver = Executors.newCachedThreadPool();
      // クライアントリスト
      List<Socket> list = new ArrayList<>();
      // サーバは無限待機
      while (true) {
        try {
          // クライアントから接続待機
          Socket client = server.accept();
          // クライアントリストに追加
          list.add(client);
          // 接続情報をコンソールに出力
          System.out.println("Client connected IP address =" + client.getRemoteSocketAddress().toString());
          // クライアントスレッドプールを開始
          receiver.execute(() -> {
            // clientが終了すればソケットをcloseする
            // OutputStreamとInputStreamを受け取る。
            try (Socket thisClient = client; 
                  OutputStream send = client.getOutputStream(); 
                  InputStream recv = client.getInputStream();) {
              // メッセージを作成
              String msg = "Welcome server!\r\n>";
              // byte変換
              byte[] b = msg.getBytes();
              // クライアントに伝送
              send.write(b);
              // バッファー
              StringBuffer sb = new StringBuffer();
              // メッセージを待機ループ
              while (true) {
                // バッファー生成
                b = new byte[BUFFER_SIZE];
                // メッセージを受け取る。
                recv.read(b, 0, b.length);
                // byteをStringに変換
                msg = new String(b);
                // バッファーにメッセージ追加
                sb.append(msg.replace("\0", ""));
                // メッセージが改行の場合(クライアントからエンターを打った場合)
                if (sb.length() > 2 && sb.charAt(sb.length() - 2) == '\r' && sb.charAt(sb.length() - 1) == '\n') {
                  // メッセージをStringに変換
                  msg = sb.toString();
                  // バッファーをクリア
                  sb.setLength(0);
                  // メッセージをコンソールに出力
                  System.out.println(msg);
                  // exitメッセージの場合、メッセージループを終了する。
                  if ("exit\r\n".equals(msg)) {
                    break;
                  }
                  // echoメッセージ作成
                  msg = "echo : " + msg + ">";
                  // byteに変換
                  b = msg.getBytes();
                  // クライアントに伝送
                  send.write(b);
                }
              }
            } catch (Throwable e) {
              // エラー発生する時、コンソール出力
              e.printStackTrace();
            } finally {
              // 接続が終了すれば接続情報をコンソールに出力
              System.out.println("Client disconnected IP address =" + client.getRemoteSocketAddress().toString());
            }
          });
        } catch (Throwable e) {
          // エラー発生する時、コンソール出力
          e.printStackTrace();
        }
      }
    } catch (Throwable e) {
      // エラー発生する時、コンソール出力
      e.printStackTrace();
    }
  }
}

上のacceptはwhile(true)の無限ループに入れてクライアントを待機します。

クライアント接続すればスレッドプールにSocketを渡してクライアントからメッセージを待機します。

ここからSocketのStreamを受け取ってwrite、readを使うことになりますが、IOと同じロジックです。

link - [Java] 26. ファイル(IO)を扱う方法(ファイル作成、ファイル修正、アクセス日付変更とIOをclose(リソース返却)する理由、Closableインタフェース)


起動すればコンソールにInitialize completeメッセージがコンソールに出力してListenの状態になります。


Windowコンソールからtelnetに接続してメッセージを送信しましょう。


telnetで127.0.0.1 9999に接続してhello worldを打ったらechoメッセージが受信することを確認できます。また、exitを打ったら接続が終了します。


サーバを確認すればクライアントが接続してメッセージを受け取って終了することまで確認できます。


サーバは完了しました。このサーバの仕様でクライアントを作成しましょう。

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// クライアントクラス
public class Client {
  // バッファーサイズ設定
  private final static int BUFFER_SIZE = 1024;
  // 実行関数
  public static void main(String[] args) {
    // サーバインスタンス生成(プログラムが終了する時に自動にcloseが呼び出す。)
    try (Socket client = new Socket()) {
      // ロカール:9999ポートのサーバに接続する。
      InetSocketAddress ipep = new InetSocketAddress("127.0.0.1", 9999);
      // 接続
      client.connect(ipep);
      // clientが終了すればソケットをcloseする
      // OutputStreamとInputStreamを受け取る。
      try (OutputStream send = client.getOutputStream(); 
           InputStream recv = client.getInputStream();) {
        // コンソールに出力
        System.out.println("Client connected IP address =" + client.getRemoteSocketAddress().toString());
        // サーバからメッセージを待つスレッドプール
        ExecutorService receiver = Executors.newSingleThreadExecutor();
        receiver.execute(() -> {
          try {
            // メッセージの無限待機
            while (true) {
              // バッファー生成
              byte[] b = new byte[BUFFER_SIZE];
              // メッセージを受け取る。
              recv.read(b, 0, b.length);
              // コンソールに出力
              System.out.println(new String(b));
            }
          } catch (Throwable e) {
            // エラー発生する時、コンソール出力
            e.printStackTrace();
          }
        });
        // コンソールからメッセージを受け取る。
        try (Scanner sc = new Scanner(System.in)) {
          // コンソールメッセージの無限待機
          while (true) {
            // メッセージを受け取る。
            String msg = sc.next() + "\r\n";
            // byte変換
            byte[] b = msg.getBytes();
            // サーバにメッセージを送信
            send.write(b);
            // exitの場合に接続終了
            if ("exit\r\n".equals(msg)) {
              break;
            }
          }
        }
      }
    } catch (Throwable e) {
      // エラー発生する時、コンソール出力
      e.printStackTrace();
    }
  }
}

eclipseからは同時に二つのmainを実行することができないので、サーバはjarファイルでexportしてコンソールから実行します。


これからeclipseからクライアントを実行して接続しましょう。


接続が正常になりました。

メッセージを送ったらechoメッセージも正常に受信します。exitをすればサーバと接続が切れました。その後に正常に終了します。エラーExceptionが発生しましたが、正常終了で発生したものです。


サーバからも正常接続、メッセージ、終了まで確認できます。


サーバとクライアントソースをみれば差異が多くないです。サーバはServerSocketインスタンスを生成して接続すればSocketインスタンスを受け取ります。

クライアントからSocketインスタンスを生成して接続します。つまり、Socketから送信、受信はSocketクラスから行います。


ここまでJavaでネットワーク通信(Socket)をする方法に関する説明でした。


ご不明なところや間違いところがあればコメントしてください。

最新投稿