seletor连接器,多路复用器,类似于路由;是用一个selector管理多个channel
0.主要方法
//获取选择器 Selector.open(); //将通道注册到选择器中让选择器管理这个通道 channle.regeister(selc,ops) //检查已经注册在选择器上的通道关心的操作是否有已经就绪可以处理的 int select() //检查注册在选择器上的通道们关心的事件是否已经有就绪可以处理的,如果有此方法返回,返回值为可以处理的数量,如果没有可处理的则此方法阻塞。 //获取已经就绪的键的集合 SetselectedKeys()
1.selector 实现读写
服务端
public class SelectorServiceDemo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // 服务端channel创建 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); // 获取连接 Selector selector = Selector.open(); // 注册选择器 serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while(true){ // 当前已经就绪的数量 int readyCount = selector.select(); if(readyCount > 0 ){ Iterator iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()){ SelectionKey key = (SelectionKey) iterator.next(); if(key.isAcceptable()){ ServerSocketChannel serverSocketChannelConnectable = (ServerSocketChannel) key.channel(); SocketChannel socketChannelConnectable = serverSocketChannelConnectable.accept(); socketChannelConnectable.configureBlocking(false); // 接受连接已完成,再注册一个读数据 socketChannelConnectable.register(selector, SelectionKey.OP_READ); }else if(key.isReadable()){ SocketChannel socketChannelReadable = (SocketChannel) key.channel(); socketChannelReadable.configureBlocking(false); ByteBuffer temp = ByteBuffer.allocate(1); String head = "" ; while(!head.endsWith("\r\n")){ socketChannelReadable.read(temp); head += new String(temp.array()); temp.clear(); } int len = Integer.valueOf(head.substring(0,head.length()-2)).intValue(); ByteBuffer byteBuffer = ByteBuffer.allocate(len); while(byteBuffer.hasRemaining()){ // 保证读入完毕 socketChannelReadable.read(byteBuffer); } String content = new String(byteBuffer.array()); System.out.println("服务端接收到的客户端发送的数据内容:"+content); } // 已操作完毕的,删除掉,否则会报空指针异常 iterator.remove(); } } } } }
客户端
public class SelectorClientDemo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); Selector selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_CONNECT); socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999)); while(true){ int readyCount = selector.select(); if(readyCount > 0 ){ Iterator iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()){ SelectionKey selectionKey = (SelectionKey) iterator.next(); if(selectionKey.isConnectable()){ SocketChannel socketChannelConnectable = (SocketChannel) selectionKey.channel(); socketChannelConnectable.configureBlocking(false); socketChannelConnectable.finishConnect(); socketChannelConnectable.register(selector, SelectionKey.OP_WRITE); }else if(selectionKey.isWritable()){ SocketChannel socketChannelWriteable = (SocketChannel) selectionKey.channel(); socketChannelWriteable.configureBlocking(false); String content = "hello world! i am coming !" ; StringBuilder sb = new StringBuilder(); sb.append(content.length()+"").append("\r\n").append(content); ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes()); while(byteBuffer.hasRemaining()){// 保证写入完毕 socketChannelWriteable.write(byteBuffer); } System.out.println("客户端发送的数据内容:"+sb.toString()); } } // 删除已处理过的 iterator.remove(); } } } }
运行发现,代码一直执行,循环输出
原因:
客户端发起连接
服务端接受连接
客户端写
服务端读
客户端,isWriteable 因为数据已经读出,发现还可以写,继续写
服务端,isReadable 因为数据已经写入,发现还可以读,继续读
...
2.
将多次读写,改为一次读写
Selector 维护的三个集合
- 已注册: register到selector 上的channel集合
- 已就绪: selector() 方法执行时,已准备好做某事件的channel 集合
- 待删除: 不能直接删除,防止此channel在使用中,引发其他情况;待删除,在下次 selector方法执行前删除
修改客户端代码,增加 cancel代码
表示,将已注册到selector上的channel 集合中的当前 channel 移动 待删除集合中
待下次执行 seletor() 方法前,删除掉
String content = "hello world! i am coming !" ; StringBuilder sb = new StringBuilder(); sb.append(content.length()+"").append("\r\n").append(content); ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes()); while(byteBuffer.hasRemaining()){ socketChannelWriteable.write(byteBuffer); } System.out.println("客户端发送的数据内容:"+sb.toString()); // 取消,在上面的代码的基础上,增加此行 selectionKey.cancel();
3.NIO的优势
操作:
3.1 启动服务端
3.2 多次启动客户端
模拟实现 服务端同时为多个客户端服务
4.发送汉字
string content = "你好!全世界!";
发现接收到内容丢失!
原因:str.length 指的是 字符串的字符个数;而传递的内容是 bytes ,而按照 UTF-8编码,汉字占用三个字节,即使是GBK编码格式,汉字也占用两个字节
客户端,发送内容的长度,改为
content.getBytes().length
5.
异常
远程主机强制关闭连接
强制关闭客户端,则服务端报错;或相反;
处理:
对整个while(true)内部进行异常捕获,
catch 部分 continue ;
6.selectionKey
- OP_READ = 1 << 0
- OP_WRITE = 1 << 2
- OP_CONNECT = 1 << 3;
- OP_ACCEPT = 1 << 4
四个常量值,对应四个不同的值转换为2进制的形式,对应不同的开关
int 类型为 4B = 32bit
read | 00000000 00000000 00000000 00000001 |
write | 00000000 00000000 00000000 00000100 |
connect | 00000000 00000000 00000000 00001000 |
accept | 00000000 00000000 00000000 00010000 |
由此得出:
若同时注册多个,即是几种情况的组合,若取消注册的某个值,则将这个值取反,再与操作
6.1 组合
read & write & accept
00000000 00000000 00000000 00000001
00000000 00000000 00000000 00000100
00000000 00000000 00000000 00010000
00000000 00000000 00000000 00010101
6.2 取消 write
00000000 00000000 00000000 00000100--写
11111111 11111111 11111111 11111011--取反
00000000 00000000 00000000 00010101--当前的组合结果
00000000 00000000 00000000 00010001--与操作 -- 去掉了写,剩余 read 与 accept 组合
7.同时注册多个
服务端
public class SelectorServerDemo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { // 创建channel,设置非阻塞模式,绑定端口号 ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); serverSocketChannel.configureBlocking(false); serverSocketChannel.socket().bind(new InetSocketAddress(9999)); // 创建选择器,注册channel到选择器 Selector selector = Selector.open(); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); // 死循环,时刻等待客户端发起连接 while(true){ // 当前已注册的 channel 集合中,已准备就绪的channel数量 int readCount = selector.select(); if(readCount > 0 ){ // 遍历当前已就绪的channel集合 Iterator iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()){ // selectionKey 连接 channel 与 selector SelectionKey selectionKey = (SelectionKey) iterator.next(); // 是否接收请求就绪 if(selectionKey.isAcceptable()){ // 取出的channel与注册的channel是同一个 ServerSocketChannel serverSocketChannelConnect = (ServerSocketChannel) selectionKey.channel(); SocketChannel socketChannel = serverSocketChannelConnect.accept(); socketChannel.configureBlocking(false); // 接收连接后,进行读取客户端数据并响应操作 socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE); // 是否读取请求就绪 }else if(selectionKey.isReadable()){ // 获取当前的channel SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); // 读取客户端发送的内容的头部信息 String head = "" ; ByteBuffer temp = ByteBuffer.allocate(1); while(!head.endsWith("\r\n")){ socketChannel.read(temp); head += new String(temp.array()); temp.clear(); } int len = Integer.valueOf(head.substring(0, head.length() - 2)).intValue(); // 读取客户端发送的内容部分 ByteBuffer byteBuffer = ByteBuffer.allocate(len); while(byteBuffer.hasRemaining()){ socketChannel.read(byteBuffer); } String content = new String(byteBuffer.array()); System.out.println("client:"+content); }else if (selectionKey.isWritable()){ // 此处可不获取,与注册的channel是同一个 SocketChannel socketChannel = (SocketChannel) selectionKey.channel(); String content = "hi client,you send date already accept" ; StringBuffer sb = new StringBuffer(); // 发送数据的长度+发送内容 sb.append(content.getBytes().length+"").append("\r\n").append(content); ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes()); // 确保数据已全部写入 while(byteBuffer.hasRemaining()){ socketChannel.write(byteBuffer); } // 写入操作完毕,取消注册到selector上的write // 方式与 op_write 的结果取反,再进行与操作 socketChannel.register(selector, selectionKey.interestOps() & ~SelectionKey.OP_WRITE); } iterator.remove(); } } } } }
客户端
public class SelectorClentDemo { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { SocketChannel socketChannel = SocketChannel.open(); socketChannel.configureBlocking(false); socketChannel.connect(new InetSocketAddress("127.0.0.1", 9999)); // 直接操作 socketChannel 同时注册多个 Selector selector = Selector.open(); socketChannel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE | SelectionKey.OP_READ ); while(true){ int readyCount = selector.select(); if(readyCount >0 ){ Iterator iterator = selector.selectedKeys().iterator(); while(iterator.hasNext()){ SelectionKey selectionKey = (SelectionKey) iterator.next(); if(selectionKey.isConnectable()){ SocketChannel socketChannelConnect = (SocketChannel) selectionKey.channel(); socketChannelConnect.configureBlocking(false); socketChannelConnect.finishConnect(); }else if(selectionKey.isWritable()){ SocketChannel socketChannelWrite = (SocketChannel) selectionKey.channel(); socketChannelWrite.configureBlocking(false); String content = "hello world " ; StringBuilder sb = new StringBuilder(); sb.append(content.length()+"").append("\r\n").append(content); ByteBuffer byteBuffer = ByteBuffer.wrap(sb.toString().getBytes()); while(byteBuffer.hasRemaining()){ socketChannelWrite.write(byteBuffer); } System.out.println("send:"+sb.toString()); // selectionKey.cancel(); // 运行后,取消所有事件 // 实际此时只需去掉写事件 socketChannel.register(selector, selectionKey.interestOps() & ~SelectionKey.OP_WRITE); }else if (selectionKey.isReadable()){ SocketChannel socketChannelRead = (SocketChannel) selectionKey.channel(); socketChannel.configureBlocking(false); String head = "" ; ByteBuffer temp = ByteBuffer.allocate(1); while(!head.endsWith("\r\n")){ socketChannelRead.read(temp); head += new String(temp.array()); temp.clear(); } int len = Integer.valueOf(head.substring(0, head.length()-2)).intValue(); ByteBuffer byteBuffer = ByteBuffer.allocate(len); while(byteBuffer.hasRemaining()){ socketChannelRead.read(byteBuffer); } String content = new String(byteBuffer.array()); System.out.println("content:"+content); } iterator.remove(); } } } } }