1、sc.register(selector,key) 注册的时机?
需要根据需要,不断变换SocketChannel关注的OP事件;
需要叠加 OPS的话,要用 | 或操作,而不能多次注册,多次注册只会覆盖;
OPS用一个byte保存,多次注册,只是把这个byte变量覆盖了,会导致前一个 OPS 失效(如果不重复的话)。
2、一方进程崩溃,另一方如何安全关闭?
在异常处理中,将Key.cancel(),并关闭Key关联的channel的socket
3、如何处理non-blocking模式下,connection的挂起(循环连接直到连接成功)?
SocketChannel.open(SockInetAddress remoteAddr) 按照《JAVA 网络编程》的说法,是blocking模式的;
如果用non-blocking,则执行 connect 之后线程可以继续做其它的事情,直到需要使用channel的时候,
调用 isConnectionPending(), isConnected() 判断连接状态,调用 finishConnect() 结束连接状态,
如果连接有错误,则调用 finishConnect() 时会报异常,需要进行异常处理。
non-blocking模式的connect的好处就是调用connect后可以不管,而不是线程阻塞,业务线程的操作与底层操作系统的
connect操作并行。
报异常后,进行异常处理,从selector中cancel对应的SelectionKey,并关闭对应的channel,重新open一个channel进行
重连。
4、AbstractSelectableChannel与Selector以及SelectionKey的关系
SocketChannel和ServerSocketChannel均继承 AbstractSelectableChannel;
SocketChannel调用的是,实际是 AbstractSelectableChannel.register,实现如下:
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel); //channel根据selector查找有没有对应自己的Selectionkey,可见key跟channel对应,而不是跟 OP事件对应
if (k != null) {
k.interestOps(ops); //将要注册的 OP事件,调用SelectionKey的 interestOps,添加到channel的关注的事件集
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att); //如果没有key,则调用Selector的注册,返回key,可见实际的注册是Selector完成的
addKey(k);
}
}
return k;
}
}
5、几个常用的易混淆的接口
SocketChannel.write(ByteBuffer src)
【从buffer的 position开始读数据,最大读到 remaining 长度,即 position == limit 的时候,如果 socket output buffer空间不足,则不能拷贝】
public abstract long read(ByteBuffer[] dsts, int offset,int length)
【offset指从 dsts[offset] 开始拷贝数据(从socket recv buf拷入),直到 dsts[offset+length-1]。
read(ByteBuffer[] dsts) 相当于 read(dsts,0,dsts.length)】
ByteBuffer.remaining()
【返回 (limit-position) ,这个函数属于基类Buffer】
ByteBuffer.get(byte[] dst, int offset, int length)
【从buffer的 position 开始拷贝,拷贝 length 长度的字节到 dst ,dst从 offset开始接收拷贝字节流,如果 buffer的
remainning < length,则一个字节都不会拷贝,报 BufferUndeflowException】
ByteBuffer.put(byte[] src, int offset, int length)
【将src的字符数组,拷贝指定长度到buffer中,buffer从 position 开始拷贝,src 从 offset 开始拷贝,拷贝长度
为 length,如果 length > (limit-position),即length > remaining,则拷贝失败,一个字节都不拷贝,并且报
BufferOverflowException】
ByteBuffer.wrap(byte[] array, int offset, int length)
【将制定的array封装到buffer里。The new buffer will be backed by the given byte array; that is, modifications to the buffer will cause the array to be modified and vice versa. The new buffer's capacity will be array.length, its position will be offset, its limit will be offset + length, and its mark will be undefined. Its backing array will be the given array, and its array offset will be zero.
ByteBuffer内部是用一个byte[] 来存储数据的,这个函数相当于将byte[] 封装成一个buffer,对这个buffer进行修改,也会影响到
byte[]中的原始数据】
ByteBuffer.rewind()
【The Buffer.rewind() sets the position back to 0, so you can reread all the data in the buffer. The limit remains untouched, thus still marking how many elements (bytes, chars etc.) that can be read from the Buffer.
仅将position设置为0.rewind表示倒带】
ByteBuffer.conpact()
【compact() copies all unread data to the beginning of the Buffer. Then it sets position to right after the last unread element. The limit property is still set to capacity, just like clear() does. Now the Buffer is ready for writing, but you will not overwrite the unread data.
如果buffer.remaining() > 0,表示buffer还有内容残留,执行compact将把 buffer[position]到 buffer[limit-1]的
内容,拷贝到buffer[0] 到 buffer[remaining-1]的区域,position设置为 remaining,从原来的内容后面开始,可以写入数据。】
ByteBuffer.mark()
【将当前 position的值,赋值给mark,作为标记】
ByteBuffer.reset()
【将position的值,重置为 mark】
ByteBuffer.equals()
【Two buffers are equal if:
They are of the same type (byte, char, int etc.) 类型相同
They have the same amount of remaining bytes, chars etc. in the buffer. remaining bytes 数量相同
All remaining bytes, chars etc. are equal. remaining bytes 内容相同
】
ByteBuffer.compareTo()
【The compareTo() method compares the remaining elements (bytes, chars etc.) of the two buffers】
Selector.wakeup()
【促使Selector的第一个selection操作立即返回,相当于唤醒被阻塞的调用了Selector.select()的线程,select()立即返回,
线程继续往下执行,通常此线程是一个while循环,线程执行完select()调用后的代码,将从头开始执行。
如果当前没有selection操作在执行(阻塞),则下一次selection操作将立即返回,除非调用了 selectNow,selectNow将会清除
wakeup的影响,并执行一次 non-blocking selection,返回selected-set的数目。
】
6、注意事项:
6.1 select线程的阻塞
Selector如果在阻塞的状态,调用SocketChannel.register将被阻塞,等待唤醒。
一般情况下,需要在调用select()阻塞之前,进行register处理,否则只能通过wakup唤醒select线程
6.2 FileChannel不能用Selector
The Channel must be in non-blocking mode to be used with a Selector. This means that you cannot use FileChannel's with a Selector since FileChannel's cannot be switched into non-blocking mode. Socket channels will work fine though.
需要根据需要,不断变换SocketChannel关注的OP事件;
需要叠加 OPS的话,要用 | 或操作,而不能多次注册,多次注册只会覆盖;
OPS用一个byte保存,多次注册,只是把这个byte变量覆盖了,会导致前一个 OPS 失效(如果不重复的话)。
2、一方进程崩溃,另一方如何安全关闭?
在异常处理中,将Key.cancel(),并关闭Key关联的channel的socket
3、如何处理non-blocking模式下,connection的挂起(循环连接直到连接成功)?
SocketChannel.open(SockInetAddress remoteAddr) 按照《JAVA 网络编程》的说法,是blocking模式的;
如果用non-blocking,则执行 connect 之后线程可以继续做其它的事情,直到需要使用channel的时候,
调用 isConnectionPending(), isConnected() 判断连接状态,调用 finishConnect() 结束连接状态,
如果连接有错误,则调用 finishConnect() 时会报异常,需要进行异常处理。
non-blocking模式的connect的好处就是调用connect后可以不管,而不是线程阻塞,业务线程的操作与底层操作系统的
connect操作并行。
报异常后,进行异常处理,从selector中cancel对应的SelectionKey,并关闭对应的channel,重新open一个channel进行
重连。
4、AbstractSelectableChannel与Selector以及SelectionKey的关系
SocketChannel和ServerSocketChannel均继承 AbstractSelectableChannel;
SocketChannel调用的是,实际是 AbstractSelectableChannel.register,实现如下:
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel); //channel根据selector查找有没有对应自己的Selectionkey,可见key跟channel对应,而不是跟 OP事件对应
if (k != null) {
k.interestOps(ops); //将要注册的 OP事件,调用SelectionKey的 interestOps,添加到channel的关注的事件集
k.attach(att);
}
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att); //如果没有key,则调用Selector的注册,返回key,可见实际的注册是Selector完成的
addKey(k);
}
}
return k;
}
}
5、几个常用的易混淆的接口
SocketChannel.write(ByteBuffer src)
【从buffer的 position开始读数据,最大读到 remaining 长度,即 position == limit 的时候,如果 socket output buffer空间不足,则不能拷贝】
public abstract long read(ByteBuffer[] dsts, int offset,int length)
【offset指从 dsts[offset] 开始拷贝数据(从socket recv buf拷入),直到 dsts[offset+length-1]。
read(ByteBuffer[] dsts) 相当于 read(dsts,0,dsts.length)】
ByteBuffer.remaining()
【返回 (limit-position) ,这个函数属于基类Buffer】
ByteBuffer.get(byte[] dst, int offset, int length)
【从buffer的 position 开始拷贝,拷贝 length 长度的字节到 dst ,dst从 offset开始接收拷贝字节流,如果 buffer的
remainning < length,则一个字节都不会拷贝,报 BufferUndeflowException】
ByteBuffer.put(byte[] src, int offset, int length)
【将src的字符数组,拷贝指定长度到buffer中,buffer从 position 开始拷贝,src 从 offset 开始拷贝,拷贝长度
为 length,如果 length > (limit-position),即length > remaining,则拷贝失败,一个字节都不拷贝,并且报
BufferOverflowException】
ByteBuffer.wrap(byte[] array, int offset, int length)
【将制定的array封装到buffer里。The new buffer will be backed by the given byte array; that is, modifications to the buffer will cause the array to be modified and vice versa. The new buffer's capacity will be array.length, its position will be offset, its limit will be offset + length, and its mark will be undefined. Its backing array will be the given array, and its array offset will be zero.
ByteBuffer内部是用一个byte[] 来存储数据的,这个函数相当于将byte[] 封装成一个buffer,对这个buffer进行修改,也会影响到
byte[]中的原始数据】
ByteBuffer.rewind()
【The Buffer.rewind() sets the position back to 0, so you can reread all the data in the buffer. The limit remains untouched, thus still marking how many elements (bytes, chars etc.) that can be read from the Buffer.
仅将position设置为0.rewind表示倒带】
ByteBuffer.conpact()
【compact() copies all unread data to the beginning of the Buffer. Then it sets position to right after the last unread element. The limit property is still set to capacity, just like clear() does. Now the Buffer is ready for writing, but you will not overwrite the unread data.
如果buffer.remaining() > 0,表示buffer还有内容残留,执行compact将把 buffer[position]到 buffer[limit-1]的
内容,拷贝到buffer[0] 到 buffer[remaining-1]的区域,position设置为 remaining,从原来的内容后面开始,可以写入数据。】
ByteBuffer.mark()
【将当前 position的值,赋值给mark,作为标记】
ByteBuffer.reset()
【将position的值,重置为 mark】
ByteBuffer.equals()
【Two buffers are equal if:
They are of the same type (byte, char, int etc.) 类型相同
They have the same amount of remaining bytes, chars etc. in the buffer. remaining bytes 数量相同
All remaining bytes, chars etc. are equal. remaining bytes 内容相同
】
ByteBuffer.compareTo()
【The compareTo() method compares the remaining elements (bytes, chars etc.) of the two buffers】
Selector.wakeup()
【促使Selector的第一个selection操作立即返回,相当于唤醒被阻塞的调用了Selector.select()的线程,select()立即返回,
线程继续往下执行,通常此线程是一个while循环,线程执行完select()调用后的代码,将从头开始执行。
如果当前没有selection操作在执行(阻塞),则下一次selection操作将立即返回,除非调用了 selectNow,selectNow将会清除
wakeup的影响,并执行一次 non-blocking selection,返回selected-set的数目。
】
6、注意事项:
6.1 select线程的阻塞
Selector如果在阻塞的状态,调用SocketChannel.register将被阻塞,等待唤醒。
一般情况下,需要在调用select()阻塞之前,进行register处理,否则只能通过wakup唤醒select线程
6.2 FileChannel不能用Selector
The Channel must be in non-blocking mode to be used with a Selector. This means that you cannot use FileChannel's with a Selector since FileChannel's cannot be switched into non-blocking mode. Socket channels will work fine though.