一、浅谈NIO

1. 什么是NIO?

​​Java NIO​​:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有 ​​I/O​​ 请求就进行处理。【简单示意图】

手写的服务端,是利用多路复用的技术处理多个客户端的,类似于redis的单线程多路复用处理,有什么好处?比传统的BIO(Blocking I/O)处理速度更快,传统的BIO处理一个客户端,服务端得启动一个线程去处理,这个线程没法关心处理其他客户端,是1对1模式。
 
NIO(non-Blocking I/O)是一个线程处理多个客户端,相比BIO减少了对线程的开销,使用NIO开发群聊系统使用的主要Java类

SelectionKey

  1. SelectionKey,表示Selector和网络通道的注册关系,共四种:
  • int OP_ACCEPT:有新的网络连接可以 accept,值为 16
  • int OP_CONNECT:代表连接已经建立,值为 8
  • int OP_READ:代表读操作,值为 1
  • int OP_WRITE​​​:代表写操作,值为 ​​​4​​
  1. SelectionKey 相关方法

ServerSocketChannel

  1. ServerSocketChannel 在服务器端监听新的客户端 Socket 连接,负责监听,不负责实际的读写操作
  2. 相关方法如下

SocketChannel

  1. SocketChannel,网络 IO 通道,具体负责进行读写操作NIO 把缓冲区的数据写入通道,或者把通道里的数据读到缓冲区。
  2. 相关方法如下

二、群聊系统开发

1. 原理理解模型

服务端:

package com.fyp.nio;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;/*** @Auther: fyp* @Date: 2022/2/5* @Description: NIO服务器* @Package: com.fyp.nio* @Version: 1.0*/
public class NIOServer {public static void main(String[] args) throws IOException {//创建ServerSocketChannel -> ServerSocketServerSocketChannel serverSocketChannel = ServerSocketChannel.open();//得到一个Selector对象Selector selector = Selector.open();//绑定一个端口6666,在服务端监听serverSocketChannel.socket().bind(new InetSocketAddress(6666));//设置为非阻塞serverSocketChannel.configureBlocking(false);//把 serverSocketChannel 注册到 selector, 关注事件 为 OP_ACCEPTserverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//循环等待客户端连接while (true) {//这里我们等待1秒,如果没有事件发生(连接事件)if (selector.select(1000) == 0) {//没有事件发生System.out.println("服务器等待了1秒,无连接");continue;}//如果返回的 > 0, 就获取到相关的 selectionKey集合//1. 如果返回的 > 0, 表示已经获取到关注的事件//2. selector.selectedKeys() 返回关注事件的集合//通过 selectionKeys 反向获取通道Set<SelectionKey> selectionKeys = selector.selectedKeys();//遍历 Set<SelectionKey>, 使用迭代器Iterator<SelectionKey> keyIterator = selectionKeys.iterator();while (keyIterator.hasNext()) {//获取到selectionKeySelectionKey key = keyIterator.next();//根据key 对应的通道发生的事件做相应处理if (key.isAcceptable()) { //如果是OP_ACCEPT, 表示新的客户端连接//给该客户端生成一个 SocketChannelSocketChannel socketChannel = serverSocketChannel.accept();System.out.println("客户端连接成功!生成了一个socketChannel " + socketChannel.hashCode());//将 SocketChannel 设置为非阻塞socketChannel.configureBlocking(false);//将socketChannel注册到 selector上, 关注事件为OP_READ, 同时给socketChannel//关联一个BuffersocketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));System.out.println("客户端连接后 ,注册的selectionkey 数量=" + selector.keys().size());}if (key.isReadable()) { //发生OP_READ// 通过key 反向获取对应的channelSocketChannel channel = (SocketChannel) key.channel();//获取该channel关联的 buffer,在与客户端连接就已经创建好了ByteBuffer buffer = (ByteBuffer) key.attachment();channel.read(buffer);System.out.println("from 客户端: " + new String(buffer.array()));}//手动从集合中移动当前的selectionKey, 防止重复操作keyIterator.remove();}}}
}

客户端:

package com.fyp.nio;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;/*** @Auther: fyp* @Date: 2022/2/6* @Description: 客户端* @Package: com.fyp.nio* @Version: 1.0*/
public class NIOClient {public static void main(String[] args) throws IOException {//得到一个网络通道SocketChannel socketChannel = SocketChannel.open();//设置非阻塞socketChannel.configureBlocking(false);//提供服务端的 ip 和 端口InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);/*连接服务器1. 为非阻塞模式时,即不会等到方法执行完毕再返回,会立即返回,如果返回前已经连接成功,则返回true返回false 时,说明未连接成功,所以需要再通过while循环地finishConnect()完成最终的连接2. 为阻塞模式时,直到连接建立或抛出异常不会返回false,连接不上就抛异常,不需要借助finishConnect()*/if (!socketChannel.connect(inetSocketAddress)) {while (!socketChannel.finishConnect()) {System.out.println("因为连接需要时间,客户端不会阻塞,可以做其他工作");}}//如果连接成功,就发送数据String str = "hello, 尚硅谷";ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());//发送数据,将buffer 写入 channelsocketChannel.write(buffer);System.in.read();}
}

2. 开发模型

实现要求:

  1. 服务器端:可以监测用户上线,离线,并实现消息转发功能
  2. 客户端:通过 Channel 可以无阻塞发送消息给其它所有用户,同时可以接受其它用户发送的消息(由服务器转发得到)

服务端:

package com.fyp.nio.groupchat;import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;/*** @Auther: fyp* @Date: 2022/2/6* @Description: 群聊系统服务端* @Package: com.fyp.nio.groupchat* @Version: 1.0*/
public class GroupChatServer {private Selector selector;private ServerSocketChannel listenChannel;private static final int PORT = 6667;public GroupChatServer() {try {selector = Selector.open();listenChannel = ServerSocketChannel.open();listenChannel.socket().bind(new InetSocketAddress(PORT));listenChannel.configureBlocking(false);listenChannel.register(selector, SelectionKey.OP_ACCEPT);} catch (IOException e) {e.printStackTrace();}}public void listen() {try {while (true) {// count 获取的是 在阻塞过程中 同时发生的 事件 数,直到有事件 发生,才会执行,否则一直阻塞/*select 方法在 没有 客户端发起连接时, 会一直阻塞,至少有一个客户端连接,其他 客户端再 发起连接 不再阻塞,会立即返回*/int count = selector.select();System.out.println(count);if(count > 0) {// 有事件 处理// 遍历得到 SelectionKey 集合Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {//取出SelectionKeySelectionKey key = iterator.next();//监听到acceptif (key.isAcceptable()) {SocketChannel sc = listenChannel.accept();// 监听到 客户端 的 SocketChannel 总是默认为阻塞方式,需要重新设置sc.configureBlocking(false);//将该 sc 注册到 selectorsc.register(selector, SelectionKey.OP_READ);System.out.println(sc.getRemoteAddress() + " 上线 ");}if (key.isReadable()) { // 通道发送 read 事件, 即通道是可读状态//处理读readData(key);}/*每次  监听到 客户端后, selector会将 连接上的 客户端 选中, 并添加到 selectionKeys 中要注册到 selector 上,使用该方法,selector 将 不再选中如果没有移除,selector 不能选中 其他的 客户端连接iterator.remove() 移除后,将释放 selector 中的 selectionKeys*/iterator.remove();}} else {//System.out.println("等待....");}}} catch (IOException e) {e.printStackTrace();} finally {// 发送异常处理}}public void readData(SelectionKey key) {// 取到关联的 channelSocketChannel 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());// 输出该消息System.out.println("from 客户端: " + msg);//想其他客户端转发消息,专门写一个方法来处理sendInfoToOtherClients(msg, channel);}} catch (IOException e) {//e.printStackTrace();try {System.out.println(channel.getRemoteAddress() + "离线了");// 取消 注册key.cancel();// 关闭通道channel.close();} catch (IOException ioException) {ioException.printStackTrace();}}}// 转发消息给其他客户端(通道)private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {System.out.println("服务器转发消息中....");// 遍历所有 注册到 selector 上 的 SockChannel, 并排除 selffor (SelectionKey key : selector.keys()) {// 通过 key 取出 对应的 SocketChannelChannel targetChannel = key.channel();// 排除自己if (targetChannel instanceof SocketChannel && targetChannel != self) {// 转型SocketChannel dest = (SocketChannel) targetChannel;// 将 msg 存储到 bufferByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());// 将 buffer 的数据 写入 通道dest.write(buffer);}}}public static void main(String[] args) {// 创建 服务器 对象GroupChatServer groupChatServer = new GroupChatServer();groupChatServer.listen();}
}

客户端:

package com.fyp.nio.groupchat;import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;/*** @Auther: fyp* @Date: 2022/2/7* @Description: 群聊系统客户端* @Package: com.fyp.nio.groupchat* @Version: 1.0*/
public class GroupChatClient {//定义 相关 属性private final String HOST = "127.0.0.1";private final int PORT = 6667;private Selector selector;private SocketChannel socketChannel;private String username;// 构造器,完成初始化工作public GroupChatClient() throws IOException {selector = Selector.open();// 连接 服务器socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));// 设置 非阻塞socketChannel.configureBlocking(false);// 将 channel 注册到 selectorsocketChannel.register(selector, SelectionKey.OP_READ);// 得到 usernameusername = socketChannel.getLocalAddress().toString().substring(1);System.out.println(username + " is ok....");}// 向 服务器 发送 消息public void sendInfo(String info) {info = username + " 说:" + info;try {socketChannel.write(ByteBuffer.wrap(info.getBytes()));} catch (Exception e) {}}// 读取从 服务器 端 回复的 消息public void readInfo() {try {int readChannels = selector.select();if (readChannels > 0) {// 有可以用的 通道Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isReadable()) {// 得到 相关的 通道SocketChannel sc = (SocketChannel) key.channel();// 得到一个 BufferByteBuffer buffer = ByteBuffer.allocate(1024);// 读取sc.read(buffer);// 把缓存区的数据 转成 字符串String msg = new String(buffer.array());System.out.println(msg.trim());}}iterator.remove();// 删除当前的selectionKey, 防止重复操作} else {//System.out.println("没有可以用的通道....");}} catch (IOException e) {e.printStackTrace();}}public static void main(String[] args) throws IOException {// 启动 客户端GroupChatClient chatClient = new GroupChatClient();// 启动一个线程,每隔3秒, 读取从 服务器 发送过来的数据new Thread() {@Overridepublic void run() {while (true) {chatClient.readInfo();try {Thread.currentThread().sleep(3000);} catch (Exception e) {e.printStackTrace();}}}}.start();// 发送数据给 服务端Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()) {String s = scanner.nextLine();chatClient.sendInfo(s);}}}

三、结束语

评论区可留言,可私信,可互相交流学习,共同进步,欢迎各位给出意见或评价,本人致力于做到优质文章,希望能有幸拜读各位的建议!

专注品质,热爱生活。
交流技术,奢求同志。
—— 嗝屁小孩纸 QQ:1160886967

手写一个NIO群聊系统相关推荐

  1. Java NIO群聊系统

    实例要求: 1.编写一个 NIO 群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞) 2.实现多人群聊 3.服务器端:可以监测用户上线,离线,并实现消息转发功能 4.客户端:通过 Channe ...

  2. Java NIO网络编程之群聊系统

    概述 对ServerSocketChannel.SocketChannel.SelectionKey有一定的理解和了解对应API. NIO非阻塞网络编程相关关系梳理: 以下概念: ServerSock ...

  3. Netty学习笔记:二、NIO网络应用实例-群聊系统

    实例要求: 编写一个NIO群聊系统,实现服务器端和多个客户端之间的数据简单通讯(非阻塞): 实现多人群聊: 服务器端:可以监测用户上线.离线,并实现消息转发功能: 客户端:通过channel可以无阻塞 ...

  4. 4.基于NIO的群聊系统

    [README] 1.本文总结自B站<netty-尚硅谷>,很不错: 2.文末有错误及解决方法: [1]群聊需求 1)编写一个 NIO 群聊系统,实现服务器端和客户端之间的数据简单通讯(非 ...

  5. (六)Netty网络编程应用实例-群聊系统

    实例要求: 编写一个NIO群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞) 实现多人群聊 服务器端:可以监测用户上线,离线,并实现消息转发功能 客户端:通过channel可以无阻塞发送消息给 ...

  6. Netty之实现一个简单的群聊系统

    要求 编写一个 Netty 群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞) 实现多人群聊 服务器端:可以监测用户上线,离线,并实现消息转发功能 客户端:通过channel可以无阻塞发送消息 ...

  7. Java基础之《netty(18)—群聊系统》

    一.实例要求 1.编写一个netty群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞) 2.实现多人群聊 3.服务器端:可以检测用户上线,离线,并实现消息转发功能 4.客户端:通过channe ...

  8. Netty基础,Netty实现群聊系统

    NIO群聊系统 这里面的知识比较全面,用到了我们之前学习的三大组件,首先我先来给大家介绍本系统的功能 服务端功能 最基本的当然是注册功能,也就是将serverSocketChannel注册进Selec ...

  9. Java NIO 应用案例:实现一个简单的群聊系统

    1 案例要求 编写一个 NIO 多人群聊系统,实现服务器端和客户端之间的数据简单通讯(非阻塞): 服务器端功能: 监测用户上线,离线: 实现客户端消息的转发功能(将该客户端的消息转发给其它客户端): ...

最新文章

  1. 量子物理 詹班 计算机,6量子物理作业答案
  2. H264和AAC合成FLV案例
  3. 使用Vert.x进行响应式开发
  4. ZZULIOJ 1088: 手机短号 (多实例)
  5. Java基础-SSM之mybatis快速入门篇
  6. centos下安装teamview
  7. PowerDesigner下载、安装配置
  8. 唐峻:互联网内容产业永远有机会
  9. h264 i p 帧特点
  10. (转载)32个Python爬虫项目
  11. 如何修改pdf文件中文字的大小及颜色
  12. 如何用vb播放幻灯片
  13. CSS —— 手摸手实现一个文字霓虹灯闪烁特效
  14. 机器人xacro设计+gazebo/rviz启动
  15. JavaScript的三个主要组成部分
  16. 烟草企业客户关系管理现状及解决途径
  17. kafka远离_在远离社会的时代如何保持生产力
  18. 18条心理学定律,值得一看
  19. opencv基础1,图片与矩阵
  20. IntelliJ IDEA快捷键大全

热门文章

  1. t-SNE:可视化效果最好的降维算法
  2. 当迪士尼遇上大数据和机器学习,奇妙的体验之旅开始了
  3. java群发图文消息_java微信群发图文消息 java总结_图文.doc
  4. WinCDEmu-好用的iso制作和模拟软件
  5. 同为(TOWE)远程智能防雷预警监测——交直流遥信防雷配电柜
  6. 2022-2028全球及中国户外烟灰缸行业研究及十四五规划分析报告
  7. 常用数据分析指标和术语
  8. 【python】对于try...except的用法
  9. 电脑技巧:分享浏览器几个小技巧,太实用了
  10. 如何选择最佳的相机参数以实现最佳图像质量