Netty学习——实战篇2 NIO 群聊系统(简单版) 备份

         需求:

        1、编写一个NIO群聊系统,实现服务端和客户端之间数据简单通讯(非阻塞)

        2、实现多人群聊

        3、服务端:可以监测用户上线、离线、并实现消息转发功能。

        4、客户端:通过channel可以无阻塞发送消息给其他所有用户,同时可以接受其他用户发送的消息。

        5、目的:进一步理解NIO非阻塞网络编程机制

        服务端代码:GroupChatServer.java

@Slf4j
public class GroupChatServer {
    //选择器
    private Selector selector;
    //监听器
    private ServerSocketChannel serverSocketChannel;
    //端口号
    private static final  int PORT = 8000;
    //构造方法,初始化成员变量
    public GroupChatServer(){
        try {
            //1 创建监听器
            serverSocketChannel = ServerSocketChannel.open();
            //2 创建选择器
            selector = Selector.open();
            //3 绑定端口号
            serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
            //4 设置非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //5 事件绑定
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        }catch (Exception e){
            e.printStackTrace();
        }

    }

    //监听
    public void listen(){
        log.info("监听的线程号是:{}",Thread.currentThread().getId());
        try {
            //循环等待客户端连接
            while(true){
                int count  = selector.select();
                if(count > 0){
                    //表示有客户端连接
                    //遍历得到selectorKey
                    Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                    while(iterator.hasNext()){
                        SelectionKey key = iterator.next();
                        //判断事件监听类型
                        if(key.isAcceptable()){
                            /*//事件:连接
                            SocketChannel socketChannel = serverSocketChannel.accept();
                            //设置为非阻塞
                            socketChannel.configureBlocking(false);
                            //把socketchannel的读取事件类型注册到选择器上
                            socketChannel.register(selector,SelectionKey.OP_READ);
                            //提示用户上线
                            log.info("用户,{}",socketChannel.getRemoteAddress(),"已上线");*/
                            accept(serverSocketChannel);
                        }
                        if(key.isReadable()){
                            readClientMessage(key);
                        }
                        iterator.remove();
                    }
                }else{
                    log.info("等待客户端连接");
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {

        }

    }

    //客户端连接事件
    public void accept(ServerSocketChannel serverSocketChannel) throws Exception{
        //获取SocketChannel
        SocketChannel socketChannel = serverSocketChannel.accept();
        //设置SocketChannel 为非阻塞模式
        socketChannel.configureBlocking(false);
        //把读取事件绑定到选择器上
        socketChannel.register(selector,SelectionKey.OP_READ);
        //提示用户上线
        log.info("用户,{},已上线",socketChannel.getRemoteAddress());
    }

    //读取客户端信息
    public void readClientMessage(SelectionKey key){
        SocketChannel socketChannel = null;
        try {
            //根据key获取SocketChannel
            socketChannel = (SocketChannel)key.channel();
            //创建ByteBuffer
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            //channel 读取 buffer
            int count = socketChannel.read(buffer);
            //根据count的值做处理
            if(count > 0) {
                //把缓冲区的数据转换成字符串
                String msg = new String(buffer.array());
                //输出该消息
                log.info("来自客户端:{}, 的消息是:{}",socketChannel.getRemoteAddress(),msg);
                //向其他客户端转发消息(排除自己)
                sendMessageToOtherClients(msg,socketChannel);
            }


        }catch (Exception e){
            try {
                //提示离线
                log.info("{}",socketChannel.getRemoteAddress(),":已下线");
                //取消注册
                key.cancel();
                //关闭通道
                socketChannel.close();
            }catch (Exception e2){
                e2.printStackTrace();
            }
        }
    }

    public void sendMessageToOtherClients(String message,SocketChannel socketChannel) throws Exception{
        log.info("消息转发中。。。");
        log.info("服务器发送数据给客户端的线程是:{}",Thread.currentThread().getId());
        //遍历所有注册到selector的SocketChannel,排除自己
        for(SelectionKey key : selector.keys()){
            //通过key取出对应的SocketChannel
            Channel targetChannel =  key.channel();
            //排除自己
            if(targetChannel instanceof SocketChannel &&  targetChannel != socketChannel){
                SocketChannel dest = (SocketChannel) targetChannel;
                //把消息存储到ByteBuffer
                ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
                //把buffer的数据写入通道
                dest.write(buffer);
            }

        }


    }

    public static void main(String[] args) {
        GroupChatServer server = new GroupChatServer();
        server.listen();
    }

}

        客户端 GroupChatClient.java

@Slf4j
public class GroupChatClient {
    //服务器IP
    private static final String HOST = "127.0.0.1";
    //服务器端口号
    private static final int PORT = 8000;
    //选择器
    private Selector selector;
    //SocketChannel
    private SocketChannel socketChannel;
    //用户名
    private String username;
    //构造器
    public GroupChatClient() throws Exception{
        //获取selector
        selector = Selector.open();

        //连接到服务器
        socketChannel = socketChannel.open(new InetSocketAddress("127.0.0.1", PORT));
        //设置为非阻塞
        socketChannel.configureBlocking(false);
        //将channel的读事件注册到selector
        socketChannel.register(selector, SelectionKey.OP_READ);
        //初始化username
        username = socketChannel.getLocalAddress().toString().substring(1);
        log.info("{},客户端初始化完成",username);
    }

    //向服务器发送消息
    public void sendMessage(String message){
        message = username + " 说:" + message;
        try {
            socketChannel.write(ByteBuffer.wrap(message.getBytes()));
            //log.info("用户:{},说:{}",username,message);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //读取从服务器获取的消息
    public void readMessage(){
        try {
            //获取socketChannel的通道数量
            int count = selector.select();
            //如果通道数量大于0,说明有可用的通道
            if(count > 0){
                //获取所有通道的迭代器
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                //循环判断
                while(iterator.hasNext()){
                    //获取key
                    SelectionKey key = iterator.next();
                    //如果事件类型是读取类型
                    if(key.isReadable()){
                        //获取相关通道
                        SocketChannel channel = (SocketChannel) key.channel();
                        //创建Bytebuffer
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        //读取buffer
                        channel.read(buffer);
                        //把读取到缓冲区的数据转换成字符串,并输出
                        String message = new String(buffer.array());
                        log.info("{}",message);
                    }

                }
                //删除当前的SelectionKey,方法重复注册
                iterator.remove();

            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws Exception{
        //启动客户端
        GroupChatClient chatClient = new GroupChatClient();
        //启动一个线程,每隔3秒,从服务器读取数据
        new Thread(){
            public void run(){
                while(true){
                    chatClient.readMessage();
                    try {
                        Thread.currentThread().sleep(3000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }.start();
        //发送数据到服务端
        Scanner scanner = new Scanner(System.in);
        while(scanner.hasNextLine()){
            String s = scanner.nextLine();
            chatClient.sendMessage(s);
        }
    }
}

        服务端运行结果:

        客户端1运行结果:

 

        客户端2运行结果:

 

        客户端3运行结果:

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【位运算】3097. 或值至少为 K 的最短子数组 II

本文涉及知识点 位运算 LeetCode3097. 或值至少为 K 的最短子数组 II 给你一个 非负 整数数组 nums 和一个整数 k 。 如果一个数组中所有元素的按位或运算 OR 的值 至少 为 k &#xff0c;那么我们称这个数组是 特别的 。 请你返回 nums 中 最短特别非空 子数组 的长度&…

AI大模型语言开源大语言模型完整列表

开源大语言模型完整列表 Large Language Model (LLM) 即大规模语言模型&#xff0c;是一种基于深度学习的自然语言处理模型&#xff0c;它能够学习到自然语言的语法和语义&#xff0c;从而可以生成人类可读的文本。 所谓"语言模型"&#xff0c;就是只用来处理语言文…

游戏开发者必看:Perforce Helix Core 的功能特点及游戏开发中的常用工具、典型用例介绍

「不出海&#xff0c;即出局」随着全球化的加速发展&#xff0c;企业出海已成燎原之势。日前&#xff0c;2024 亚马逊云科技出海全球化论坛在深圳成功举办。龙智携手 Perforce 亮相游戏行业展区&#xff0c;展示了Perforce Helix Core如何与主流游戏开发引擎高效集成&#xff0…

关于《CS创世 SD NAND》的技术学习分享

最近发现一个好玩的东西《CS创世 SD NAND》&#xff0c;带大家一起体验一下。 本文引用了部分厂家产品资料及图像&#xff0c;如有侵权&#xff0c;请及时联系我删除&#xff0c;谢谢。 《CS创世 SD NAND》官方网站&#xff1a;http://www.longsto.com/ 什么是CS创世 SD NAND呢…

c++的学习之路:4、入门(3)

摘要 本章将介绍一下auto、for和指针空值&#xff0c;文章末附上入门的所有代码。 目录 摘要 一、auto 二、for 三、指针空值 四、代码 五、思维导图 一、auto 这个关键字是c提出的&#xff0c;可以自动识别变量的类型&#xff0c;可以看出下方图片&#xff0c;auto自…

【研发日记】Matlab/Simulink软件优化(一)——动态内存负荷压缩

文章目录 背景介绍 初始代码 优化代码 分析和应用 总结 背景介绍 在一个嵌入式软件开发项目中&#xff0c;有一个使用MATLAB Function编写的算法模块&#xff0c;功能是从一个较大的数组中提取一段数据&#xff0c;然后求均值输出&#xff0c;示例如下&#xff1a; 初始代…

OpenHarmony实战开发-如何实现图片缩放效果。

介绍 图片预览在应用开发中是一种常见场景&#xff0c;在诸如QQ、微信、微博等应用中均被广泛使用。本模块基于Image组件实现了简单的图片预览功能。 使用说明&#xff1a; 双指捏合对图片进行缩放双击图片进行图片的大小切换&#xff0c;在放大状态下&#xff0c;双击可恢复…

腾讯EdgeOne产品测评体验——多重攻击实战验证安全壁垒:DDoS攻击|CC压测|Web漏洞扫描|SQL注入

腾讯EdgeOne产品测评体验——实战验证安全壁垒&#xff1a;DDoS攻击|CC压测|Web漏洞扫描|SQL注入 写在最前面一、产品概述1.1 什么是边缘安全加速平台 EO&#xff1f;1.2 EdgeOne产品功能 二、准备工作2.1 选择&#xff1a;NS&#xff08;Name Server&#xff09;接入模式或 CN…

【架构-13】云原生架构

云原生架构产生背景&#xff1f; &#xff08;1&#xff09;大量资源被占用且难以分享&#xff0c;上云后&#xff0c;云厂商提供统一的IaaS能力和云服务。 &#xff08;2&#xff09;提供极致性能的云原生算力。 &#xff08;3&#xff09;集成服务&#xff0c;构建管理数据、…

如何在CentOS本地部署FastDFS文件系统并实现无公网IP远程上传下载内网文件

文章目录 前言1. 本地搭建FastDFS文件系统1.1 环境安装1.2 安装libfastcommon1.3 安装FastDFS1.4 配置Tracker1.5 配置Storage1.6 测试上传下载1.7 与Nginx整合1.8 安装Nginx1.9 配置Nginx 2. 局域网测试访问FastDFS3. 安装cpolar内网穿透4. 配置公网访问地址5. 固定公网地址5.…

C语言学习笔记之操作符篇

目录 算术运算符 移位操作符 整型在内存中的存储&#xff08;补充知识&#xff09; ​编辑左移操作符 右移操作符 位操作符 赋值操作符 复合赋值操作符 单目操作符 关系操作符 逻辑操作符 && 与 || 的计算特点 条件操作符 逗号表达式 下标引用操作符 函…

负载均衡(理解/解析)

目录 什么是负载均衡 应用场景 网络服务和应用&#xff1a; 云计算和虚拟化&#xff1a; 负载均衡分类 硬件负载均衡器 软件负载均衡器 部署方式 硬件部署&#xff1a; 软件部署&#xff1a; 云部署&#xff1a; 路由模式&#xff1a; 算法实现 轮询法&#xff08;Round R…

MacOS - 程序坞,但图标消失不见了 但是还能用

如图 强迫症难受死 重启什么的都尝试了。不好使&#xff01; 差点重装系统。 经验证 改名字可以修复。 但是系统的比如启动台 也显示不出来 全网好使的方案 在“应用程序”中打开“终端” 输入命令如下&#xff1a;&#xff08;注意&#xff1a;需要 sudo 权限&#xff0…

【CLR】《Cyclical Learning Rates for Training Neural Networks》

WACV-2017 IEEE Winter Conference on Applications of Computer Vision 文章目录 1 Background and Motivation2 Related Work3 Advantages / Contributions4 Method5 Experiments5.1 Datasets and Metrics5.2 CIFAR-10 and CIFAR-1005.3 ImageNet 6 Conclusion&#xff08;o…

Unity 中消息提醒框

Tooltip 用于ui布局 using System.Collections; using System.Collections.Generic; using UnityEngine; using TMPro; using UnityEngine.UI;[ExecuteInEditMode()] // 可以在编辑模式下运行public class Tooltip : MonoBehaviour {public TMP_Text header; // 头部文本publi…

✌粤嵌—2024/3/11—跳跃游戏

代码实现&#xff1a; 方法一&#xff1a;递归记忆化 int path; int used[10000];bool dfs(int *nums, int numsSize) {if (path numsSize - 1) {return true;}for (int i 1; i < nums[path]; i) {if (used[path i]) {continue;}path i;used[path] 1;if (dfs(nums, num…

ZYNQ之嵌入式开发03——按键中断实验

文章目录 按键中断控制LED的状态AXI GPIO实现按键中断使用多个AXI GPIO实现按键中断 GPIO的简图如下图所示。 GPIO对应的中断ID是52。 按键中断控制LED的状态 前面实验中已经做了按键控制LED状态的实验&#xff0c;但是LED的状态分为按键按下时和按键松开时的两种状态&…

00_STM32CubeMX如何新建一个工程

STM32CubeMX如何新建一个工程 STM32CubeMX如何新建一个工程以使用PA1口点亮LED为例子 STM32CubeMX如何新建一个工程 以使用PA1口点亮LED为例子 1.创建一个新工程 2.搜索芯片&#xff0c;然后双击 3.点击PA1引脚&#xff0c;设置为输出口 4.文件一定要保存到英文路径&#xff…

4. WPF应用程序中的未捕获异常处理

文章目录 一. 目标二. 技能介绍① UI未捕获异常的处理方式② 全局程序域抛出的未处理异常的捕获③ 异步Task任务中的异常捕获 三. 总结 一. 目标 理解和使用UI未捕获异常DispatcherUnhandledException的使用方法和触发方式.理解和使用程序域未捕获异常AppDomain.CurrentDomain.…

vs2022断点调试怎么看堆栈帧,找异常位置

打一个断点以后&#xff0c;会出现如图报错 我们要怎么找到报错的语句&#xff1f;鼠标点击->堆栈帧->上一行运行的位置->直到找到错误出错如图所示&#xff1a; 跳转到&#xff0c;我们手写的代码&#xff0c;执行出错的位置