使用Netty实现一个简单的聊天服务器

✅作者简介:热爱Java后端开发的一名学习者,大家可以跟我一起讨论各种问题喔。
🍎个人主页:Hhzzy99
🍊个人信条:坚持就是胜利!
💞当前专栏:Netty
🥭本文内容:Netty实现一个简单的聊天服务器。

文章目录

  • 使用Netty实现一个简单的聊天服务器
    • 一、项目初始化与依赖配置
      • 1.1 创建Maven项目并添加依赖
    • 二、服务器端代码实现
      • 2.1 `ChatServer` - 服务端启动类
      • 2.2 `ChatServerHandler` - 消息处理器
    • 三、控制台客户端实现
      • 3.1 `ChatClient` - 客户端启动类
      • 3.2 `ChatClientHandler` - 客户端消息处理器
    • 四、启动与测试
      • 5.1 启动服务器和控制台客户端
  • 结语


使用Netty实现一个简单的聊天服务器

在本篇教程中,我们将通过Java的Netty框架实现一个简单的聊天服务器。

Netty是一款基于NIO(非阻塞IO)开发的网络框架,适用于高性能、高并发的应用场景。本文会详细展示如何通过Netty实现聊天服务器,实现控制台版客户端。

一、项目初始化与依赖配置

1.1 创建Maven项目并添加依赖

我们使用Maven管理依赖,首先在pom.xml文件中添加Netty依赖:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.example</groupId>
    <artifactId>netty-chat</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!-- Netty 依赖 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.68.Final</version>
        </dependency>
    </dependencies>
</project>

这样配置完成后,Maven会自动下载Netty的相关包。

二、服务器端代码实现

服务器端负责接受客户端连接、广播消息,并在客户端断开时进行处理。接下来我们编写服务器端启动类和处理器。

2.1 ChatServer - 服务端启动类

ChatServer类用于配置Netty服务器的基础设置,包括监听的端口、通道类型(NIO模式)和处理器链。服务器会监听指定端口,并通过处理器将消息分发给各客户端。

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
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 {

    private final int port;

    public ChatServer(int port) {
        this.port = port;  // 设置服务器监听的端口
    }

    public void start() throws InterruptedException {
        // bossGroup:接受客户端连接的线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // workerGroup:处理客户端连接的数据传输
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // ServerBootstrap用于启动和配置Netty服务器
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)  // 设置boss和worker线程组
             .channel(NioServerSocketChannel.class)  // 指定NIO传输的通道类型
             .option(ChannelOption.SO_BACKLOG, 1024)  // 设置队列容量
             .childHandler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ch.pipeline().addLast(new StringDecoder());
                     ch.pipeline().addLast(new StringEncoder());
                     // 为每个客户端连接配置消息处理器
                     ch.pipeline().addLast(new ChatServerHandler());
                 }
             });

            // 绑定端口并启动服务器
            ChannelFuture f = b.bind(port).sync();
            System.out.println("Chat server started on port " + port);
            // 等待服务器关闭
            f.channel().closeFuture().sync();
        } finally {
            // 关闭资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 8080;  // 定义服务器监听的端口号
        new ChatServer(port).start();  // 启动服务器
    }
}

2.2 ChatServerHandler - 消息处理器

ChatServerHandler类负责管理客户端连接和消息的广播。我们会在客户端加入时广播“用户加入”消息,并在消息接收时进行广播。

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

public class ChatServerHandler extends ChannelInboundHandlerAdapter  {

    // 使用ChannelGroup来保存所有的连接,便于消息广播
    private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    // 当新的客户端连接加入时调用
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel incoming = ctx.channel();
        // 广播消息给所有已连接的客户端,通知新的连接加入
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " joined\n");
        }
        channels.add(incoming);  // 将新连接加入ChannelGroup
    }

    // 当客户端断开连接时调用
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel outgoing = ctx.channel();
        // 广播消息给所有已连接的客户端,通知连接已断开
        for (Channel channel : channels) {
            channel.writeAndFlush("[SERVER] - " + outgoing.remoteAddress() + " left\n");
        }
        channels.remove(outgoing);  // 从ChannelGroup中移除断开的连接
    }

    // 当服务器接收到客户端发来的消息时调用
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
        Channel incoming = ctx.channel();
        // 使用StringDecoder与StringEncoder编码解码消息,所以直接字符串
        String msg = (String) message;
        System.out.println("[client] : " + msg);
        // 向其他客户端广播消息,当前发送者不接收自己的消息
        for (Channel channel : channels) {
            if (channel != incoming) {
                channel.writeAndFlush("[" + incoming.remoteAddress() + "] " + msg + "\n");
            } else {
                channel.writeAndFlush("[you] " + msg + "\n");
            }
        }
    }

    // 当出现异常时调用
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();  // 关闭连接
    }
}

三、控制台客户端实现

在实现浏览器客户端之前,我们先用Java创建一个简单的控制台客户端,以便在控制台中测试服务器的基本功能。通过这种方式,我们可以在不使用HTML页面的情况下,验证服务器的消息广播和客户端连接是否正常工作。

3.1 ChatClient - 客户端启动类

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.EventLoopGroup;
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 {

    private final String host;
    private final int port;

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

    public void start() throws InterruptedException {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ch.pipeline().addLast(new StringDecoder());
                     ch.pipeline().addLast(new StringEncoder());
                     ch.pipeline().addLast(new ChatClientHandler());
                 }
             });

            Channel channel = b.connect(host, port).sync().channel();

            Scanner scanner = new Scanner(System.in);
            System.out.println("Enter your messages (type 'exit' to quit):");

            while (true) {
                String message = scanner.nextLine();
                if ("exit".equalsIgnoreCase(message)) {
                    break;
                }
                channel.writeAndFlush(message + "\n");
            }

        } finally {
            group.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new ChatClient("localhost", 8080).start();
    }
}

3.2 ChatClientHandler - 客户端消息处理器

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);  // 输出从服务器收到的消息
    }

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

四、启动与测试

5.1 启动服务器和控制台客户端

  1. 启动ChatServer
  2. 启动ChatClient,进行消息测试。

我们启动服务端ChatServer
chatServer

接着再启动客户端ChatClient
chatClient

客户端ChatClient发送消息,并且服务端转发回来( [YOU] 那一行就是服务端转发回来的):
在这里插入图片描述
服务端接收到消息:
在这里插入图片描述


结语

本文通过实现简单的控制台客户端和HTML客户端,让大家更好地理解Netty服务器的工作原理。希望对大家有所帮助。

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

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

相关文章

使用 Spring Boot 搭建 WebSocket 服务器实现多客户端连接

在 Web 开发中&#xff0c;WebSocket 为客户端和服务端之间提供了实时双向通信的能力。本篇博客介绍如何使用 Spring Boot 快速搭建一个 WebSocket 服务器&#xff0c;并支持多客户端的连接和消息广播。 1. WebSocket 简介 WebSocket 是 HTML5 的一种协议&#xff0c;提供了客…

PHP常量

PHP 中的常量是指一旦定义后将不能被改变的标识符。 常量可以用const和define&#xff08;&#xff09;来定义。 PHP常量的特性 不变性: 常量一旦定义&#xff0c;其值不能改变。全局作用域: 常量在定义后&#xff0c;可以在整个脚本的任何地方使用&#xff0c;无需使用 glo…

让Erupt框架支持.vue文件做自定义页面模版

Erupt是什么&#xff1f; Erupt 是一个低代码 全栈类 框架&#xff0c;它使用 Java 注解 动态生成页面以及增、删、改、查、权限控制等后台功能。 零前端代码、零 CURD、自动建表&#xff0c;仅需 一个类文件 简洁的注解配置&#xff0c;快速开发企业级 Admin 管理后台。 提…

Echarts 图表根据屏幕大小自适应图表大小/标签文字大小

自适应图表大小 echarts多个图表大小随屏幕的大小改变自适应&#xff0c;Echarts 多图表自适应窗口大小&#xff0c;echarts随页面大小变化而变化&#xff1b; 但 Echarts 同一页面存在多个图表的时候&#xff0c;只有一个生效 只有一个图表的时候 直接用 window.onresize …

基于 Transformer 的语言模型

基于 Transformer 的语言模型 Transformer 是一类基于注意力机制&#xff08;Attention&#xff09;的模块化构建的神经网络结构。给定一个序列&#xff0c;Transformer 将一定数量的历史状态和当前状态同时输入&#xff0c;然后进行加权相加。对历史状态和当前状态进行“通盘…

Docker:容器编排 Docker Compose

Docker&#xff1a;容器编排 Docker Compose docker-composedocker-compose.ymlservicesimagecommandenvironmentnetworksvolumesportshealthcheckdepends_on 命令docker compose updocker compose down其它 docker-compose 多数情况下&#xff0c;一个服务需要依赖多个服务&a…

力扣633.平方数之和 c++

给定一个非负整数 c &#xff0c;你要判断是否存在两个整数 a 和 b&#xff0c;使得 a2 b2 c 。 示例 1&#xff1a; 输入&#xff1a;c 5 输出&#xff1a;true 解释&#xff1a;1 * 1 2 * 2 5示例 2&#xff1a; 输入&#xff1a;c 3 输出&#xff1a;false提示&…

【ESP32】ESP-IDF开发 | I2C从机接收i2c_slave_receive函数的BUG导致程序崩溃解决(idf-v5.3.1版本)

1. 问题 在调试I2C外设的demo时&#xff0c;按照官方文档的描述调用相关API&#xff0c;烧录程序后发现程序会不断崩溃&#xff0c;系统log如下。 初步分析log&#xff0c;原因是访问到了不存在的地址。一开始我以为是自己的代码问题&#xff0c;反反复复改了几次都会出现同样的…

链表交集相关算法题|AB链表公共元素生成链表C|AB链表交集存放于A|连续子序列|相交链表求交点位置(C)

AB链表公共元素生成链表C 设A和B是两个单链表(带头节点)&#xff0c;其中元素递增有序。设计一个算法从A和B中的公共元素产生单链表C&#xff0c;要求不破坏A、B的节点 算法思想 表A&#xff0c;B都有序&#xff0c;可从第一个元素起依次比较A、B两表的元素&#xff0c;若元…

蓝牙BLE开发——红米手机无法搜索蓝牙设备?

解决 红米手机&#xff0c;无法搜索附近蓝牙设备 具体型号当时忘记查看了&#xff0c;如果你遇到有以下选项&#xff0c;记得打开~ 设置权限

2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能

基于matlab-GUI的脉冲响应不变法实现音频滤波功能&#xff0c;输入加噪信号&#xff0c;通过巴特沃斯模拟滤波器脉冲响应不变法进行降噪。效果较好。程序已调通&#xff0c;可直接运行。 下载源程序请点链接&#xff1a;2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能…

智慧生活新标准:解锁耐能科技的潜能

『从马路上&#xff0c;看到旁边摄影机下方的牌子写着科技执法』 『开车进入停车场时&#xff0c;车牌辨识成功开启闸门』 『回到家门口前&#xff0c;进行脸部辨识解锁开门』 『手机APP弹起提醒&#xff0c;出现宝宝的画面表示正在哭泣』 上述的情景&#xff0c;你我是否很熟悉…

[VUE]框架网页开发1 本地开发环境安装

前言 其实你不要看我的文章比较长&#xff0c;但是他就是很长&#xff01;步骤其实很简单&#xff0c;主要是为新手加了很多解释&#xff01; 步骤一&#xff1a;下载并安装 Node.js 访问 Node.js 官网&#xff1a; Node.js — Download Node.js 下载 Windows 64 位版本&…

C++线程异步

本文内容来自&#xff1a; 智谱清言 《深入应用C11 代码优化与工程级应用》 std::future std::future作为异步结果的传输通道&#xff0c;可以很方便地获取线程函数的返回值。 std::future_status Ready (std::future_status::ready): 当与 std::future 对象关联的异步操作…

【Python】【数据可视化】【商务智能方法与应用】课程 作业一 飞桨AI Studio

作业说明 程序运行和题目图形相同可得90分&#xff0c;图形显示有所变化&#xff0c;美观清晰可适当加分。 import matplotlib.pyplot as plt import numpy as npx np.linspace(0, 1, 100) y1 x**2 y2 x**4plt.figure(figsize(8, 6))# yx^2 plt.plot(x, y1, -., labelyx^2,…

后端eclipse——文字样式:UEditor富文本编辑器引入

目录 1.富文本编辑器的优点 2.文件的准备 3.文件的导入 导入到项目&#xff1a; 导入到html文件&#xff1a; ​编辑 4.富文本编辑器的使用 1.富文本编辑器的优点 我们从前端写入数据库时&#xff0c;文字的样式具有局限性&#xff0c;不能存在换行&#xff0c;更改字体…

基于springboot+小程序的汽车销售管理系统(汽车4)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 ​ 1、管理员实现了首页、个人中心、管理员管理、基础数据管理、论坛管理、公告信息管理、汽车管理、用户管理、轮播图信息等。 ​ 2、用户实现了注册、登录、首页、汽车类型、论坛、购物…

江协科技STM32学习- P29 实验- 串口收发HEX数据包/文本数据包

&#x1f680;write in front&#x1f680; &#x1f50e;大家好&#xff0c;我是黄桃罐头&#xff0c;希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流 &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd;​…

Quartz的使用

1.准备工作 建立Maven工程 2.引入Quartz的jar包 <dependencies><dependency><groupId>org.quartz-scheduler</groupId><artifactId>quartz</artifactId><version>2.3.0</version></dependency></dependencies>3…

黑马官网最新2024前端就业课V8.5笔记---CSS篇(2)

盒子模型 画盒子 目标:使用合适的选择器画盒子 新属性 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"vi…