Netty简单聊天室的实现(详细注释)

news2024/11/18 20:01:46

目录

    • 说明
    • 交互逻辑
      • Client
      • Server
    • 代码实现
    • 运行

说明

Netty 的入门练习,使用 Netty 模拟多人聊天室的功能,不考虑高并发,只实现基础聊天功能和提示上下线

交互逻辑

Client

  • 连接服务端成功后,打印本客户端信息Ip:Port
  • 读取用户客户端的键盘输入,发送给服务器,服务器转发给其他客户端

Server

  • 服务端感知到某个客户端上线后,给其他所有客户端发消息提示上线
  • 服务端感知到某个客户端下线后,给其他所有客户端发消息提示下线,统计当前活跃客户端
  • 读取到客户端的消息后,转发给所有客户端

代码实现

在这里插入图片描述

  1. 导包
		<dependency>
			<groupId>io.netty</groupId>
			<artifactId>netty-all</artifactId>
			<version>4.1.35.Final</version>
		</dependency>
  1. 创建ChatServer,用于启动服务端
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

public class ChatServer {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // 加入 编解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            pipeline.addLast("encoder", new StringEncoder());

                            // 加入 自己的业务处理
                            pipeline.addLast(new ChatServerHandler());
                        }
                    });
            System.out.println("聊天室启动...");
            ChannelFuture channelFuture = serverBootstrap.bind(9000).sync();
            channelFuture.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
  1. 创建ChatServerHandler,实现具体业务
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.text.SimpleDateFormat;

/**
 * 用户上线时,打印  [ 客户端 ] xxx 上线了
 * 用户离线时,打印  [ 客户端 ] xxx 下线了
 * 用户发消息时
 *      如果不在自己的窗口,打印  [ 客户端 ] xxx 发送了消息 xxx
 *      如果在自己的窗口,打印   [ 自己 ] 发送了消息 xxx
 */
public class ChatServerHandler extends SimpleChannelInboundHandler<String> {

    //GlobalEventExecutor.INSTANCE 是全局的事件执行器,是一个单例,可以看做 所有客户端的集合
    private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");


    /**
     * 功能:提示上线
     * 实现:当某个客户端上线时(channel 处于就绪状态),给其他所有客户端发送 该客户端上线消息
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();
        //将该客户加入聊天的信息推送给其它在线的客户端
        //该方法会将 channelGroup 中所有的 channel 遍历,并发送消息
        channelGroup.writeAndFlush("[ 客户端 ]" + channel.remoteAddress() + " 上线了 " + sdf.format(new
                java.util.Date()) + "\n");
        //将当前 channel 加入到 channelGroup
        channelGroup.add(channel);
        System.out.println("[ 客户端 ]" + ctx.channel().remoteAddress() + " 上线了 " + sdf.format(new java.util.Date()) + "\n");
    }

    /**
     * 功能:提示下线
     * 实现:当某个客户端下线时(channel 处于非就绪状态),给其他所有客户端发送 该客户端下线消息
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        Channel channel = ctx.channel();
        //将客户离开信息推送给当前在线的客户
        channelGroup.writeAndFlush("[ 客户端 ]" + channel.remoteAddress() + " 下线了" + "\n");
        System.out.println("[ 客户端 ]" + channel.remoteAddress() + " 下线了");
        System.out.println("剩余客户端个数 = " + channelGroup.size() + "\n");
    }

    //读取某个客户端数据,转发给其他客户端
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        //获取到当前 channel
        Channel channel = ctx.channel();
        //这时我们遍历 channelGroup, 根据不同的情况, 回送不同的消息
        channelGroup.forEach(ch -> {
            if (channel != ch) { //不是当前的 channel,转发消息
                ch.writeAndFlush("[ 客户端 ]" + channel.remoteAddress() + " 发送了消息:" + msg + "\n");
            } else {//回显自己发送的消息给自己
                ch.writeAndFlush("[ 自己 ]发送了消息:" + msg + "\n");
            }
        });
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //关闭通道
        ctx.close();
    }

}
  1. 创建ChatClient,启动客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;

import java.util.Scanner;

public class ChatClient {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();

                            // 添加编解码器
                            pipeline.addLast("decoder", new StringDecoder());
                            pipeline.addLast("encoder", new StringEncoder());
                            // 添加自己的业务逻辑
                            pipeline.addLast(new ChatClientHandler());

                        }
                    });
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9000).sync();
            Channel channel = channelFuture.channel();
            // 打印 当前客户端信息
            System.out.println("========" + channel.localAddress() + "========");
            // 从键盘读取 当前客户端 的消息,发给服务端
            Scanner scanner = new Scanner(System.in);
            while (scanner.hasNextLine()) {
                String msg = scanner.nextLine();
                channel.writeAndFlush(msg);
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}
  1. 创建 ChatClientHandler,实现客户端业务逻辑
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) {
        // 客户端收到 服务端消息,去掉尾部回车 后,打印到屏幕
        System.out.println(msg.trim());
    }
}

运行

  1. 先执行 ChatServermain 方法,启动服务端,查看日志
  2. 执行 ChatClientmain 方法,启动第一个客户端,查看日志
  3. 执行 ChatClientmain 方法,启动第二个客户端,查看日志
  4. 选择第一个客户端,发送消息,查看日志
  5. 关闭第二个客户端,查看日志
    在这里插入图片描述

注:如果不能开启多个客户端,可按如下设置
在这里插入图片描述
在这里插入图片描述

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

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

相关文章

链表的经典面试题(数据结构详解)+顺序表和链表之间区别+计算机存储体系

前言 首先这里已经正式步入数据结构的知识&#xff0c;之前我们已经讲解了链表的使用&#xff0c;接下来我们需要的就是大量的练习&#xff0c;熟练掌握数据结构。下面的题型我们选择的都是链表的经典题型&#xff0c;面试题型&#xff0c;包含快慢指针&#xff0c;数形结合&am…

Github的使用教程(下载项目、寻找开源项目和上传项目)

根据『教程』一看就懂&#xff01;Github基础教程_哔哩哔哩_bilibili 整理。 1.项目下载 1&#xff09;直接登录到源码链接页或者通过如下图的搜索 通过编程语言对搜索结果进一步筛选。 如何去找开源项目&#xff1a;(Github 新手够用指南 | 全程演示&个人找项目技巧放…

# 从浅入深 学习 SpringCloud 微服务架构(十二)网关限流算法和 SCG 网关 filter 限流。

从浅入深 学习 SpringCloud 微服务架构&#xff08;十二&#xff09;网关限流算法和 SCG 网关 filter 限流。 一、网关限流算法&#xff1a; 1、网关限流算法&#xff1a;常见的限流算法 网关限流算法&#xff1a;计数器算法网关限流算法&#xff1a;漏桶算法网关限流算法&am…

QT ERROR: Unknown module(s) in QT: xlsx怎么办

现象描述 QT编译c代码的时候&#xff0c;报这种QT ERROR: Unknown module(s) in QT: xlsx&#xff0c;应该如何解决&#xff1f; 这里&#xff0c;我简单记录一下自己的解决问题过程。有可能&#xff0c;对遇到同样的问题的你&#xff0c;也有所帮助 第一步 检查perl是否安装…

墨刀原型工具-小白入门篇

1.引言 作为一个小白&#xff0c;要怎么在短时间内快速学会原型设计&#xff1f; “时间紧&#xff0c;任务重”&#xff0c;如何在短时间内理解、掌握一个原型设计工具的使用&#xff1f;据同事们的推荐&#xff0c;选择了入手“墨刀”这个软件&#xff01; 2.软件介绍 墨…

Colab/PyTorch - 001 PyTorch Basics

Colab/PyTorch - 001 PyTorch Basics 1. 源由2. PyTorch库概览3. 处理过程2.1 数据加载与处理2.2 构建神经网络2.3 模型推断2.4 兼容性 3. 张量介绍3.1 构建张量3.2 访问张量元素3.3 张量元素类型3.4 张量转换&#xff08;NumPy Array&#xff09;3.5 张量运算3.6 CPU v/s GPU …

【测试报告】星光日册

⭐ 作者&#xff1a;Jwenen &#x1f331; 作者主页&#xff1a;Jwenen的个人主页 &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 测试报告 1. 项目介绍2. 测试用例框架3. 自动化测试源码 1. 项目介绍 “星光日册”项目实现了用…

电脑如何改变ip地址到外地

在现今这个网络无处不在的时代&#xff0c;互联网已成为我们日常生活和工作的关键要素。有时&#xff0c;为了追求特定的需求&#xff0c;我们可能需要将电脑的IP地址更改为其他地区的。如果你正身处本地&#xff0c;却对如何为电脑设置外地IP地址感到困惑&#xff0c;那么本文…

【YOLO】目标检测 YOLO框架之train.py参数含义及配置总结手册(全)

1.一直以来想写下基于YOLO开源框架的系列文章&#xff0c;该框架也是日常项目开发中常用的一款工具&#xff0c;最近刚好挤时间梳理、总结下这块儿的知识体系。 2.熟悉、梳理、总结下YOLO目标检测相关知识体系&#xff0c;之前实战配置时总是临时性检索些注释含义&#xff0c;但…

【算法-程序的灵魂#谭浩强配套】(适合专升本、考研)

无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 无偿分享学习资料&#xff0c;需要的小伙伴评论区或私信dd。。。 完整资料如下&#xff1a; 1.一个程序主要包括以下两方面信息&#xff1a;程…

Golang | Leetcode Golang题解之第75题颜色分类

题目&#xff1a; 题解&#xff1a; func sortColors(nums []int) {p0, p2 : 0, len(nums)-1for i : 0; i < p2; i {for ; i < p2 && nums[i] 2; p2-- {nums[i], nums[p2] nums[p2], nums[i]}if nums[i] 0 {nums[i], nums[p0] nums[p0], nums[i]p0}} }

Pandas数据取值与选择

文章目录 第1关&#xff1a;Series数据选择第2关&#xff1a;DataFrame数据选择方法 第1关&#xff1a;Series数据选择 编程要求 本关的编程任务是补全右侧上部代码编辑区内的相应代码&#xff0c;要求实现如下功能&#xff1a; 添加一行数据&#xff0c;时间戳2019-01-29值为…

蓝牙 | 软件:Git管理高通的ChipCode项目

哈喽大家好&#xff0c;最近发现大家在高通chipcode网站上下载不了代码&#xff0c;小编一直使用git的方式获取新版本代码&#xff0c;没有遇到什么阻碍。于是小编到新主机上尝试下载代码的压缩包和git代码&#xff0c;都遇到了问题。由于压缩包是高通自己处理卡住了&#xff0…

SpringCloud微服务之Eureka、Ribbon、Nacos详解

SpringCloud微服务之Eureka、Ribbon、Nacos详解 1、认识微服务1.1、单体架构1.2、分布式架构1.3、微服务1.4、SpringCloud 2、服务拆分与远程调用2.1、服务拆分的原则2.2、服务拆分示例2.2、提供者与消费者 3、Eureka注册中心3.1、Eureka的结构和作用3.2、搭建eureka-server3.2…

如何把音乐的原声调小?调整音频大小的方法你知道吗?

调节音频声音的大小是一项常见的音频处理任务&#xff0c;无论是在家庭娱乐、工作学习还是专业音频制作中&#xff0c;都是必不可少的技能。通过调节音频声音的大小&#xff0c;我们可以更好地控制音频的质量和听感&#xff0c;以满足不同的需求和环境。首先&#xff0c;我们需…

防火墙技术基础篇:什么是包过滤技术

什么是防火墙包过滤技术 当数据在网络中传输时&#xff0c;它们被分割成小的单元&#xff0c;称为数据包。防火墙的包过滤是一种基本的网络安全技术&#xff0c;用于检查这些数据包并根据预定义的规则决定是否允许它们通过防火墙。 防火墙包过滤是一种关键的网络安全技术&am…

ENVI拓展工具资源去哪里找

ENVI拓展工具资源去哪里找&#xff1f; 文章目录 ENVI拓展工具资源去哪里找&#xff1f;前言网站&#xff08;链接见文末&#xff09;ENVI应用商店&#xff08;App Store&#xff09;ENVI官方提供第三方制作自己制作 总结参考 前言 ENVI 拓展工具是指 ENVI 软件的扩展功能或插…

【微信小程序开发】微信小程序、大前端之flex布局方式详细解析

✨✨ 欢迎大家来到景天科技苑✨✨ &#x1f388;&#x1f388; 养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; &#x1f3c6; 作者简介&#xff1a;景天科技苑 &#x1f3c6;《头衔》&#xff1a;大厂架构师&#xff0c;华为云开发者社区专家博主&#xff0c;…

力扣HOT100 - 35. 搜索插入位置

解题思路&#xff1a; 二分法模板 class Solution {public int searchInsert(int[] nums, int target) {int left 0;int right nums.length - 1;while (left < right) {int mid left ((right - left) >> 1);if (nums[mid] target)return mid;else if (nums[mid…

自动化脚本如何有效防检测、防风控?

我们在编写自动化脚本过程中经常会出现被app检测到&#xff0c;有的甚至会被风控&#xff0c;遇到这类问题如何有效应对呢&#xff1f;本人长期从事自动化脚本领域工作&#xff0c;从早期的按键精灵&#xff0c;到autojs&#xff0c;到现代的冰狐智能辅助&#xff0c;积累的不少…