4.基于NIO的群聊系统

【README】

1.本文总结自B站《netty-尚硅谷》,很不错;

2.文末有错误及解决方法;


【1】群聊需求

1)编写一个 NIO 群聊系统,实现服务器端和客户端之间的数据简单通讯(非
阻塞)
2)实现多人群聊;
3)服务器端:可以监测用户上线,离线,并实现消息转发功能;
4)客户端:通过channel 可以无阻塞发送消息给其它所有用户,同时可以接受
其它用户发送的消息(有服务器转发得到);


【2】概要设计

1)服务器端:

  •   服务器启动并监听 6667 ;
  •   服务器接收客户端消息,并实现转发,处理上线 与  离线;

2)客户端

  •   连接服务器;
  •   发送消息;
  •   接收服务器消息 ;

【3】代码实现及自测

【3.1】服务器端

/*** @Description 群聊服务器端 * @author xiao tang* @version 1.0.0* @createTime 2022年08月19日*/
public class NIOGchatServer {private Selector selector;private ServerSocketChannel listenChannel;private static final int PORT = 6667;/*** @description 构造器* @author xiao tang* @date 2022/8/19*/public NIOGchatServer() {try {// 得到选择器selector = Selector.open();// 初始化 ServerSocketChannellistenChannel = ServerSocketChannel.open();// 绑定端口listenChannel.socket().bind(new InetSocketAddress(PORT));// 设置非阻塞模式listenChannel.configureBlocking(false);// 把listenChannel注册到selector,事件为 ACCEPTlistenChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (Exception e) {e.printStackTrace();System.out.println("群聊服务器构造异常");}}public static void main(String[] args) throws IOException {// 创建服务器对象,并监听端口new NIOGchatServer().listen();}/*** @description 监听* @param* @author xiao tang* @date 2022/8/19*/public void listen() throws IOException {while(true) {// 等待客户端请求连接selector.select();// 获取选择key集合Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) { // 通道发生连接事件SocketChannel sc = listenChannel.accept();sc.configureBlocking(false); // 设置为非阻塞// 将 sc 注册到 selector 上sc.register(selector, SelectionKey.OP_READ);// 提示System.out.println(sc.getRemoteAddress() + " connected successfully.");}if (key.isReadable()) { // 通道发生 read 事件// 处理读this.readData(key);}// 用完之后,要移除keyiterator.remove();}}}/** * @description 读取客户端消息* @param key 选择键* @return * @author xiao tang * @date 2022/8/19 */private void readData(SelectionKey key) {// 定义一个socketchannelSocketChannel channel = null;try {// 取到关联的channelchannel = (SocketChannel) key.channel();// 创建缓冲 bufferByteBuffer buffer = ByteBuffer.allocate(1024);int count = channel.read(buffer);// 根据count的值做处理if (count > 0) {// 把缓冲区的数据转为字符串并输出String msg = new String(buffer.array(), 0, count, StandardCharsets.UTF_8);// 输出该消息System.out.println(msg);// 向其他客户端转发消息this.forward2OtherClients(msg, channel);}} catch (IOException e) {e.printStackTrace();try {System.out.println(channel.getRemoteAddress() + " has been offline.");// 取消注册key.channel();// 关闭通道channel.close();} catch (IOException e2) {e2.printStackTrace();}}}/*** @description 消息转发给其他客户端* @param msg 消息* @param self 当前 SocketChannel* @author xiao tang* @date 2022/8/19*/private void forward2OtherClients(String msg, SocketChannel self) throws IOException {// 遍历所有注册到 selector 上的 SocketChannel 并排除自己for (SelectionKey key : selector.keys()) {// 排除自己if (key.equals(self.keyFor(selector))) continue;// 通过key 取出对应的 SocketChannelChannel targetChannel = key.channel();// 消息转发if (targetChannel instanceof SocketChannel) {SocketChannel dest = (SocketChannel) targetChannel;// 把 msg 存储到bufferByteBuffer buffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));// 把buffer数据写入通道dest.write(buffer);}}}}

【3.2】客户端

/*** @Description NIO群聊客户端* @author xiao tang* @version 1.0.0* @createTime 2022年08月19日*/
public class NIOGchatClient {// 定义相关的属性private static final String HOST = "127.0.0.1"; // 服务器ip地址private static final int PORT = 6667; // 服务器端口private Selector selector;private SocketChannel socketChannel;private String userName;// 线程池private static ExecutorService THREAD_POOL = Executors.newCachedThreadPool();public static void main(String[] args) {try {// 启动客户端NIOGchatClient client = new NIOGchatClient();// 启动一个线程,每隔3秒读取服务器发送的数据THREAD_POOL.submit(new Runnable() {@Overridepublic void run() {while(true) {try {client.read();TimeUnit.SECONDS.sleep(3);} catch (Exception e) {e.printStackTrace();break;}}}});// 客户端接收控制台输入,并发送数据给服务器Scanner scanner = new Scanner(System.in);while(scanner.hasNextLine()) {client.send(scanner.nextLine());}} catch (Exception e) {e.printStackTrace();} finally {THREAD_POOL.shutdown();}}/*** @description 构造器* @author xiao tang* @date 2022/8/19*/public NIOGchatClient() throws IOException {this.selector = Selector.open();// 连接服务器socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", PORT));// 设置非阻塞socketChannel.configureBlocking(false);// 将 channel 注册到 selector,事件 READsocketChannel.register(this.selector, SelectionKey.OP_READ);// 得到userNameuserName = socketChannel.getLocalAddress().toString().substring(1);System.out.println(userName + " connected server successfully.");}/*** @description 发送消息到服务器* @param msg 消息* @author xiao tang* @date 2022/8/19*/private void send(String msg) {msg = userName + ":" + msg;ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes(StandardCharsets.UTF_8));try {socketChannel.write(buffer);} catch (IOException e) {e.printStackTrace();}}/*** @description 从通道读取数据并显示* @author xiao tang* @date 2022/8/20*/private void read() throws IOException {selector.select();// 存在可用通道,读取数据并显示 (注意这里是 selector.selectedKeys() 而不是 selector.keys() )Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();// 若可读通道,则读取if (key.isReadable()) {SocketChannel sc = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int count = sc.read(buffer);System.out.println(new String(buffer.array(), 0, count , StandardCharsets.UTF_8));}// 用完key要移除iterator.remove();}}
}

【3.3】测试效果


【4】报错及解决

1)问题1:为什么要移除key ?

// 用完之后,要移除key
iterator.remove();

refer2 Why the key should be removed in `selector.selectedKeys().iterator()` in java nio? - Stack Overflow

There are 2 tables in the selector:

  1. registration table: when we call channel.register, there will be a new item(key) into it. Only if we call key.cancel(), it will be removed from this table.

  2. ready for selection table: when we call selector.select(), the selector will look up the registration table, find the keys which are available, copy the references of them to this selection table. The items of this table won't be cleared by selector(that means, even if we call selector.select() again, it won't clear the existing items)

That's why we have to invoke iter.remove() when we got the key from selection table. If not, we will get the key again and again by selector.selectedKeys() even if it's not ready to use.

大意就是:选择器中有2个表,分别是 表1是注册表; 表2是就绪选择表

调用 selector.select() 时, 注册表1中对应通道有事件的key 会被拷贝到就绪选择表2;而 选择器不会清理表2的key;即便我们重复调用 selector.select() 时,它也不会清理表2的key;

这也就是为什么我们从选择表2中获得key后,会调用 it.remove() 清理掉key;如果不清理,我们重复调用 selector.selectedKeys() 时,还是会获取之前的key,即便这些key对应 通道没有事件,这就会导致报空指针

2)分清楚 selector.selectedKeys() 和 selector.keys() 的 区别

  • selector.selectedKeys():获取有事件发生的通道对应的键集合,如 ACCEPT事件,READ事件;
  • selector.keys():获取注册到当前选择器的所有通道对应的key集合;(因为通道要先实现多路复用,就需要注册到选择器,选择器会产生一个key,与通道关联起来);

3)为什么客户端或服务器在读取缓冲区的内容时,我要通过offset + 长度去获取?如 代码:

// 若可读通道,则读取
if (key.isReadable()) {SocketChannel sc = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int count = sc.read(buffer);System.out.println(new String(buffer.array(), 0, count , StandardCharsets.UTF_8));
}

【代码解说】

  • 上述代码的最后一行,offset 等于0, 长度是count;
  • 因为如果不使用 count 限定buffer范围的话,打印出来有很多换行。(当然是我的测试案例里是有换行 ,有兴趣的同学可以自己试下);
  • 加了count,限定范围后,就没有换行了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/329393.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

构建高性能.NET应用之配置高可用IIS服务器-第二篇 IIS请求处理模型

在IIS 中&#xff0c;Http监听者(http.sys)和请求处理者由两个系统服务在控制着。一个是WWW 服务&#xff0c;另外一个就是Windows Process Activation。 对于WWW服务&#xff0c;它主要是监控IIS的配置文件&#xff0c;将新的配置信息用到HTTP.sys和WAS上。同时它也维持一些性…

Oracle入门(十四F)之PL/SQL定义变量

一、变量介绍 &#xff08;1&#xff09;变量的使用可以使用变量&#xff1a; 临时存储数据存储值的操作可重用性&#xff08;2&#xff09;PL&#xff0f;SQL中的变量处理变量是&#xff1a; 在声明部分中声明和初始化在可执行部分中使用和分配新值变量可以是&#xff1a;作为…

小米手环nfc门卡摸拟成功后不能开门_如何使用小米手环5 NFC版进行门卡模拟(如公司门禁卡、小区门禁卡、学校门禁卡等)?...

由于本人最近购入了小米手环5 NFC版&#xff0c;所以对小米手环模拟门禁卡比较清楚一点。说一下用该手环模拟门禁的方法吧&#xff0c;我本人模拟的是学校公寓的门禁卡&#xff0c;不过学校的门禁卡是加密卡&#xff0c;可能操作起来稍微比不加密的门禁卡麻烦一点&#xff0c;因…

5.NIO零拷贝与传统IO的文件传输性能比较

【README】 1.本文总结自B站《netty-尚硅谷》&#xff0c;很不错&#xff1b; 2.本文部分内容参考自 NIO效率高的原理之零拷贝与直接内存映射 - 腾讯云开发者社区-腾讯云 【1】零拷贝原理 【1.1】传统IO的文件拷贝 【图解】 step1&#xff09;调用 sys_read系统调用&#…

二进制漏洞利用与挖掘_二进制各种漏洞原理实战分析总结

本部分将对常见的二进制漏洞做系统分析&#xff0c;方便在漏洞挖掘过程中定位识别是什么类型漏洞&#xff0c;工欲善其事&#xff0c;必先利其器。0x01栈溢出漏洞原理栈溢出漏洞属于缓冲区漏洞的一种&#xff0c;实例如下&#xff1a;编译后使用windbg运行直接运行到了地址0x41…

Oracle入门(十四H)之良好的编程实践

一、为什么要学习它 好的编程实践是技巧&#xff0c;可以按照创建最好的代码可能。 编程实践涵盖了一切从代码更多可以用更快的速度创建代码性能。 软件工程团队通常会遵循风格指导让团队中的每个人使用相同的技术。 这使它更容易阅读和修改编写的代码其他。二、编程实践已经学…

微软.NET 正式劈腿成功,横跨所有平台

.NET官方博客宣布了《Announcing .NET Core RC2 and .NET Core SDK Preview 1》&#xff0c;正式如期发布了.NET Core RC2, 现在可以放心的基于.NET Core 构建 ASP.NET Core, console apps 和 class libraries for Windows, OS X and Linux。这里贴张图表达下他们之间的关系: …

2.BIO与NIO区别

【README】 1.本文总结自B站《netty-尚硅谷》&#xff0c;很不错&#xff1b;2.本文介绍 BIO&#xff0c; NIO的知识&#xff1b;【1】BIO&#xff08;传统java IO模型&#xff09; 1&#xff09;BIO-Blocking IO&#xff1a;同步阻塞&#xff0c;服务器实现模式为一个连接一…

k8s往secret里导入证书_K8S之Secret

简介secret顾名思义&#xff0c;用于存储一些敏感的需要加密的数据。这些数据可能是要保存在pod的定义文件或者docker的镜像中。把这些数据通过加密的方式存放到secrets对象中&#xff0c;可以降低信息泄露的风险。在secret中存储的数据都需要通过base64进行转换加密后存放。创…

Oracle入门(十四G)之PL / SQL中检索数据

一、PL / SQL中检索数据 &#xff08;1&#xff09;PL / SQL中的SQL语句可以在PL / SQL中使用以下几种SQL语句&#xff1a;•SELECT从数据库检索数据。•DML语句&#xff0c;例如INSERT&#xff0c;UPDATE和DELETE&#xff0c;以更改数据库中的行。•事务控制语句&#xff0c;例…

.NET Core 1.0 CentOS7 尝试

昨天宣布 ASP.NET Core RC2&#xff0c;据说差不多稳定了&#xff0c;以后不会有大改了。 参考:https://blogs.msdn.microsoft.com/webdev/2016/05/16/announcing-asp-net-core-rc2/ 一、环境装备 等待很久了&#xff0c;高兴之余昨晚安装一个CentOS系统&#xff0c;版本如下&a…

6.netty线程模型-Reactor

【README】 1..本文部分内容翻译自&#xff1a; [Netty] Nettys thread model and simple usage 2.netty模型是以 Reactor模式为基础的&#xff0c;具体的&#xff0c;netty使用的是 主从Reactor多线程模型&#xff1b; 3.先介绍了 Reactor线程模型&#xff1b;后介绍了 Ne…

python mac读取 文件属性_从Python获取和设置mac文件和文件夹查找器标签

macfile模块是^{}模块的一部分&#xff0c;在"2006-11-20 0.2.0"中被重命名为mactypes使用此模块&#xff0c;以下两个函数可用于获取和设置appscript 1.0版的查找器标签&#xff1a;from appscript import appfrom mactypes import File as MacFile# Note these lab…

Oracle入门(十四.1)之PL / SQL简介

一、PL / SQL描述程序语言扩展到SQL&#xff1a; •允许将基本程序逻辑和控制流与SQL语句组合在一起。 •是Oracle专有编程语言。- 它只能用于Oracle数据库或工具。二、程序语言扩展到SQL•是一种程序语言。 - 当遵循一系列指令时会产生结果。 •是3GL&#xff08;第三代编程语…

构建高性能.NET应用之配置高可用IIS服务器-第三篇 IIS中三个核心组件的讲解(上)

今天的文章的比较的容易&#xff0c;主要讲述IIS中三个比较重要的组件&#xff1a;协议监听者(Protocol Listeners)&#xff0c;WWW服务(World Wide Web Publishing Service)和WAS(Windows Process Activation Service)&#xff0c;理解这三个组件的功能&#xff0c;是理解IIS…

7.netty服务器中提交任务到NioEventLoop(Nio事件循环执行线程)

【README】 1.本文总结自 B站 《尚硅谷-netty》; 2.NioEventLoop实际上是一个提交到线程池的Runnable任务&#xff0c;在while无限循环中运行 taskQueue中的任务&#xff08;串行&#xff09;&#xff1b; 【1】提交任务到NioEventLoop 1&#xff09;NioEventLoop&#xff1…

Oracle入门(十四.2)之PL / SQL的好处

一、PL / SQL的好处在Oracle数据库中使用PL / SQL编程语言有很多好处。 1.将过程构造与SQL集成 2.模块化程序开发 3.改进的性能 4.与Oracle工具集成 5.便携性6.异常处理二、优点 优点1&#xff1a;使用SQL集成程序化结构PL / SQL的首要优势是程序结构与SQL的集成。 SQL是一种非…

炒菜机器人的弊端_机器人炒菜真不是你想的那样!

7月8日下午&#xff0c;爱餐全产业链模式发布会在张家口进行&#xff0c;会上&#xff0c;张家口文旅投集团与上海爱餐机器人有限公司签订了3000台智能机器人设备合作协议&#xff0c;这些机器人可是身手不凡&#xff0c;清一色的都是会厨艺的炒菜机器人&#xff0c;未来他们还…

构建高性能.NET应用之配置高可用IIS服务器-第四篇 IIS常见问题之:工作进程回收机制(上)

通过三篇文章的普及&#xff0c;相信大家对IIS应该有了一个基本的了解。那么从本篇文章开始&#xff0c;我们就开始进入IIS一些比较实际的话题&#xff1a;如何配置IIS&#xff0c;使得其性能尽可能的高。 我们在本篇中主要讲述的就是“工作进程回收机制”&#xff0c;下面我们…

8.基于netty实现群聊,心跳检测

【README】 1.本文总结自B站《netty-尚硅谷》&#xff0c;很不错&#xff1b; 2.本文po出了 Unpooled创建缓冲区的 代码示例&#xff1b; 3.本文示例代码基于netty实现以下功能&#xff1a; 群聊客户端及服务器&#xff1b;心跳检测&#xff1b;【1】Unpooled创建缓冲区 U…