概述
文章目录
- 1.概述
- 2.心跳服务
- 3.服务端实现
- 4.客户端实现
- 5.结果展示
- 6.总结
1.概述
Netty原理详解系列的前两篇文章介绍了NIO中的三个重要的组件,有了理论的基础,这篇博客就带着大家实现一个简单的心跳服务。
2.心跳服务
在类似RPC(远程过程调用)场景中为了保证传输的效率,通常情况下会采用长链接,而长链接的保持即通过定时心跳实现。类似场景在消息推送服务中也是非常常见。所以心跳服务是网络编程中的基础且普遍的应用。
3.服务端实现
服务端主要用于接收客户端的请求,并且接收客户端的心跳请求,进行响应。
服务端流程说明:
- 创建serverSocketChannel
- 设置阻塞模型为false
- 绑定端口
- 创建选择器Selector
- 将第一步创建的channel注册到选择器Selector中,并设置监听模式是ACCEPT,也就是用于接收客户端的连接消息。
- 轮询选择键集
- 若当前选择键是可接收连接的状态。则接收客户端连接,构建新的管道socketChannel,设置阻塞模型为false。然后将该管道注册到选择器中,并设置监听模式是Read,用来监听客户端发送来的消息。
- 若当前选择键是可写模式,则基于缓冲区,将内容写入管道中,写入后将监听模式修改为Read。
- 若当前选择键是可读模式,则将内容从管道中读到缓冲区。也就是写入缓冲区。然后再从缓冲区中拿出消息。最后设置监听模式为Write。注意写入缓冲区后,要记得对缓冲区执行flip操作。才可以正确的读取缓冲区。
代码实现如下:
/**
* @author gongsenlin
* @version 1.0
* @date 2020-10-12 20:32
*/
public class echoServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//创建serverSocketChannel
serverSocketChannel.configureBlocking(false);//设置阻塞模型为false
Selector selector = Selector.open();//创建选择器Selector
serverSocketChannel.bind(new InetSocketAddress(8080));//绑定端口
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//将第一步创建的channel注册到选择器Selector中,并设置监听模式是ACCEPT,也就是用于接收客户端的连接消息。
for (; ; ) {// 轮询选择键集
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (!key.isValid()) {
continue;
}
if (key.isAcceptable()) {
System.out.println("接收客户端连接");
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
} else if (key.isWritable()) {
String str = "心跳 pong";
SocketChannel channel = (SocketChannel) key.channel();
channel.write(ByteBuffer.wrap(str.getBytes()));
key.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
channel.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
key.interestOps(SelectionKey.OP_WRITE);
}
}
}
}
}
4.客户端实现
和服务端建立连接,建立可发送可接收的消息通道
客户端的流程如下:
- 创建socketChannel 这里和服务端的不同。原因是socketChannel是SelectableChannel的子类 可以设置CONNECT、WRITE、READ模式,但是serverSocketChannel仅可以设置ACCEPT模式
- 设置阻塞模式为false
- 创建选择器Selector
- 将socketChannel注册到选择器中,并设置监听模型为CONNECT
- 执行连接服务端 connect()是异步完成的,当服务端Accept链接,客户端会触发OP_CONNECT事件,然后必须调用 finishConnect() 才能真正完成调用。
- 轮询选择键集
- 若当前选择键是连接状态,那么要执行socketChannel.finishConnect完成连接。然后将选择键的监听模式改为WRITE
- 若当前选择键是可写状态,那么就基于buffer写入到channel中,将“心跳 ping”发送到服务端,并修改选择键的监听模式为READ
- 若当前选择键是可读模式,则将内容从管道中读到缓冲区。也就是写入缓冲区。然后再从缓冲区中拿出消息,得到客户端发送的“心跳 pong”。最后设置监听模式为Write。注意写入缓冲区后,要记得对缓冲区执行flip操作。才可以正确的读取缓冲区。
代码实现如下:
public static void main(String[] args) throws IOException, InterruptedException {
SocketChannel socketChannel = SocketChannel.open();//创建socketChannel
socketChannel.configureBlocking(false);//设置阻塞模式为false
Selector selector = Selector.open();//创建选择器Selector
socketChannel.register(selector, SelectionKey.OP_CONNECT);//将socketChannel注册到选择器中,并设置监听模型为CONNECT
socketChannel.connect(new InetSocketAddress("127.0.0.1", 8080));//连接服务端
for (; ; ) {// 轮询选择键集
selector.select();
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (!key.isValid()) {
continue;
}
if (key.isConnectable()) {
System.out.println("是否连接:" + socketChannel.isConnected());
socketChannel.finishConnect();//完成与服务端的连接
System.out.println("是否连接:" + socketChannel.isConnected());
key.interestOps(SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
String str = "心跳 ping";
socketChannel.write(ByteBuffer.wrap(str.getBytes()));
key.interestOps(SelectionKey.OP_READ);
} else if (key.isReadable()) {
ByteBuffer buffer = ByteBuffer.allocate(1024);
socketChannel.read(buffer);
buffer.flip();
System.out.println(new String(buffer.array(), 0, buffer.limit()));
key.interestOps(SelectionKey.OP_WRITE);
Thread.sleep(2_000);
}
}
}
}
5.结果展示
先启动服务端,然后启动客户端。控制台的输出如下:
6.总结
客户端与服务端的异同
- 服务端创建的是ServerSocketChannel,用于接收客户端的连接, 该通道仅能设置监听模式为ACCEPT
- 客户端创建的是SocketChannel,用于发送连接,发送消息,读取消息,可设置的监听模式有三种WRITE,READ,CONNECT
- 服务端在接收了连接后,要多创建一个用于收发消息的socketChannel,有多少个客户端连接,就会创建多少个socketChannel,上限是多少个连接不是很清楚,可能和电脑的额配置有关系吧,清楚的大佬可以评论区分享一下。我的电脑上模拟了1000个连接是可行的,1W个连接就报错了。
- 服务端与客户端用于收发消息的channel 对应的键的监听模式在WRITE和READ之间来回切换。
最后
以上就是年轻柜子为你收集整理的Netty原理详解系列(三)---NIO实战之心跳服务的全部内容,希望文章能够帮你解决Netty原理详解系列(三)---NIO实战之心跳服务所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
发表评论 取消回复