我开始以为 attach 是分读的附件和写的附件的,但写了测试代码发现并不是。 服务端代码:
package NonBlocking;
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 NonBlockingServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress(8888));
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); //首先注册 ACCEPT 事件
int result = 0; int i = 1;
while(true) { //遍历获得就绪事件
result = selector.select();
System.out.println(String.format("selector %dth loop, ready event number is %d", i++, result));
if (result == 0) {
continue;
}
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){ //就绪事件可能不止一个
SelectionKey sk=iterator.next();
if(sk.isAcceptable()){ //如果是 ACCEPT,那么与之关联的 channel 肯定是个 ServerSocketChannel
System.out.println("服务端有 ACCEPT 事件就绪");
ServerSocketChannel ss = (ServerSocketChannel)sk.channel();
SocketChannel socketChannel = ss.accept();
socketChannel.configureBlocking(false); //也切换非阻塞
socketChannel.register(selector, SelectionKey.OP_READ); //注册 read 事件
}
else if(sk.isReadable()){ //如果是 READ,那么与之关联的 channel 肯定是个 SocketChannel
System.out.println("服务端有 READ 事件就绪");
SocketChannel socketChannel = (SocketChannel)sk.channel();
ByteBuffer buf=ByteBuffer.allocate(1024);
int len=0;
StringBuilder sb = new StringBuilder();
while((len=socketChannel.read(buf))>0){
buf.flip();
String s = new String(buf.array(),0,len);
sb.append(s);
buf.clear();
}
//服务端开始响应消息
ByteBuffer readAtta = (ByteBuffer)sk.attachment();
if (readAtta != null) {
System.out.println("lasttime readAtta string is: "+new String(readAtta.array()));
} else {
System.out.println("lasttime readAtta is null ");
}
sk.attach(ByteBuffer.wrap(sb.toString().getBytes()));
sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
String sendStr = "您的消息'"+sb.toString()+"'我已经收到了";
System.out.println("接下来 attach 的是:"+sendStr);
sk.attach(ByteBuffer.wrap(sendStr.getBytes()));
}
else if(sk.isWritable()){
System.out.println("服务端有 WRITE 事件就绪");
SocketChannel socketChannel = (SocketChannel)sk.channel();
ByteBuffer writeAtta = (ByteBuffer) sk.attachment();
if (writeAtta != null) {
System.out.println("lasttime writeAtta string is: "+new String(writeAtta.array()));
} else {
System.out.println("lasttime writeAtta is null ");
}
sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
}
iterator.remove();
System.out.println("after remove key");
}
}
}
}
客户端代码(这个不重要,就放链接里了): https://paste.ubuntu.com/p/58P39BQQTm/ 使用方法:客户端在控制台每输入一句话,再去服务端看执行效果:
selector 1th loop, ready event number is 1
服务端有 ACCEPT 事件就绪
after remove key
selector 2th loop, ready event number is 1
服务端有 READ 事件就绪
lasttime readAtta is null
接下来 attach 的是:您的消息'你好'我已经收到了
after remove key
selector 3th loop, ready event number is 1
服务端有 WRITE 事件就绪
lasttime writeAtta string is: 您的消息'你好'我已经收到了
after remove key
selector 4th loop, ready event number is 1
服务端有 READ 事件就绪
lasttime readAtta string is: 您的消息'你好'我已经收到了
接下来 attach 的是:您的消息'他好吗'我已经收到了
after remove key
selector 5th loop, ready event number is 1
服务端有 WRITE 事件就绪
lasttime writeAtta string is: 您的消息'他好吗'我已经收到了
after remove key
sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
和sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
是正确的注册和反注册 write 事件的姿势?然后我进行服务端代码修改:
sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);
换成socketChannel.register(selector, SelectionKey.OP_WRITE)
sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
换成socketChannel.register(selector, SelectionKey.OP_READ)
再看服务端效果则是这样了:
selector 1th loop, ready event number is 1
服务端有 ACCEPT 事件就绪
after remove key
selector 2th loop, ready event number is 1
服务端有 READ 事件就绪
lasttime readAtta is null
接下来 attach 的是:您的消息'你好'我已经收到了
after remove key
selector 3th loop, ready event number is 1
服务端有 WRITE 事件就绪
lasttime writeAtta string is: 您的消息'你好'我已经收到了
after remove key
selector 4th loop, ready event number is 1
服务端有 READ 事件就绪
lasttime readAtta is null
接下来 attach 的是:您的消息'他好吗'我已经收到了
after remove key
selector 5th loop, ready event number is 1
服务端有 WRITE 事件就绪
lasttime writeAtta string is: 您的消息'他好吗'我已经收到了
after remove key
可以发现:
socketChannel.register(selector, SelectionKey.OP_READ);
后,下一次再执行 attachment,附件就丢了,变成 null 了。socketChannel.register(selector, SelectionKey.OP_WRITE);
后,下一次再执行 attachment,附件却不会丢。 这是为什么呢? 1
az467 2020-03-22 11:33:37 +08:00
读文档呀。
Selector 持有两个 Set,Keys 和 SelectedKeys,后者只是前者的子集。 每次调用 select(),会把前者中的部分添加到后者中,而不是重复注册生成新的 SelectionKey 。 register()也是一样的,如果已经在给定的 selector 注册过,那么方法只会对 interestOps 和 attachment 进行覆盖。 key.attach()会设置 key 的 attachment 字段,而一个 key 和一个 channel 绑定,所以你的理解是正确的。 socketChannel.register(selector, SelectionKey.OP_WRITE); 👈 String sendStr = "您的消息'"+sb.toString()+"'我已经收到了"; System.out.println("接下来 attach 的是:"+sendStr); sk.attach(ByteBuffer.wrap(sendStr.getBytes())); 👈 可以看出,你调用 attach()在 register()之后,所以 attachment 不会“丢失”。 |
2
amiwrong123 OP @az467
好吧,大概懂了。等会我再去仔细看文档。 但有个地方没想通,就是第二种运行效果,为啥 ``` selector 4th loop, ready event number is 1 服务端有 READ 事件就绪 lasttime readAtta is null 接下来 attach 的是:您的消息'他好吗'我已经收到了 after remove key ``` 为什么经过 socketChannel.register(selector, SelectionKey.OP_READ);后(因为上一次执行了 WRITE 事件,然后在 WRITE 事件里,执行了 socketChannel.register(selector, SelectionKey.OP_READ);),下一次再执行 attachment,附件就丢了,变成 null 了呢 |
3
az467 2020-03-22 12:08:01 +08:00
@amiwrong123
你可以认为 socketChannel.register(selector, SelectionKey.OP_READ); 其实就是 socketChannel.register(selector, SelectionKey.OP_READ, null); 第三个参数就是 attachment |
4
amiwrong123 OP @az467
纳尼,这怎么说,看文档也没看出有这个意思啊。注册 读 或 写 表现还不一样昂 |
5
az467 2020-03-22 12:33:29 +08:00 1
@amiwrong123
* Registers this channel with the given selector, returning a selection * key. * * <p> An invocation of this convenience method of the form * * <blockquote><tt>sc.register(sel, ops)</tt></blockquote> * * behaves in exactly the same way as the invocation * * <blockquote> <tt>sc.{@link * #register(java.nio.channels.Selector,int,java.lang.Object) * register}(sel, ops, null)</tt></blockquote> 还是我原来说的,你仔细看你的代码: //sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE); socketChannel.register(selector, SelectionKey.OP_WRITE); 👈 sk.attach(ByteBuffer.wrap(sendStr.getBytes())); 👈 注册写,attachment 置 null,然后调用了 attach(),attachment 为 sendStr 。 //sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE); socketChannel.register(selector, SelectionKey.OP_READ); 👈 注册读,attachment 置空,没有调用 attach(),所以 attachment 依然为 null 。 |
6
az467 2020-03-22 12:33:29 +08:00
@amiwrong123
* Registers this channel with the given selector, returning a selection * key. * * <p> An invocation of this convenience method of the form * * <blockquote><tt>sc.register(sel, ops)</tt></blockquote> * * behaves in exactly the same way as the invocation * * <blockquote> <tt>sc.{@link * #register(java.nio.channels.Selector,int,java.lang.Object) * register}(sel, ops, null)</tt></blockquote> 还是我原来说的,你仔细看你的代码: //sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE); socketChannel.register(selector, SelectionKey.OP_WRITE); 👈 sk.attach(ByteBuffer.wrap(sendStr.getBytes())); 👈 注册写,attachment 置 null,然后调用了 attach(),attachment 为 sendStr 。 //sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE); socketChannel.register(selector, SelectionKey.OP_READ); 👈 注册读,attachment 置空,没有调用 attach(),所以 attachment 依然为 null 。 |
7
amiwrong123 OP @az467
好吧,懂了。怪我没仔细看代码~~ 所以 注册什么事件的时候,不想弄丢附件,就 sk.interestOps(sk.interestOps() | SelectionKey.OP_WRITE);呗 而想直接清空附件,就 socketChannel.register(selector, SelectionKey.OP_WRITE);呗 也不所谓哪种是正确的 |