NIO(JDK1.4)モデルは、同期ノンブロッキングIOであり、チャネル、バッファー、セレクターの3つのコア部分があります。従来のIOはバイトストリームと文字ストリームに基づいて動作しますが、NIOはチャネルとバッファ(バッファ)で動作します。データは常にチャネルからバッファに読み取られるか、バッファからチャネルに書き込まれます。セレクター(マルチプレクサー)は、複数のチャネルのイベント(接続のオープン、データの到着など)を監視するために使用されます。したがって、1つのスレッドで複数のデータチャネルを監視できます。
NIOと従来のIO(以下IOと呼びます)の最初の最大の違いは、IOがストリーム指向であるのに対し、NIOはバッファー指向であるということです。Java IOストリーム指向とは、ストリームから1つ以上のバイトが読み取られるたびに、すべてのバイトが読み取られるまで、それらはどこにもキャッシュされないことを意味します。さらに、ストリーム内のデータを前後に移動することはできません。ストリームから読み取ったデータを前後に移動する必要がある場合は、最初にデータをバッファーにキャッシュする必要があります。
IOのさまざまなストリームがブロックされます。これは、スレッドがread()またはwrite()を呼び出すと、一部のデータが読み取られるか、データが完全に書き込まれるまで、スレッドがブロックされることを意味します。この期間中、スレッドは何もできなくなります。NIOの非ブロッキングモードでは、スレッドはチャネルからデータを読み取る要求を送信できますが、取得できるのは現在利用可能なデータのみです。現在利用可能なデータがない場合は、何も取得されません。スレッドをブロックしたままにする代わりに、スレッドはデータが読み取り可能になるまで他のことを続けることができます。ノンブロッキング書き込みについても同じことが言えます。スレッドは特定のチャネルにデータを書き込むように要求しますが、データが完全に書き込まれるのを待つ必要はありません。このスレッドは同時に他のことを行うことができます。スレッドは通常、非ブロッキングIOのアイドル時間を使用して他のチャネルでIO操作を実行するため、1つのスレッドで複数の入力および出力チャネル(チャネル)を管理できるようになりました。
NIOの利点:
- クライアントとサーバー間の一種の通信は、チャネルによってセレクタに登録された状態を介して実現されます。
- チャネル内のデータは、非ブロッキングの読み取り方法であるバッファを介して読み取られます。
- セレクタマルチプレクサシングルスレッドモデル、スレッドリソースのオーバーヘッドは比較的小さいです。
サーバーコード:NIOServer.java
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;
import java.util.Set;
public class NIOServer {
public static void main(String[] args) throws Exception{
//创建一个serverSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//创建Selector
Selector selector = Selector.open();
//绑定端口6666,在服务端监听
serverSocketChannel.socket().bind(new InetSocketAddress(6666));
//设置非阻塞模式
serverSocketChannel.configureBlocking(false);
//将channel注册到selector中,关心事件为op_ACCEPT
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
//循环监听客户端连接
while (true){
//selector等待1s钟,如果没有事件发生则可以去做别的事情
if(selector.select(1000)==0){
System.out.println("服务端等待了1s,没有事件发生");
continue;
}
//如果>0,则得到selectionKeys集合,已经获取到关注的事件了,selectionKeys是关注事件的集合
//通过selectionKeys反向获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
//遍历selectionKeys集合,使用迭代器
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//根据key对应的通道发生的事件做相应的处理
//相当于有客户端连接,给该客户端生成一个socketchannel
if(key.isAcceptable()){
SocketChannel socketChannel = serverSocketChannel.accept();
//设置为非阻塞
socketChannel.configureBlocking(false);
System.out.println("客户端连接成功,生成了一个socketchannel"+socketChannel.hashCode());
//将sockerchannel注册到selector,关注事件为read,同行关联一个buffer
socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()){
//通过key反向获取socketChannel
SocketChannel channel = (SocketChannel)key.channel();
//获取到该channel关联的buffer
ByteBuffer buffer=(ByteBuffer)key.attachment();
channel.read(buffer);
System.out.println("客户端"+new String(buffer.array()));
}
//手动从集合中移除当前的selectionkey,防止重复操作
iterator.remove();
}
}
}
}
2つのクライアントコード
NIOClient.java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的IP和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端没有阻塞,可以做其他工作。。");
}
}
//如果连接成功,发送数据
String str="客户端ZTY已连接";
//直接将字符串对应的字节数组包裹到buffer中,不用指定大小
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer写入到channel
socketChannel.write(buffer);
System.in.read();
}
}
NIOClient2.java
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient2 {
public static void main(String[] args) throws Exception{
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//提供服务器端的IP和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if(!socketChannel.connect(inetSocketAddress)){
while (!socketChannel.finishConnect()){
System.out.println("因为连接需要时间,客户端没有阻塞,可以做其他工作。。");
}
}
//如果连接成功,发送数据
String str="客户端LPJ已连接";
//直接将字符串对应的字节数组包裹到buffer中,不用指定大小
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer写入到channel
socketChannel.write(buffer);
System.in.read();
}
}
最初にサーバーを実行します。以下のスクリーンショット:
クライアント1(NIOClient.java)を実行した後、サーバーインターフェイスは次の出力を生成します。
クライアント2(NIOClient2.java)を実行した後、サーバーインターフェイスは次の出力を生成します。
NettyはNIOに基づいているため、NIOを終了した後、Nettyをマスターすることはそう遠くありません。