Socket/ServerSocket
- 一次性连接
- socket 用完在两端close 即可
- 长连接
- socket端
- 发送:在每次write到OutputStream时,使用flush();
- 接收:用一个线程专门监听socket对象的inputStream,如果available>0,则读取里面的数据
- ServerSocket端
- 接收: 用一个(或多个)线程监听已连接的socket,如果inputStrea的available>0,说明有数据
- 发送:write完flush
- socket端
SocketChannel/ServerSocketChannel
- 阻塞模式 (blocking)
- 一次性连接
- socketChannel 用完在两端close() 即可
- 长连接
- socketChannel端
- 发送:write即可;
- 接收:用一个线程专门监听socketChannel.read()>0,则读取里面的数据
- ServerSocketChannel 端
- 接收:用一个线程专门监听socketChannel.read()>0,则读取里面的数据
- 发送:write即可;
- socketChannel端
- 一次性连接
- 非阻塞模式 (non-blocking)
- 见下文
附
若是使用 阻塞模式的SocketChannel,与传统Socket相比,两者都是阻塞的;在使用长连接的方式时,Socket可以通过InputStream的avaliable()得到未读字节从而确定一次发送是否结束, 而SocketChannel 由于无法获得剩余未读字节,不能判断是都结束一次发送。
解决办法
- 发送端write()时,在ByetBuffer 里的约定位置写入描述(可以是一个json的字节数组),储存本次发送byte的lenth、以及其他必要信息。在接收端,按照约定先把描述json的字节从ByteBuffer取出即可。
如果通过Socket向ServerSocketChannel 发送数据,也是按照SocketChannel一样处理即可。
- 网络Channel不支持直接缓冲区
DatagramChannel
~ | ~ |
---|---|
write\read | 读写数据,必须在connected状态下使用 |
send\receive | 读写数据,可以在不connected状态下使用, 但是这样每次都会做安全检查, 避免此项安全检查开销的方法是首先通过 connect 方法连接该套接字 |
- 使用非阻塞模式的DatagramChannel时,需注意在注册selector时,只有 OP_WRITE\OP_READ 是允许的
- 调用connect方法,并不会真实的连接目标地址,但是DatagramChannel对象的isConnected会改变
- read、write 只有在connected状态下才可以使用,receive、send 不论是否connected状态都可以使用,但是未连接状态会多一步安全检查。
Selector
selector 是实现非阻塞式Socket的核心。
~~ | ~ |
---|---|
open | 略 |
register | |
select | |
wakeUp | 如果有一个线程调用select()而阻塞,另一个线程调用该对象的wakeUp, 可使select()立即返回,即使没有已就绪的selectedKey。 |
close | 关闭selector,将会使注册在该对象上的selectedKey失效,与之绑定的通道不会受影响 |
创建selector
Selector selector = Selector.open();
注册channeld到selector
SelectionKey key = selector.register(channelObj,SelectionKey.OP_ACCEPT);
//register 返回监听channelObj的SelectionKey对象
//SelectionKey对象将监听channelObj的accept
获取selector中已经就绪的SelectionKey数量
int num = selector.select();
/*
//select方法返回已经就绪的channel数量
int select() 阻塞到至少有一个已经就绪的channel
int select(long timeout) timeout表示阻塞时限
int selectNow() 非阻塞,如果没有就绪的cahnnel,返回0
*/
获取selector中已经就绪的SelectionKey集合
Set set =selector.selectedKeys();
完整demo
//创建channel
ServerSocketChannel channelObj = ServerSocketChannel.open(7777);
//创建selector
Selector selector = Selector.open();
//注册通道
SelectionKey key = selector.register(channelObj,SelectionKey.OP_ACCEPT);
while(selector.select()>0){
Set set =selector.selectedKeys();
//迭代已就绪的selectedKey
Iterator<SelectionKey> iterator = set.iterator();
while(iterator.hasNext()){
SelectionKey key = set.next();
//获得与selectedKey绑定的通道
Channel target = key.channel();
//判断就绪状态
if(key.isAcceptable()){
}else if(key.isConnected()){
}else if(key.isReadable()){
}else if(key.isWritable()){
}
iterator.remove();
}
//已经处理过的就绪selectedKey需要移除,否则下次获取selectedKeys集合时还会存在之前处理过的selectedKey
}
SelectionKey
静态常量 | ~ |
---|---|
OP_CONNECT | 连接就绪,channel成功连接 |
OP_ACCEPT | 接收就绪,ServerSocketChannel 准备好接收 |
OP_READ | 读取就绪,通道有数据可读 |
OP_WRITE | 写就绪 |
方法 | ~ |
---|---|
isAcceptable | 略 |
isConnected | 略 |
isReadable | 略 |
isWritable | 略 |
isVaild | 返回 选择键是否cancel或其选择器是否close或注册的通道是否关闭 |
cancel|请求取消此键的通道到其选择器的注册
- SelectionKey用来与channel绑定,一个channel注册到Slector对象中时,便会产生一个SelectionKey对象,通过它可以获得channel处于何种就绪状态
Pipe
Unix系统中,管道被用来连接一个进程的输出和另一个进程的输入。java中的Pipe是进程内(在Java虚拟机进程内部)而非进程间使用的。
graph LR
A[A线程]-->|write|B[Pipe.Sink<br>Pipe.Source]
C[B线程]-->|read|B
Pipe p = Pipe.open();
//写入
p.sink().write(ByteBuffer.wrap("管道测试".getBytes()));
//读取
ByteBuffer buff = ByteBuffer.allocate(1024);
while(p.source().read(buff)>0) {
System.out.println(new String(buff.array(),0,buff.limit()));
buff.clear();
}