利用Netty,从零到一实现自己的简易实时聊天系统

1. 前言

先看实现效果:

在这里插入图片描述

由于篇幅有限,而且代码量还是有的,所以下面只给出重要部分的代码,详细大家可以去项目里面看,完整代码已经完全开源。(仓库链接在文章末尾)

到这里我们可以看到接下来要实现的内容包括:

  • 聊天主页面收到新消息后提示并刷新最新消息。
  • 消息已读/未读状态实时变更。
  • 消息实时收与发。
  • 聊天页面收到新消息或者发送了消息,聊天主页面消息展示同步更新。(视频未展示)

只要思路正确,最后实现出效果所产生的代码量并不是很多。

寥寥的四条实现效果,可能并不多,但是你要学习的东西却与之成反比例。

通过本篇文章您大至能够了解到或者学习到:

  • Netty的基础知识
  • websocket基础知识
  • 小程序开发基础知识(uni-app vscode开发版)
  • vue、vuex的一些内容

2. 前置知识准备

这里说一下实现这些功能需要具备的一些基础,读者可以进一步的深入了解,更有利于自身的学习与成长,仅仅介绍技术的基础,认识到这个是做什么的,不深入讨论某些功能。

2.1 简单介绍一下Netty

Netty是什么?Netty 是一个基于 Java 的高性能网络应用框架,主要用于开发客户端-服务器 (Client-Server) 的通信程序,支持多种传输协议(如 TCP、UDP、HTTP 等)。它是一个异步事件驱动的网络框架,能够大幅简化网络编程,特别是在高并发场景下。

  • 异步和事件驱动
    基于事件驱动机制,通过 ChannelEventLoopFuture 等模型实现高效的异步通信。
  • 多协议支持
    支持 TCP、UDP、HTTP/2、WebSocket 等协议,也可以自定义协议。
  • 高性能
    • 线程模型优化:采用 Reactor 模式,使用少量线程处理大量连接。
    • 内存优化:通过对象池和零拷贝技术优化内存使用。
  • 可扩展性
    通过 ChannelHandlerPipeline 实现灵活的业务逻辑扩展。(重要,本文介绍的内容基于该特性实现)

它的一些应用场景:

  1. 即时通讯:IM 系统、聊天服务(如微信、QQ)。
  2. 网关系统:微服务网关、API 网关。
  3. 高性能 HTTP 服务:如 HTTP 反向代理、流媒体服务。
  4. 游戏服务器:需要长连接和低延迟的实时通信场景。
  5. 分布式系统:消息队列、RPC 框架的底层实现。

选择使用Netty,它有这比较丰富的API和工具,简化socket编程,在了解它的前提下可以基于它快速构建自己的应用。

我有一篇文章介绍了Netty的一部分源码内容,感兴趣的可以去研究,看源码是一项有挑战性的事情。Netty部分源码

2.2 WebSocket

WebSocket表示一种网络传输协议,位于OSI模型的应用层,并且依赖于传输层的TCP协议。它是一种不同于http的协议,但是RFC 6455中:it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries(WebSocket通过HTTP端口80和443进行工作,并支持HTTP代理和中介),为了实现兼容性,WebSocket握手使用HTTP Upgrade头从HTTP协议更改为WebSocket协议。

WebSocket协议支持Web浏览器(或其他客户端应用程序)与Web服务器之间的交互,具有较低的开销,便于实现客户端与服务器的实时数据传输。

2.3 开发工具

使用的是uniapp跨端开发语言,实际使用和vue一样,只是有一些新组件,看官方文档就知道如何使用。

uni-app官网

开发工具使用的是vscode,至于如何在vscode上面开发uniapp,要先调教一下vscode,笔者是参考的这篇文章。

vscode开发uni-app

小程序开发工具下载

微信开发者工具

可以去微信开发者平台注册一个账号,拿到appid后面方便自己调试使用。

微信开放平台

3. 数据库设计

看到这里,相信你已经对用到的技术和工具有了大致的了解,现在下面开始动手如何去实现。

首先我们需要一张消息表来记录各自收发的信息,不能离开聊天界面把消息都丢了,当然如果你不需要存储消息,只是即时聊天,也可以不用搞一个表去存储消息。

create table tbl_message
(
    id                  varchar(32) primary key,
    message_type        smallint      not null comment '消息类型:待办通知:0 申请结果通知:1',
    read_state          smallint      not null default 0 comment '阅读状态:0未读,1已读',
    content             varchar(1024) not null comment '消息内容',
    message_receiver_id varchar(32)   not null comment '消息接收者id',
    message_sender_id   varchar(32)   not null comment '消息发送者id',
    create_time         bigint        not null comment '创建时间',
    update_time         bigint        not null comment '修改时间',
    constraint message_receiver_fk foreign key (message_receiver_id) references tbl_user (id),
    constraint message_sender_fk foreign key (message_sender_id) references tbl_user (id)
) comment '消息表';

避免篇幅过长这里的用户表就不贴出了。

位置在这里:src/main/resources/db/migration/V1.0__init.sql

3. 后端

netty的依赖要引入到项目中:

<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.110.Final</version>
</dependency>

创建Netty启动类:

@Component
@Slf4j
public class NettyBootstrapRunner implements ApplicationRunner, ApplicationListener<ContextClosedEvent>, ApplicationContextAware {
   
   

    @Value("${netty.websocket.port}")
    private int port;

    @Value("${netty.websocket.pemFile}")
    private String pemFile;

    @Value("${netty.websocket.keyFile}")
    private String keyFile;

    private Channel serverChannel;

    private ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
   
   
        this.applicationContext = applicationContext;
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
   
   
        // 使用自签名证书进行 SSL 配置
        ApplyRoomRecordConfig globalConfig = applicationContext.getBean(ApplyRoomRecordConfig.class);
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
   
   
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.localAddress(new InetSocketAddress(port));
            serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
   
   
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
   
   
                    ChannelPipeline pipeline = socketChannel.pipeline();
                    addSslHandler(pipeline, socketChannel, globalConfig);
                    pipeline.addLast(new HttpServerCodec());//请求解码器
                    pipeline.addLast(new HttpObjectAggregator(65536));//将多个消息转换成单一的消息对象
                    pipeline.addLast(new ChunkedWriteHandler());//支持异步发送大的码流,一般用于发送文件流
                    pipeline.addLast(new WebSocketServerCompressionHandler());//压缩处理
                    pipeline.addLast(applicationContext.getBean(UserAuthHandler.class));// 用户认证处理
                    // 参数配置请百度
                    pipeline.addLast(new WebSocketServerProtocolHandler("/websocket", null, true, 16384, false, true, 60000L));//websocket协议处理
                    pipeline.addLast(applicationContext.getBean(SocketConnectedHandler.class)); // 自定义处理器,处理消息发送与在线统计
                }
            });
            serverChannel = serverBootstrap.bind().sync().channel();
            log.info("websocket 服务启动,port={}", this.port);
            serverChannel.closeFuture().sync();
        } catch (Exception e) {
   
   
            log.error(e.getMessage());
        } finally {
   
   
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    @Override
    public void onApplicationEvent(ContextClosedEvent event) {
   
   
        if (this.serverChannel != null) {
   
   
            this.serverChannel.close();
        }
        log.info("websocket 服务停止");
    }

    private void addSslHandler(ChannelPipeline pipeline, SocketChannel socketChannel, ApplyRoomRecordConfig globalConfig) {
   
   
        // 取决于你的配置,如果配置了是,那么请您同时配置pem和key文件
        // 这两个文件请放在resource目录下
        if (globalConfig.getUseWebsocketSSL()) 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值