基于Netty实现聊天室

news2024/11/27 9:20:13

前言

了解了Netty的基本功能和相关概念,使用基于Netty实现多人聊天的功能。

需求

1.服务端能够接收客户端的注册,并且接受用户的信息注册

2.服务端能够处理客户端发送的消息,并且根据消息类型进行私发或者广播发送消

3.服务端能够私发消息和广播消息

代码实现

环境

java版本:JDK17
netty版本: 4.1.111.Final

服务端

import com.fftf.netty.handler.ChatServerHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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 ChatRoomServer {
    private final int port;

    public ChatRoomServer(int port) {
        this.port = port;
    }

    public void run() throws Exception{
        //用于建立连接
        EventLoopGroup bossGroup=new NioEventLoopGroup();
        EventLoopGroup workerGroup=new NioEventLoopGroup();
        try {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup,workerGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ChannelPipeline pipeline = ch.pipeline();
                        pipeline.addLast(new StringDecoder());
                        pipeline.addLast(new StringEncoder());
                        pipeline.addLast(new ChatServerHandler());
                    }
                })
                .option(ChannelOption.SO_BACKLOG,128)
                .childOption(ChannelOption.SO_KEEPALIVE,true);


            ChannelFuture f  = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) {
        ChatRoomServer chatRoomServer=new ChatRoomServer(8080);
        try {
            chatRoomServer.run();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

服务端自定义handler


import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

import java.util.*;

public class ChatServerHandler extends SimpleChannelInboundHandler<String> {
    private static final Map<ChannelHandlerContext, String> clients = Collections.synchronizedMap(new HashMap<>());

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        super.channelRegistered(ctx);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client connected: " + ctx.channel().remoteAddress());
        ctx.writeAndFlush("请输入你的用户名:\n");

    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("Client disconnected: " + clients.get(ctx));
        clients.remove(ctx);
        broadcastMessage("User left: " + clients.get(ctx));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        if (!clients.containsKey(ctx)) {
            if (!msg.isEmpty()) {
                if (clients.containsValue(msg)) {
                    ctx.writeAndFlush("用户名已经存在,请重新输入用户名:\n");
                    return;
                }
                clients.put(ctx, msg.trim());
                broadcastMessage("用户加入聊天室: " + msg);
            } else {
                ctx.writeAndFlush("无效的用户名,请重新输入用户名:\n");
                return;
            }
        } else {
            System.out.println("接收到消息 " + clients.get(ctx) + ": " + msg);
            if (msg.startsWith("/msg ")) {
                handlePrivateMessage(ctx, msg.substring(5));
            } else {
                broadcastMessage(clients.get(ctx) + ": " + msg);
            }
        }
    }

    private void broadcastMessage(String msg) {
        synchronized (clients) {
            for (ChannelHandlerContext client : clients.keySet()) {
                client.writeAndFlush(msg + "\n");
            }
        }
    }

    private void handlePrivateMessage(ChannelHandlerContext senderCtx, String msg) {
        String[] parts = msg.split(" ", 2);
        if (parts.length != 2) {
            senderCtx.writeAndFlush("无效的消息格式 使用 /msg <username> <message>\n");
            return;
        }

        String recipientUsername = parts[0];
        String message = parts[1];

        for (Map.Entry<ChannelHandlerContext, String> entry : clients.entrySet()) {
            if (entry.getValue().equals(recipientUsername)) {
                entry.getKey().writeAndFlush("[私发消息 来自于 " + clients.get(senderCtx) + "] " + message + "\n");
                return;
            }
        }

        senderCtx.writeAndFlush("用户未找到: " + recipientUsername + "\n");
    }
}

服务端自定义handler主要的功能:

  • 维护客户端注册信息
  • channel的注册
  • channel的激活事件的处理
  • channel注销事件的处理
  • 消息的处理
    • 广播消息的转发
    • 私发消息的转发

客户端

import com.fftf.netty.handler.ChatClientHandler;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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.io.BufferedReader;
import java.io.InputStreamReader;

public class ChatRoomClient {
    private final String host;
    private final int port;
    private  Channel channel;

    public ChatRoomClient(String host, int port) {
        this.host = host;
        this.port = port;
    }

    public Channel getChannel() {
        return channel;
    }

    public void run() throws Exception {
        EventLoopGroup 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(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new ChatClientHandler(ChatRoomClient.this));
                        }
                    });

            ChannelFuture future = bootstrap.connect(host, port).sync();
            channel = future.channel();

            // 启动一个线程用于接收用户的输入
            Thread userInputThread = new Thread(() -> {
                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                while (true) {
                    try {
                        String msg = in.readLine();
                        if (msg == null || "exit".equalsIgnoreCase(msg)) {
                            break;
                        }
                        channel.writeAndFlush(msg + "\n");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                channel.close();
            });
            userInputThread.start();

            // 等待直到客户端连接关闭
            future.channel().closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        String host = "localhost";
        int port = 8080;
        if (args.length == 2) {
            host = args[0];
            port = Integer.parseInt(args[1]);
        }
        new ChatRoomClient(host, port).run();
    }
}

客户端自定义handler

import com.fftf.netty.client.ChatRoomClient;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

public class ChatClientHandler extends SimpleChannelInboundHandler<String> {
    private final ChatRoomClient client;

    public ChatClientHandler(ChatRoomClient client){
        this.client=client;
    }
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
        System.out.println("收到消息:"+msg);
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
        client.getChannel().close();
    }
}

客户端handler的功能比较简单,就是输出收到消息的内容

效果演示

启动一个sever端,两个client端

服务端

客户端1:

客户端2:

完整代码

https://github.com/qyfftf/netty-chatroom

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

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

相关文章

利用 Jsoup 进行高效 Web 抓取与 HTML 处理

Jsoup 是一款 Java 的 HTML 解析器&#xff0c;可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API&#xff0c;可通过 DOM&#xff0c;CSS 以及类似于 JQuery 的操作方法来取出和操作数据。 官网&#xff1a;https://jsoup.org/ 中文文档&#xff1a;Jsou…

【c语言】文件操作详解 - 从打开到关闭

文章目录 1. 为什么使用文件&#xff1f;2. 什么是文件&#xff1f;3. 如何标识文件&#xff1f;4. 二进制文件和文本文件&#xff1f;5. 文件的打开和关闭5.1 流和标准流5.1.1 流5.1.2 标准流 5.2 文件指针5.3 文件的打开和关闭 6. 文件的读写顺序6.1 顺序读写函数6.2 对比一组…

004 逻辑变量与运算

当0和1表示逻辑状态时&#xff0c;两个二进制数码按照某种特定的因果关系进行的运算——就叫&#xff1a;逻辑运算 1.二值逻辑变量与基本逻辑运算 逻辑代数: 与普通代数不同,逻辑代数中的变量只有0和1两个可取值&#xff0c;它们分别用来表示完全两个对立的逻辑状态 逻辑运…

Deepnote、JupyterLab、Google Colab、Amazon SageMaker、VS Code对比

功能比较 平台语言支持扩展性数据连接可视化能力DeepnotePython、R、SQL中等&#xff0c;依赖云端支持主要云平台&#xff08;BigQuery、Snowflake等&#xff09;内置仪表盘与交互图表JupyterLab多种语言&#xff0c;插件支持广泛极高&#xff0c;完全可自定义使用库&#xff…

网络安全中的数据科学如何重新定义安全实践?

组织每天处理大量数据&#xff0c;这些数据由各个团队和部门管理。这使得全面了解潜在威胁变得非常困难&#xff0c;常常导致疏忽。以前&#xff0c;公司依靠 FUD 方法&#xff08;恐惧、不确定性和怀疑&#xff09;来识别潜在攻击。然而&#xff0c;将数据科学集成到网络安全中…

C语言数据结构与算法--简单实现队列的入队和出队

&#xff08;一&#xff09;队列的基本概念 和栈相反&#xff0c;队列(Queue)是一种先进先出&#xff08;First In First Out&#xff09;的线性表。只 允许在表的一端进行插入&#xff0c;而在另一端删除元素&#xff0c;如日常生活中的排队现象。队列中 允许插入的一端叫队尾…

快速理解微服务中Sentinel怎么实现限流

Sentinel是通过动态管理限流规则&#xff0c;根据定义的规则对请求进行限流控制。 一.实现步骤 1.定义资源&#xff1a;在Sentinel中&#xff0c;资源可以是URL、方法等&#xff0c;用于标识需要进行限流的请求&#xff1b;(在Sentinel中&#xff0c;需要我们去告诉Sentinel哪些…

matlab根据excel表头筛选表格数据

有如下表格需要筛选&#xff1a; 如果要筛选style中的A&#xff0c;color中的F2&#xff0c;num中的3。 代码如下&#xff1a; clear;clc; file_Pathstrcat(F:\csdn\,test1.xlsx); %表格路径、文件名 E1readtable(file_Path,Sheet,1); %读取表格中的字母和数字,1代表第一个…

学习日志016--python实现双向循环列表与链栈

python中一些复合数据结构通过类的封装来实现的。双向循环链表与链栈也在其中。 双向循环链表 双向循环链表是一种特殊类型的链表&#xff0c;它结合了双向链表和循环链表的特点。在双向循环链表中&#xff0c;每个节点不仅包含数据&#xff0c;还持有指向前一个和后一个节点的…

【Docker】常用命令汇总

Docker 是1个开源的应用容器引擎&#xff0c;基于Go 语言并遵从 Apache2.0 协议开源。 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。 容器是完全使用沙箱机制&#xff0c;相…

QT QRadioButton控件 全面详解

本系列文章全面的介绍了QT中的57种控件的使用方法以及示例,包括 Button(PushButton、toolButton、radioButton、checkBox、commandLinkButton、buttonBox)、Layouts(verticalLayout、horizontalLayout、gridLayout、formLayout)、Spacers(verticalSpacer、horizontalSpacer)、…

Docker部署mysql:8.0.31+dbsyncer

Docker部署mysql8.0.31 创建本地mysql配置文件 mkdir -p /opt/mysql/log mkdir -p /opt/mysql/data mkdir -p /opt/mysql/conf cd /opt/mysql/conf touch my.config [mysql] #设置mysql客户端默认字符集 default-character-setUTF8MB4 [mysqld] #设置3306端口 port33…

[SUCTF 2019]EasySQL--详细解析

信息搜集 进入界面是一个搜索框&#xff1a; 查看一下源代码&#xff0c;显示是POST传参&#xff1a; 随便上传个数字1&#xff1a; 抓包测试一下闭合&#xff0c;发现以双引号闭合会回显nonono,单引号闭合则无回显。 由于没有报错信息&#xff0c;所以我们不能确定具体的闭…

警钟长鸣,防微杜渐,遨游防爆手机如何护航安全生产?

近年来&#xff0c;携非防爆手机进入危险作业区引发爆炸的新闻屡见报端。2019年山西某化工公司火灾&#xff0c;2018年延安某煤业瓦斯爆炸&#xff0c;均因工人未用防爆手机产生静电打火引发。涉爆行业领域企业量大面广&#xff0c;相当一部分企业作业场所人员密集&#xff0c;…

【智能流体力学】RAG大模型方法:解决固体力学和流体动力学问题

【使用 AutoGen + GPT-4o + Chainlit UI 进行工程仿真的对话式多智能体 AI 聊天机器人】 本项目构建了一个由多个AI代理组成的系统,这些代理通过使用Microsoft AutoGen进行对话交互,能够自主地创建和仿真固体力学(FEA)和流体动力学(CFD)问题。每个AI代理都擅长规划、问题…

Redis与MySQL如何保证数据一致性

Redis与MySQL如何保证数据一致性 简单来说 该场景主要发生在读写并发进行时&#xff0c;才会发生数据不一致。 主要流程就是要么先操作缓存&#xff0c;要么先操作Redis&#xff0c;操作也分修改和删除。 一般修改要执行一系列业务代码&#xff0c;所以一般直接删除成本较低…

Java项目实战II基于微信小程序的校运会管理系统(开发文档+数据库+源码)

目录 一、前言 二、技术介绍 三、系统实现 四、核心代码 五、源码获取 全栈码农以及毕业设计实战开发&#xff0c;CSDN平台Java领域新星创作者&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导 一、前言 在充满活力与激情的校园生活中&#xff0c;校运会不仅是…

【西瓜书】神经网络-MP神经元、感知机和多层网络

神经网络&#xff08;neural networks&#xff09;的定义&#xff1a;神经网络是由具有适应性的简单单元组成的广泛并行互联的网络&#xff0c;它的组织能够模拟生物神经系统对真实世界物体所作出的交互反应。&#xff08;T. Kohonen 1988年在Neural Networks创刊号上给出的定义…

《安富莱嵌入式周报》第346期:开源2GHz带宽,12bit分辨率,3.2Gsps采样率示波,开源固件安全分析器, 开源口袋电源,开源健康测量,FreeCAD

周报汇总地址&#xff1a;嵌入式周报 - uCOS & uCGUI & emWin & embOS & TouchGFX & ThreadX - 硬汉嵌入式论坛 - Powered by Discuz! 视频&#xff1a; https://www.bilibili.com/video/BV1TYBhYKECK/ 《安富莱嵌入式周报》第346期&#xff1a;开源2GHz带…

介绍一下atoi(arr);(c基础)

hi , I am 36 适合对象c语言初学者 atoi(arr)&#xff1b;是返回整数(int型)&#xff0c;整数是arr数组中字符中数字 格式 #include<stdio.h> atoi(arr); 返回值arr数组中的数字 未改变arr数组 #include<stdlib.h>//atoi(arr); 返 <stdlib> int main(…