よく使われるルーチン
ネットワーク プログラミングでは、サービスを提供する側をサーバーと呼び、サービスに接続する側をクライアントと呼びます。開発プロセスでは、Server または ServerSocket という単語を持つクラスがサーバーによって使用されます.Socket という単語を持つクラスしかない場合は、特定のネットワークの読み取りと書き込みを担当します。
サーバーにとって、ServerSocket は単なる場所であり、特定の読み取りおよび書き込み操作を担当するのではなく、クライアントに接続した後にクライアントと通信するための新しいソケットを作成することのみを担当します。クライアントとの特定の通信は、依然として Socket クラスです。これは、ネットワーク プログラミングのすべてのモードに共通です。
ネットワーク プログラミングでは、接続 (クライアントがサーバーに接続し、サーバーが接続を待機して受信する)、ネットワーク データの読み取り、およびネットワーク データの書き込みの 3 つだけに注意する必要があります。サーバーは IP とリッスン ポートを提供します. クライアントは接続操作を通じてサーバーによって監視されているアドレスへの接続要求を開始します. 接続が正常に確立された後, 2 つの当事者はソケットを介して通信できます.
バイオ
通常、BIO 通信モデルを使用するサーバーには、クライアントの接続を監視する独立した Acceptor スレッドがあり、クライアントの接続要求を受け取ると、クライアントごとに新しいスレッドを作成してリンクを処理し、処理が完了した後に渡されます。出力 ストリームはクライアントに応答を返し、スレッドは破棄されます。つまり、典型的な要求応答モデルであると同時に、データの読み取りと書き込みもスレッドでブロックされ、その完了を待つ必要があります。
このモデルの最大の問題は、柔軟なスケーラビリティの欠如です. 同時クライアント アクセス数が増加すると、サーバー上のスレッド数と同時クライアント アクセス数は 1:1 に比例します. Java のスレッドも比較的価値があります.システム リソース. スレッドの数が急速に拡大すると、システムのパフォーマンスが急激に低下します. アクセス数が増加し続けると、システムは最終的に機能しなくなります.
この 1 接続 1 スレッド モデルを改善するために、スレッド プールを使用してこれらのスレッドを管理し、1 つ以上のスレッドが N 個のクライアントを処理するモデルを実装できます (ただし、最下層では引き続き同期ブロッキング I/O を使用します)。 、通常は「疑似非同期 I/O モデル」と呼ばれます。
コード
クライアント:
public class Client {
public static void main(String[] args) throws IOException {
//客户端启动必备
Socket socket = null;
//实例化与服务端通信的输入输出流
ObjectOutputStream output = null;
ObjectInputStream input = null;
//服务器的通信地址
InetSocketAddress addr
= new InetSocketAddress("127.0.0.1",10001);
try{
socket = new Socket();
socket.connect(addr);//连接服务器
output = new ObjectOutputStream(socket.getOutputStream());
input = new ObjectInputStream(socket.getInputStream());
/*向服务器输出请求*/
output.writeUTF("ceshi");
output.flush();
//接收服务器的输出
System.out.println(input.readUTF());
}finally{
if (socket!=null) socket.close();
if (output!=null) output.close();
if (input!=null) input.close();
}
}
}
サーバ:
public class Server {
public static void main(String[] args) throws IOException {
//服务端启动必备
ServerSocket serverSocket = new ServerSocket();
//表示服务端在哪个端口上监听
serverSocket.bind(new InetSocketAddress(10001));
System.out.println("Start Server ....");
try{
while(true){
//当有客户端连接时,会执行完一次循环,到下一次循环时,如果没有客户端连接,则一直阻塞在serverSocket.accept()方法那里,不往下执行,直到下一个客户端连接过来
new Thread(new ServerTask(serverSocket.accept())).start();
}
}finally {
serverSocket.close();
}
}
//每个和客户端的通信都会打包成一个任务,交个一个线程来执行
private static class ServerTask implements Runnable{
private Socket socket = null;
public ServerTask(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//实例化与客户端通信的输入输出流
try(ObjectInputStream inputStream =
new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(socket.getOutputStream())){
//接收客户端的输出,也就是服务器的输入
String userName = inputStream.readUTF();
System.out.println(Thread.currentThread().getName()+"Accept client message:"+userName);
//服务器的输出,也就是客户端的输入
outputStream.writeUTF(Thread.currentThread().getName()+"Hello,"+userName);
outputStream.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
スレッド プールはサーバーを実装します。
public class ServerPool {
private static ExecutorService executorService
= Executors.newFixedThreadPool(
Runtime.getRuntime().availableProcessors());
public static void main(String[] args) throws IOException {
//服务端启动必备
ServerSocket serverSocket = new ServerSocket();
//表示服务端在哪个端口上监听
serverSocket.bind(new InetSocketAddress(10001));
System.out.println("Start Server ....");
try{
while(true){
executorService.execute(new ServerTask(serverSocket.accept()));
}
}finally {
serverSocket.close();
}
}
//每个和客户端的通信都会打包成一个任务,交个一个线程来执行
private static class ServerTask implements Runnable{
private Socket socket = null;
public ServerTask(Socket socket){
this.socket = socket;
}
@Override
public void run() {
//实例化与客户端通信的输入输出流
try(ObjectInputStream inputStream =
new ObjectInputStream(socket.getInputStream());
ObjectOutputStream outputStream =
new ObjectOutputStream(socket.getOutputStream())){
//接收客户端的输出,也就是服务器的输入
String userName = inputStream.readUTF();
System.out.println("Accept client message:"+userName);
//服务器的输出,也就是客户端的输入
outputStream.writeUTF("Hello,"+userName);
outputStream.flush();
}catch(Exception e){
e.printStackTrace();
}finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
ブロッキングの理解
bio のブロックは、主に次の 2 つの場所に反映されます。
①サーバーの起動準備が整っている場合、メイン スレッドはクライアントの接続を待機しており、待機プロセス中にメイン スレッドがブロックされています。つまり、socketServer.accept() メソッドは、クライアント接続がある場合にのみコードを実行します。それ以外の場合は、常にここでブロックされます。
②コネクション確立後、サブスレッドがコネクションを提供するが、サブスレッドが提供するサービスはソケット通信に過ぎず、サブスレッドはソケット情報を読み取る前に常に待機し、閉塞状態にある。
スレッド プールによって実装される疑似非同期 IO は、クライアントがサーバーに接続するときに作成される新しいスレッドの数を減らすだけであり、ブロックには役立ちません。