【Netty】NIO 网络编程 聊天室案例(一)

简介: 【Netty】NIO 网络编程 聊天室案例(一)

一、 NIO 聊天室需求


1 . NIO 聊天室需求 :



① 服务器 客户端 通信 : 服务器 与 客户端 实现 双向通信 ; 服务器可以写出数据到客户端 , 也能读取客户端的数据 ; 客户端可以写出数据到服务器端 , 也可以读取服务器端的数据 ;


② 多人聊天 : 一个服务器 与 多个客户端 进行数据交互 , 同时还要实现将某一个客户端的数据转发给其它客户端 ;


③ 用户状态监测 : 服务器可以检测用户的 上线 , 离线 状态 ;




2 . 数据传输细节 :



① 上线监听 : 当有客户端连接时 , 服务器检测到用户上线 , 服务器将该用户上线状态通知给其它客户端 ;


② 下线监听 : 如果有客户端离线 , 服务器检测到连接断开 , 服务器将该用户离线的状态通知给聊天室的其它客户端 ;


③ 聊天信息转发 : 客户端发送消息时 , 服务器端接收到该数据 , 并转发给聊天室的其它用户客户端 ;






二、 NIO 聊天室 服务器端 代码分析


服务器端的连接管理流程 : 创建 服务器套接字通道 ( ServerSocketChannel ) , 将该通道注册给 选择器 ( Selector ) , 选择器开启监听 , 监听到客户端连接 , 就创建一个 套接字通道 ( SocketChannel ) , 注册给选择器 ;



服务器端的消息转发流程 : 服务器端收到客户端发送的消息 , 将该消息转发给除该客户端外的其它客户端 , 从选择器中可以获取到所有的 通道 , 注意 屏蔽 服务器套接字通道 和 发送本消息的客户端对应的通道 ;



服务器连接监听 : 当客户端与服务器连接成功 , 即触发注册给 选择器 ( Selector ) 的 服务器套接字通道 ( ServerSocketChannel ) 的 SelectionKey.OP_ACCEPT 事件 , 表示有客户端连接服务器成功 , 用户上线 ;



服务器断开连接监听 : 当服务器端与客户端读写数据出现异常时 , 说明该客户端离线 , 在异常处理代码中可以判定某个客户端离线 ;




1 . 服务器套接字通道 : 调用 open 静态方法创建服务器套接字通道 , 并绑定 8888 端口 , 设置非阻塞网络通信模式 ;


// 创建并配置 服务器套接字通道 ServerSocketChannel
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
serverSocketChannel.configureBlocking(false);



2 . 服务器端选择器 : 调用 open 静态方法获取 选择器 , 注册之前创建的 服务器套接字通道 ;


// 获取选择器, 并注册 服务器套接字通道 ServerSocketChannel
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);



3 . 监听事件 : 阻塞监听, 如果有事件触发, 返回触发的事件个数 ; 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中 ;


// 阻塞监听, 如果有事件触发, 返回触发的事件个数
// 被触发的 SelectionKey 事件都存放到了 Set<SelectionKey> selectedKeys 集合中
// 下面开始遍历上述 selectedKeys 集合
try {
    int eventTriggerCount = selector.select();
} catch (IOException e) {
    e.printStackTrace();
}


4 . 处理客户端连接事件 : 接受客户端连接 , 获取 网络套接字通道 ( SocketChannel ) , 并注册给 选择器 ( Selector ) , 监听 SelectionKey.OP_READ 数据读取事件 ;


// 客户端连接服务器, 服务器端需要执行 accept 操作
if (key.isAcceptable()) {
    //创建通道 : 为该客户端创建一个对应的 SocketChannel 通道
    //不等待 : 当前已经知道有客户端连接服务器, 因此不需要阻塞等待
    //非阻塞方法 : ServerSocketChannel 的 accept() 是非阻塞的方法
    SocketChannel socketChannel = null;
    try {
        socketChannel = serverSocketChannel.accept();
        //如果 ServerSocketChannel 是非阻塞的, 这里的 SocketChannel 也要设置成非阻塞的
        //否则会报 java.nio.channels.IllegalBlockingModeException 异常
        socketChannel.configureBlocking(false);
        //注册通道 : 将 SocketChannel 通道注册给 选择器 ( Selector )
        //关注事件 : 关注事件时读取事件, 服务器端从该通道读取数据
        //关联缓冲区 :
        socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
        System.out.println(String.format("用户 %s 进入聊天室", socketChannel.getRemoteAddress()));
    } catch (IOException e) {
        e.printStackTrace();
    }
}



5 . 处理客户端消息转发事件 :



① 读取客户端上传的数据 : 通过 SelectionKey 获取 通道 和 缓冲区 , 使用 套接字通道 ( SocketChannel ) 读取 缓冲区 ( ByteBuffer ) 中的数据 , 然后记录显示该数据 ;


// 获取 通道 ( Channel ) : 通过 SelectionKey 获取
SocketChannel socketChannel = (SocketChannel) key.channel();
// 获取 缓冲区 ( Buffer ) : 获取到 通道 ( Channel ) 关联的 缓冲区 ( Buffer )
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
String remoteAddress = null;
String message = null;
try {
    // 读取客户端传输的数据
    int readCount = socketChannel.read(byteBuffer);
    byte[] messageBytes = new byte[readCount];
    byteBuffer.flip();
    byteBuffer.get(messageBytes);
    // 处理读取的消息
    message = new String(messageBytes);
    //重置以便下次使用
    byteBuffer.flip();
    remoteAddress = socketChannel.getRemoteAddress().toString();
    System.out.println(String.format("%s : %s", remoteAddress, message));
} catch (IOException e) {
    //e.printStackTrace();
    // 如果此处出现异常, 说明该客户端离线了, 服务器提示, 取消选择器上的注册信息, 关闭通道
    try {
        System.out.println( String.format("%s 用户离线 !", socketChannel.getRemoteAddress()) );
        key.cancel();
        socketChannel.close();
        //继续下一次循环
        continue;
    } catch (IOException ex) {
        ex.printStackTrace();
    }
}



② 转发给其它客户端 : 从 选择器 ( Selector ) 的 keys 集合 中获取所有注册的通道 , 然后除 ServerSocketChannel 和 发送本信息的 客户端对应的 SocketChannel 通道 之外 , 其它所有的通道都转发一份聊天信息 ;


// 向其它客户端转发消息, 发送消息的客户端自己就不用再发送该消息了
// 遍历所有注册到 选择器 Selector 的 SocketChannel
Set<SelectionKey> selectionKeys = selector.keys();
for (SelectionKey selectionKey : selectionKeys) {
    // 获取客户端对应的 套接字通道
    // 这里不能强转成 SocketChannel, 因为这里可能存在 ServerSocketChannel
    Channel channel = selectionKey.channel();
    // 将自己排除在外, 注意这里是地址对比, 就是这两个类不能是同一个地址的类
    // 这个类的类型必须是 SocketChannel, 排除之前注册的 ServerSocketChannel 干扰
    if (socketChannel != channel && channel instanceof SocketChannel) {
        // 将通道转为 SocketChannel, 之后将字符串发送到客户端
        SocketChannel clientSocketChannel = (SocketChannel) channel;
        // 写出字符串到其它客户端
        try {
            clientSocketChannel.write(ByteBuffer.wrap( ( remoteAddress + " : " + message ).getBytes()));
        } catch (IOException e) {
            //e.printStackTrace();
            // 如果此处出现异常, 说明该客户端离线了, 服务器提示, 取消选择器上的注册信息, 关闭通道
            try {
                System.out.println( String.format("%s 用户离线 !", clientSocketChannel.getRemoteAddress()) );
                selectionKey.cancel();
                clientSocketChannel.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
    }
}


目录
相关文章
|
3月前
|
监控 前端开发 安全
Netty 高性能网络编程框架技术详解与实践指南
本文档全面介绍 Netty 高性能网络编程框架的核心概念、架构设计和实践应用。作为 Java 领域最优秀的 NIO 框架之一,Netty 提供了异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。本文将深入探讨其 Reactor 模型、ChannelPipeline、编解码器、内存管理等核心机制,帮助开发者构建高性能的网络应用系统。
258 0
|
7月前
|
消息中间件 缓存 网络协议
Netty基础—4.NIO的使用简介
本文详细介绍了Java NIO(New Input/Output)的核心概念与编程模型。首先,讲解了Buffer缓冲区的作用及4个核心概念:capacity、limit、position、mark,并通过Direct模式创建的Buffer示例展示了其高性能特点。接着,分析了Channel通道的概念,说明其与Buffer的关系以及FileChannel在文件读写中的应用,包括顺序写、随机写和多线程安全特性。 随后,对比了BIO(Blocking IO)编程模型的局限性,如线程资源耗尽问题,引出伪异步IO编程的改进方案,但指出其仍存在级联故障风险。进一步探讨了长连接与短连接的区别及其实现代码。
|
7月前
|
弹性计算 网络协议 Java
Netty基础—2.网络编程基础二
本文介绍了网络编程的基本概念和三种主要模式:BIO(阻塞IO)、AIO(异步IO)和NIO(非阻塞IO)。BIO模型通过为每个客户端连接创建一个线程来处理请求,适合客户端较少的情况,但在高并发下性能较差。AIO模型通过异步IO操作,允许操作系统处理IO,适合高并发场景,但编码复杂且Linux支持有限。NIO模型通过Selector实现多路复用,适合高并发且性能要求高的场景。文章还详细介绍了NIO中的Buffer、Selector、Channel等核心组件,并提供了NIO的实战开发流程和代码示例。
|
7月前
|
监控 网络协议 Java
Netty基础—1.网络编程基础一
本文详细介绍了网络通信的基础知识,涵盖OSI七层模型、TCP/IP协议族及其实现细节。首先解释了OSI模型各层功能,如物理层负责数据通路建立与传输,数据链路层提供无差错传输等。接着探讨了TCP/IP协议,包括TCP和UDP的特点、三次握手与四次挥手过程,以及如何通过确认应答和序列号确保数据可靠性。还分析了HTTP请求的传输流程和报文结构,并讨论了短连接与长连接概念。 此外,解析了Linux下的IO模型,包括阻塞IO、非阻塞IO、IO复用(select/poll/epoll)、信号驱动IO和异步IO的特点与区别,强调了epoll在高并发场景下的优势及其水平触发和边缘触发两种工作模式。
|
10月前
|
缓存 网络协议 Java
JAVA网络IO之NIO/BIO
本文介绍了Java网络编程的基础与历史演进,重点阐述了IO和Socket的概念。Java的IO分为设备和接口两部分,通过流、字节、字符等方式实现与外部的交互。
344 0
|
JSON 算法 Java
Nettyの网络聊天室&扩展序列化算法
通过本文的介绍,我们详细讲解了如何使用Netty构建一个简单的网络聊天室,并扩展序列化算法以提高数据传输效率。Netty的高性能和灵活性使其成为实现各种网络应用的理想选择。希望本文能帮助您更好地理解和使用Netty进行网络编程。
189 12
|
消息中间件 编解码 网络协议
Netty从入门到精通:高性能网络编程的进阶之路
【11月更文挑战第17天】Netty是一个基于Java NIO(Non-blocking I/O)的高性能、异步事件驱动的网络应用框架。使用Netty,开发者可以快速、高效地开发可扩展的网络服务器和客户端程序。本文将带您从Netty的背景、业务场景、功能点、解决问题的关键、底层原理实现,到编写一个详细的Java示例,全面了解Netty,帮助您从入门到精通。
1714 0
|
消息中间件 缓存 Java
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
零拷贝技术 Zero-Copy 是指计算机执行操作时,可以直接从源(如文件或网络套接字)将数据传输到目标缓冲区, 而不需要 CPU 先将数据从某处内存复制到另一个特定区域,从而减少上下文切换以及 CPU 的拷贝时间。
java nio,netty,kafka 中经常提到“零拷贝”到底是什么?
|
存储 机器人 Linux
Netty(二)-服务端网络编程常见网络IO模型讲解
Netty(二)-服务端网络编程常见网络IO模型讲解
Netty BIO/NIO/AIO介绍
Netty BIO/NIO/AIO介绍
223 1