最終更新日:2022/04/11 原本2019-09-16

[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を作成しましょう。

Copy!
 [Source view] Server.java
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を打ったら接続が終了します。


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


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

Copy!
 [Source view] Client.java
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)をする方法に関する説明でした。


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