1 定义和特点
A selectable channel for stream-oriented listening sockets.
上述摘自java docs中定义:ServerSocketChannel是面向流的监听socket套接字的可选择性通道。
具有以下特点:
-
具有阻塞和非阻塞两种模式
-
可以注册到多路复用器上(且一般都与复用器配合使用)
-
基于TCP连接
-
需要绑定到特定端口上
-
是线程安全的
ServerSocketChannel支持的可选参数:
参数名 | 作用描述 |
---|---|
SO_RCVBUF | 套接字接收缓冲区大小 |
SO_REUSEADDR | 重新使用地址 |
2 使用与相关API
ServerSocketChannel 的作用:
在服务器端监听新的客户端 Socket
连接,而且本身不传输数据。
1.创建ServerSocketChannel
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">ServerSocketChannel</span> <span style="color:#000000">channel</span> <span style="color:#981a1a">=</span> <span style="color:#000000">ServerSocketChannel</span>.<span style="color:#000000">open</span>(); <span style="color:#aa5500">//创建ServerSocketChannel </span></span></span>
2.绑定到网络接口
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">channel</span>.<span style="color:#000000">bind</span>(<span style="color:#770088">new</span> <span style="color:#000000">InetSocketAddress</span>(<span style="color:#116644">9999</span>)); <span style="color:#aa5500">//绑定至本机的9999端口</span></span></span>
3.接收连接
<span style="background-color:#f8f8f8"><span style="color:#333333"><span style="color:#000000">SocketChannel</span> <span style="color:#000000">socketChannel</span> <span style="color:#981a1a">=</span> <span style="color:#000000">channel</span>.<span style="color:#000000">accept</span>();</span></span>
ServerSocketChannel的accept方法返回SocketChannel套接字通道,用于读取请求数据和写入响应数据。
ServerSocketChannel的阻塞和非阻塞的体现:
-
阻塞模式:在调用accept方法后,将阻塞知道有新的socket连接时返回SocketChannel对象,代表新建立的套接字通道。
-
非阻塞模式:在调用accept方法后,如果无连接建立,则返回
null
;如果有连接,则返回SocketChannel。
3 SocketChannel和ServerSocketChannel
SocketChannel:提供了连接到socket(套接字)的通道,NIO中提供了类似于java.net包中对于网络操作的api的功能。既然已经有连接到Socket套接字的通道,可以主动发起连接、传输数据(client端),ServerSocketChannel的功能就是接收连接、接收数据的通道(server端)。
下面演示一个简单的NIO模型的代码。
Server端:
public class NIOServer {
public static void main (String[] args) throws IOException{
// 创建服务器套接字通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 绑定本地地址
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
// 设置非阻塞
serverSocketChannel.configureBlocking(false);
// 创建选择器
Selector selector = Selector.open();
// 注册服务器套接字通道
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器启动成功");
// 循环处理客户端请求
while (true){
// 选择器如果没有可用的键,则阻塞
if (selector.select(1000) == 0) {
System.out.println("等待连接中...");
continue;
}
// 获取可用的键
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 遍历可用的键
selectionKeys.forEach(key->{
try {
// 按照selectionKey中Key的不同类型对应不同的事件,做不同的处理
// 如果键是客户端连接请求
if (key.isAcceptable()) {
// 接受客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
// 设置为非阻塞8
socketChannel.configureBlocking(false);
// 注册选择器
socketChannel.register(selector, SelectionKey.OP_READ,ByteBuffer.allocate(512));
System.out.println(socketChannel.toString()+"连接成功");
}
// 如果键是可读的
if (key.isReadable()) {
// 获取连接客户端的通道
SocketChannel socketChannel = (SocketChannel)key.channel();
// 获取缓冲区
ByteBuffer buffer = (ByteBuffer)key.attachment();
// 读取数据
socketChannel.read(buffer);
System.out.println("form客户端" + new String(buffer.array()));
}
// 移除键,防止重复处理
selectionKeys.remove(key);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
}
}
client端:
public class NIOClient {
public static void main (String[] args) throws Exception{
SocketChannel socketChannel = SocketChannel.open();
System.out.println("socketChannel "+socketChannel.toString());
//设置为非阻塞
socketChannel.configureBlocking(false);
//尝试连接服务器
if (!socketChannel.connect(new java.net.InetSocketAddress("127.0.0.1",9999))){
//如果连接失败,则循环尝试连接
while (!socketChannel.finishConnect()){
System.out.println("连接服务器中...");
}
}
System.out.println("连接服务器成功");
//向服务器发送消息
String request = "Hello Server!";
socketChannel.write(ByteBuffer.wrap(request.getBytes()));
//线程休眠10秒
Thread.sleep(10000);
}
}