IOモデルは、ネットワークデータ送信の過程でデータを送受信するために使用されるチャネルを指します。一般的なものはBIO、NIO、AIO(NIO2.0)であり、次にこれらを詳しく紹介します。
同期/非同期/ブロッキング/非ブロッキングとはどういう意味ですか?
-
同期/非同期と
は、メソッドを呼び出すことを意味します。メソッドが同期の場合は、このメソッドの実行が終了するのを待ってから後続の操作を実行します。
非同期の場合は、すぐに返されますが、これは当てはまりません。実際の結果は、メッセージメカニズムまたはコールバックメカニズムを介して通知されます。 -
/ノンブロッキングブロッキング
あなたは洗濯機の情報を取得するメソッドを呼び出したときにブロックすることを意味し何洗濯機は、現時点では存在しない場合は、洗濯機の情報を照会することができるようになるまで、この方法はブロックする結果が返されます。。。
非ブロッキングとは、洗濯機の情報を取得するメソッドを呼び出すときに、その時点で情報が見つからない場合、そこで永久にブロックされることはありません。この時点で他のことを行うことはできますが、結果があるかどうかを確認します。時々、取得をブロックしますが、これは他のことをするのに影響しません。
BIO(同期ブロッキング)
私たちはよくBIOを使用します。プログラミングの基本javaSEを学ぶとき、誰もがソケット通信を学ぶ必要があります。ここでは同期ブロッキングが使用されます。
まず、BIOモデルを見てみましょう。
BIOモデルでは、接続は処理スレッドに対応します。サーバーが処理に単一のスレッドを使用する場合、後続の接続は常にブロックされます。
- 短所:
- コード内の読み取り操作はブロック操作です。サーバーが接続後にデータを送信しない場合、サーバーは常に現在のスレッドをブロックし、リソースを浪費します。
- 接続数が多い場合は、作成されるスレッド数が増えるため、サーバーへの負荷が大きくなり、その後のスレッドプール処理方法の最適化では、この問題はほとんど解決されません。
- アプリケーションシナリオ
BIOは、接続数が少ない固定アーキテクチャに適しています。このモードでは、より多くのサーバーリソースが必要ですが、プログラムの複雑さは比較的低くなります。
コード例
// 客户端
package com.example.netty.bio;
import java.io.IOException;
import java.net.Socket;
public class SocketClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("127.0.0.1",9000);
socket.getOutputStream().write("我是客户端".getBytes());
socket.getOutputStream().flush();
System.out.println("向服务端发送数据结束");
byte[] bytes = new byte[1024];
try {
int read = socket.getInputStream().read(bytes);
System.out.println("服务端发送过来的数据为:"+new String(bytes,0,read));
} catch (IOException e) {
e.printStackTrace();
}finally {
}
}
}
//服务端
package com.example.netty.bio;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServer{
public static void main(String[] args) throws IOException {
ServerSocket serverSocket=new ServerSocket(9000);
while (true){
System.out.println("等待连接");
// 阻塞等待
Socket client = serverSocket.accept();
System.out.println("有客户端连接了");
handleRead(client);
}
}
/**
*
* @param client
*/
private static void handleRead(Socket client) {
new Thread(new Runnable() {
@Override
public void run() {
byte[] bytes = new byte[1024];
try {
int read = client.getInputStream().read(bytes);
System.out.println("客户端发送过来的数据为:"+new String(bytes,0,read));
// Thread.sleep(Integer.MAX_VALUE);
client.getOutputStream().write("你好,我收到你的数据了".getBytes());
client.getOutputStream().flush();
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
}
}
}).start();
}
}
NIO(同期ノンブロッキング)
NIOはBIOに基づいてアップグレードされ、ブロッキングが非ブロッキングに置き換えられました。モードのみが変更されましたが、コードの複雑さは大幅に増加しました。
NIOモデルでは、サーバーはスレッドを開いて複数の接続を処理できます。これは非ブロッキングです。クライアントから送信されたデータはマルチプレクサセレクターに登録されます。セレクター(セレクターのselectメソッドがブロッキングしている場合)のポーリングがある場合読み取り、書き込み、または接続要求は、処理のためにバックエンドプログラムに転送されます。データがない場合、ビジネスプログラムは待機をブロックする必要はありません。
NIOには、チャネル(チャネル)、バッファー(バッファー領域)、セレクター(セレクター)の3つの主要コンポーネントがあります。
- チャネルは
ストリームに似ていますが、双方向のストリームであり、サーバーとクライアントを接続するチャネルです。クライアントとサーバーの両方がチャネルを使用してデータを読み書きできます。 - バッファ
は、データを格納するために使用されるバッファであり、チャネルを使用してクライアントとサーバー間でデータを転送します。 - セレクター
は1つ以上のスレッドに対応し、クライアント接続はセレクターに登録され、セレクターはバックエンドハンドラーを呼び出します
NIO構造モデル
NIOのノンブロッキングはどこにありますか?
モデル図を見ると、クライアントのすべての接続チャネルがセレクタに登録され、selectがポーリングを通じてこれらのチャネルのステータスを取得することを誰もが知っているかもしれません。これらのステータスには、accpet(接続要求)とREAD読み取り要求が含まれます。
ポーリングプロセス中に接続要求ステータスがすでに存在することが判明した場合は、サーバーに接続し、このチャネルをバックエンドプログラムに直接渡して接続操作を処理するクライアントがすでに存在することを意味します。 BIOモデルにある場合は、受け入れ時に常にブロックされ、接続要求が発生するまで解放されず、後続のコードが引き続き実行されます。
ポーリングプロセス中に読み取り要求ステータスが見つかった場合は、クライアントがすでにサーバーにデータを送信しており、サーバーがチャネルをバックエンドプログラムに直接渡して、読み取り操作を処理できることを意味します。 BIOモデルの場合、接続要求が発生するまで読み取りはブロックされ、後続のコードは引き続き実行されます。
上記は次のように要約できます。NIOモデルでは、サーバーが読み取り操作を実行する場合、読み取り可能なデータがすでに存在することを意味します。accpet操作が実行される場合、クライアントはこの時点で開始してサービスを提供する必要があることを意味します。エンドの接続により、この操作の前提は、セレクターが読み取り可能で接続可能なチャネル状態をポーリングすることであることが保証されます。
セレクターが複数のチャネルの読み取りおよびアクセスステータスをポーリングする場合、NIOはそれをどのように処理しますか?
-
シングルスレッドモードでシングルスレッドの場合、処理方法はポーリング後に取得された変更されたチャネルの順序に基づきます。はい、同期的に処理されます。つまり、このチャネルの操作のみを処理できます。続行するには次のチャネルのリクエストを処理するセレクタコードでは、変更されたすべてのチャネルをトラバースすることで処理されます。コードを読むと、1つのスレッドに対応する1つのセレクタのこのモードは、実際にはredisのシングルスレッドIOであることがわかります。モデル。 -
マルチスレッド
がマルチスレッドモードの場合、セレクターが各チャネルをトラバースすると、チャネルの操作が処理のためにスレッドに渡され、通常は処理のためにスレッドにスローされます。このとき、実行の順序は次のようになります。 cpuスケジュールの状況はあなた次第です。
次に、コードを組み合わせて、NIO全体の動作メカニズムを確認します。
コード例
- サーバーコード
package com.example.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NioServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
System.out.println("等待事件发生");
// 这里还是阻塞等待,
int select = selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handleChannel(selectionKey);
}
}
}
private static void handleChannel(SelectionKey selectionKey) {
if (selectionKey.isAcceptable()) {
System.out.println("有客户端发生了连接");
ServerSocketChannel channel = (ServerSocketChannel) selectionKey.channel();
try {
SocketChannel client = channel.accept();
client.configureBlocking(false);
// 连接之后立即注册读操作,客户端发送数据过来才能监听到
client.register(selectionKey.selector(), SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
} else if (selectionKey.isReadable()) {
System.out.println("收到客户端发送数据的请求");
SocketChannel channel = (SocketChannel) selectionKey.channel();
// 如果这里你不读取数据,读事件会一直触发,这是有NIO属于水平触发决定的,
ByteBuffer allocate = ByteBuffer.allocate(1024);
try {
int read = channel.read(allocate);
if (read != -1) {
System.out.println("客户端发送的数据:" + new String(allocate.array(), 0, read));
}
channel.write(ByteBuffer.wrap("你好,我是服务端".getBytes()));
// 处理完读操作之后,需要重新注册下读操作,
// 如果下面一行被放开,将会一直会有可写操作触发,因为网络中99.999%的情况下都是可写的,一般不监听
// selectionKey.interestOps(SelectionKey.OP_WRITE | SelectionKey.OP_READ);
selectionKey.interestOps(SelectionKey.OP_READ);
} catch (IOException e) {
e.printStackTrace();
}
} else if (selectionKey.isWritable()) {
System.out.println("触发写事件");
}
}
}
- サーバー側のアーキテクチャ図
-
サーバーコードの説明
- ServerSocketChannelとセレクターを作成し、ServerSocketChannelをセレクターに登録します
- セレクターはselect()メソッドを介してチャネルイベントを監視します。クライアントが接続すると、セレクターは接続イベントを監視し、ServerSocketChannel登録にバインドされたselectionKeyを取得します。
- selectionKeyは、channel()メソッドを介してバインドされたServerSocketChannelを取得できます
- ServerSocketChannelは、accept()メソッドを介してSocketChannelを取得します
- SocketChannelをセレクターに登録し、読み取りイベントに注意してください
- 登録後にSelectionKeyを返します。これは、SocketChannelに関連付けられます。
- セレクターは引き続きselect()メソッドを介してイベントを監視します。クライアントがサーバーにデータを送信すると、セレクターは読み取りイベントをリッスンし、SocketChannel登録にバインドされたselectionKeyを取得します。
- selectionKeyは、channel()メソッドを介してバインドされたsocketChannelを取得できます
- socketChannelのデータを読み取ります
- socketChannelを使用してサーバーデータをクライアントに書き戻します
コードコメントで述べた水平トリガーに気付いたかもしれませんが、これについて説明します。水平トリガーは多重化のモードであり、これに対応するエッジトリガーがあります。
-
水平トリガー
チャネルでデータの変更が検出された後に通知がトリガーされた場合、イベントハンドラーが通知の受信後にバッファー内のすべてのデータを完全に読み取らないか、まったく読み取らない場合、水平トリガーモードでは、これは常にトリガーされますバッファの内容が読み取られるまで、NIOでの選択とポーリングはこのモードに属することに注意してください
-
エッジトリガー
状況は上記と同じですが、システムが一度通知した後、チャネル内のデータが再び変更された場合にのみ通知が再度発生します。Epollは、水平トリガーまたはエッジトリガーのいずれかを使用できます。
-
クライアントコード
package com.example.netty.nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
public class NioClient {
private Selector selector;
public static void main(String[] args) throws IOException {
NioClient client = new NioClient();
client.initClient("127.0.0.1", 9000);
client.connect();
}
private void connect() throws IOException {
while (true) {
// 阻塞等待
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
handler(selectionKey);
}
}
}
private void handler(SelectionKey selectionKey) throws IOException {
// 收到服务端发送的数据
if (selectionKey.isReadable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
int len = channel.read(buffer);
if (len != -1) {
System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
}
// 连接事件发生
} else if (selectionKey.isConnectable()) {
SocketChannel channel = (SocketChannel) selectionKey.channel();
// 一般发起了连接后,会立即返回,需要使用isConnectionPending判断是否完成连接,如果正在连接,则调用finishConnect,如果不能连接则会抛出异常
if (channel.isConnectionPending()) {
channel.finishConnect();
}
channel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.wrap("你好,我是客户端".getBytes());
channel.write(buffer);
selectionKey.interestOps(SelectionKey.OP_READ);
}
}
private void initClient(String s, int i) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.configureBlocking(false);
selector = Selector.open();
socketChannel.connect(new InetSocketAddress(s, i));
socketChannel.register(selector, SelectionKey.OP_CONNECT);
}
}
共通の基盤となる実装APIの多重化
selelctと比較すると、pollには最大接続制限がありません。epoll
は前の2つとは異なるメカニズムであり、イベント通知に基づいて呼び出し元に通知します。
AIO(非同期ノンブロッキング)
非同期で非ブロッキング。オペレーティングシステムが完了した後、サーバープログラムがコールバックされ、処理するスレッドを開始するようサーバープログラムに通知します。これは通常、接続数が多く、接続時間が長い
アプリケーションシナリオに適しています。 )アーキテクチャ、JDK7がサポートを開始
- AIOコード例:
// 服务端代码
package com.example.netty.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
public class AioServer {
public static void main(String[] args) throws IOException, InterruptedException {
final AsynchronousServerSocketChannel serverChannel =
AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(9000));
serverChannel.accept(null,new CompletionHandler<AsynchronousSocketChannel, Object>() {
@Override
public void completed(AsynchronousSocketChannel socketChannel, Object attachment) {
serverChannel.accept(attachment, this);
try {
System.out.println(socketChannel.getRemoteAddress());
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
buffer.flip();
System.out.println(new String(buffer.array(), 0, result));
socketChannel.write(ByteBuffer.wrap("HelloAioClient".getBytes()));
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void failed(Throwable exc, Object attachment) {
}
});
Thread.sleep(Integer.MAX_VALUE);
}
}
// 客户端代码
package com.example.netty.aio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.concurrent.ExecutionException;
public class AioClient {
public static void main(String[] args) throws ExecutionException, InterruptedException, IOException {
AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1", 9000)).get();
socketChannel.write(ByteBuffer.wrap("HelloServer".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(512);
Integer len = socketChannel.read(buffer).get();
if (len!=-1) {
System.out.println("客户端收到信息:" + new String(buffer.array(), 0, len));
}
}
}
概要:非同期非ブロッキングモードのコードは非常に単純であることがわかります。すべての操作はコールバックメカニズムによってトリガーされます。コールバックメソッドで独自のロジックを処理するだけで済みます。実際、AIOはNIOに基づいてカプセル化されています。 。後で説明するように、nettyもNIOに基づいてカプセル化されます。
BIO、NIO、AIO区别
WeChatで検索[AIコーダー]ハンサムな私をフォローして[乾物を受け取る]と返信すると、Javaの基本、Javaの同時実行、マイクロサービス、ミドルウェアなど、インタビュー資料やアーキテクトの必読の本がたくさんあります。など。より多くの情報があなたを待っています。