远方的灯塔 - 专注于服务端技术分享 远方的灯塔 - 专注于服务端技术分享
首页
  • Java SE
  • Struts2
  • Hibernate
  • MyBatis
  • JAX-WS
  • 并发
  • 分布式
  • Git
  • 文章分类
  • 文章标签
  • 文章归档
  • 《C程序设计语言》
心情随笔
友情链接
给我留言 (opens new window)
关于我
GitHub (opens new window)

Terwer Green

一个后端老菜鸟
首页
  • Java SE
  • Struts2
  • Hibernate
  • MyBatis
  • JAX-WS
  • 并发
  • 分布式
  • Git
  • 文章分类
  • 文章标签
  • 文章归档
  • 《C程序设计语言》
心情随笔
友情链接
给我留言 (opens new window)
关于我
GitHub (opens new window)
  • JavaSE

  • 开源框架

  • Linux

  • Struts2

  • Hibernate

  • Webservice

  • 分布式

    • RPC架构设计及IO模型
    • NIO编程及其三大核心原理
    • NIO三大核心之缓冲区(Buffer)
    • NIO三大核心之通道(Channel)
    • NIO三大核心之选择器(Selector)
    • Netty核心原理
    • 线程模型以及传统IO阻塞模型
    • Reactor模型
    • Netty线程模型
    • Netty核心API介绍
    • Netty入门与异步模型
    • Netty高级进阶之Netty编解码器
    • Netty高级进阶之基于Netty的群聊天室案例
    • Netty高级进阶之基于Netty的HTTP服务器开发
    • Netty高级进阶之基于Netty的Websocket开发网页聊天室
      • Webdocket简介
      • Websocket和HTTP的区别
      • 导入基础环境
      • 代码实现
        • 服务端开发
        • 前端js开发
      • 运行效果
    • Netty高级进阶之Netty中的粘包和拆包的解决方案
    • Nety源码剖析
    • 自定义RPC框架之分布式架构网络通信理论
    • 自定义RPC框架之基于Netty实现RPC框架
    • 分布式架构理论
    • 分布式理论之数据一致性
    • 分布式理论之CAP定理
    • 分布式理论之BASE定理
    • 分布式一致性协议之两阶段提交协议(2PC)
    • 分布式一致性协议之三阶段提交协议(3PC)
    • 分布式一致性协议之NWR协议
    • 分布式一致性协议之Gossip协议
    • 分布式一致性协议之Paxos协议
    • 分布式一致性协议之Raft协议
    • 分布式一致性协议之Lease机制
    • 分布式系统设计策略之心跳检测
    • 分布式系统设计策略之高可用
    • 分布式系统设计策略之容错性
    • 分布式系统设计策略之负载均衡
    • 分布式架构服务调用
    • 分布式服务治理之服务协调
    • 分布式服务治理之服务削峰
    • 分布式服务治理之服务降级
    • 分布式服务治理之服务限流
    • 分布式服务治理之服务熔断
    • 分布式服务治理之服务链路追踪
    • 架构设计基本原则之开闭原则(OCP)
    • 架构设计基本原则之单一职责原则(SRP)
    • 架构设计基本原则之接口隔离原则(ISP)
    • 架构设计基本原则之里式替换原则(LSP)
    • 架构设计基本原则之依赖倒置原则(DIP)
    • 架构设计基本原则知识扩展
    • 分布式架构知识拓展与总结
  • 分布式框架

  • 后端开发
  • 分布式
terwer
2022-04-27
目录

Netty高级进阶之基于Netty的Websocket开发网页聊天室

本通过实战演练,学习了如何基于Netty的websocket开发一个网页聊天室。

# Netty高级进阶之基于Netty的Websocket开发网页聊天室

# Webdocket简介

Websockt是一种在单个TCP连接上进行全双工通信的协议。

Websocket使客户端和服务端的数据交互变得简单,允许服务器主动向客户端推送数据。

在Websocket API中,客户端只需要与服务器完成一次握手,两者之间就可以创建持久性的连接,并进行双向数据传输。

他的应用场景如下:

  • 社交订阅
  • 协同编辑/编程
  • 股票基金报价
  • 体育实况更新
  • 多媒体聊天
  • 在线教育

# Websocket和HTTP的区别

HTTP协议是应用层的协议,是基于TCP协议的。

HTTP协议必须经过三次握手才能发送消息。

HTTP连接分为短连接和长链接。短连接是每次都要经过三次握手才能发送消息。就是说每一个request对应一个response。长连接在一定期限内保持TCP连接不断开。

客户端与服务器通信,必须由客户端先发起,然后服务端返回结果。客户端是主动的,服务端是被动的。

客户端想要实时获取服务端的消息,就要不断发送长连接到服务端。

Websocket实现了多路复用,它是全双工通信。在Websocket协议下,服务端和客户端可以同时发送消息。

建立了Websocket连接之后,服务端可以主动给客户端发送消息。信息中不必带有header的部分信息,与HTTP长连接通信对比,这种方式降低了服务器的压力,信息当中也减少了多余的信息。

# 导入基础环境

  1. 新建netty-springboot项目

    image-20220430225632849

  2. 导入依赖模块

    <dependencies>
        <!-- 模板引擎 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <!-- web模块 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
  3. 导入静态资源

    image-20220430230249496

  4. 配置yaml

    server:
      port: 8080
    resources:
      static-locations:
        - classpath:/static/
    spring:
      thymeleaf:
        cache: false
        checktemplatelocation: true
        enabled: true
        encoding: UTF-8
        mode: HTML
        prefix: classpath:/templates/
        suffix: .html
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

关于Springboot整合thymeleaf的404问题,参考:

/post/404-problem-with-springboot-configuration-thymeleaf.html

# 代码实现

# 服务端开发

  1. 添加Netty相关依赖

    <!--引入netty依赖 -->
    <dependency>
        <groupId>io.netty</groupId>
        <artifactId>netty-all</artifactId>
    </dependency>
    
    1
    2
    3
    4
    5
  2. Netty相关配置

    netty:
      port: 8081
      ip: 127.0.0.1
      path: /chat
    
    1
    2
    3
    4
  3. Netty配置类

    /**
     * Netty配置类
     *
     * @name: NettyConfig
     * @author: terwer
     * @date: 2022-05-01 00:04
     **/
    @Component
    @Data
    @ConfigurationProperties(prefix = "netty")
    public class NettyConfig {
        // netty监听端口
        private int port;
        // webdocket访问路径
        private String path;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  4. Netty的WebsocketServer开发

    /**
     * Netty的Websocket服务器
     *
     * @name: NettyWebsocketServer
     * @author: terwer
     * @date: 2022-05-01 00:11
     **/
    @Component
    public class NettyWebsocketServer implements Runnable {
        @Autowired
        private NettyConfig nettyConfig;
        @Autowired
        private WebsocketChannelInit websocketChannelInit;
    
        private NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
        private NioEventLoopGroup workerGroup = new NioEventLoopGroup();
    
        @Override
        public void run() {
            try {
                // 1.创建服务端启动助手
                ServerBootstrap serverBootstrap = new ServerBootstrap();
                // 2.设置线程组
                serverBootstrap.group(bossGroup, workerGroup);
                // 3.设置参数
                serverBootstrap.channel(NioServerSocketChannel.class)
                        .handler(new LoggingHandler(LogLevel.DEBUG))
                        .childHandler(websocketChannelInit);
                // 4.启动服务端
                ChannelFuture channelFuture = serverBootstrap.bind(nettyConfig.getPort()).sync();
                System.out.println("------Netty服务端启动成功------");
                channelFuture.channel().closeFuture().sync();
            } catch (InterruptedException e) {
                e.printStackTrace();
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
                throw new RuntimeException(e);
            } finally {
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    
        /**
         * 关闭资源-容器销毁时候关闭
         */
        @PreDestroy
        public void close() {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
  5. 通道初始化对象

    /**
     * 通道初始化对象
     *
     * @name: WebsocketChannelInit
     * @author: terwer
     * @date: 2022-05-01 23:11
     **/
    @Component
    public class WebsocketChannelInit extends ChannelInitializer {
    
        @Autowired
        private NettyConfig nettyConfig;
    
        @Autowired
        private WebsocketHandler websocketHandler;
    
        @Override
        protected void initChannel(Channel channel) throws Exception {
            ChannelPipeline pipeline = channel.pipeline();
    
            // 对HTTP协议的支持
            pipeline.addLast(new HttpServerCodec());
    
            // 对大数据流的支持
            pipeline.addLast(new ChunkedWriteHandler());
    
            // post请求分为三个部分:request line/request header/message body
            // 对POST请求的支持,将多个信息转化成单一的request/response对象
            pipeline.addLast(new HttpObjectAggregator(8000));
    
            // 对WebSocket协议的支持
            // 将http协议升级为ws协议
            pipeline.addLast(new WebSocketServerProtocolHandler(nettyConfig.getPath()));
    
            // 自定义处理handler
            pipeline.addLast(websocketHandler);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
  6. 处理对象

    /**
     * 自定义Websocket处理类
     * Websocket数据以帧的形式进行处理
     * 需要设置通道共享
     *
     * @name: WebsocketHandler
     * @author: terwer
     * @date: 2022-05-01 23:21
     **/
    @Component
    @ChannelHandler.Sharable
    public class WebsocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
        public static List<Channel> channelList = new ArrayList<>();
    
        /**
         * 通道就绪事件
         *
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            Channel channel = ctx.channel();
            // 有客户端连接时,将通道放入集合
            channelList.add(channel);
            System.out.println("有新的链接");
        }
    
        /**
         * 通道未就绪
         *
         * @param ctx
         * @throws Exception
         */
        @Override
        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
            // channel下线
            Channel channel = ctx.channel();
            // 客户端连接端口,移除连接
            channelList.remove(channel);
            System.out.println("连接断开");
        }
    
        /**
         * 通道读取事件
         *
         * @param ctx
         * @param textWebSocketFrame
         * @throws Exception
         */
        @Override
        protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {
            String msg = textWebSocketFrame.text();
            System.out.println("接收到消息:" + msg);
            // 当前发送消息的通道
            Channel channel = ctx.channel();
            for (Channel channel1 : channelList) {
                // 排除自身通道
                if (channel != channel1) {
                    channel1.writeAndFlush(new TextWebSocketFrame(msg));
                }
            }
        }
    
        /**
         * 异常处理事件
         *
         * @param ctx
         * @param cause
         * @throws Exception
         */
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            cause.printStackTrace();
            Channel channel = ctx.channel();
            System.out.println("消息发送异常。");
            // 移除
            channelList.remove(channel);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80

    注意:处理类需要设置成共享的

  7. 启动类

    @SpringBootApplication
    public class NettySpringbootApplication implements CommandLineRunner {
    
        @Autowired
        private NettyWebsocketServer nettyWebsocketServer;
    
        public static void main(String[] args) {
            SpringApplication.run(NettySpringbootApplication.class, args);
        }
    
        @Override
        public void run(String... args) throws Exception {
            new Thread(nettyWebsocketServer).start();
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

# 前端js开发

  • 建立连接

    var ws = new WebSocket("ws://localhost:8081/chat");
    ws.onopen = function () {
      console.log("连接成功")
    }
    
    1
    2
    3
    4
  • 发送消息

    function sendMsg() {
        var message = $("#my_test").val();
        $("#msg_list").append(`<li class="active"}>
                                  <div class="main self">
                                      <div class="text">` + message + `</div>
                                  </div>
                              </li>`);
        $("#my_test").val('');
    
        //发送消息
        message = username + ":" + message;
        ws.send(message);
        // 置底
        setBottom();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  • 接收消息

    ws.onmessage = function (evt) {
        showMessage(evt.data);
    }
    
    function showMessage(message) {
        // 张三:你好
        var str = message.split(":");
        $("#msg_list").append(`<li class="active"}>
                                  <div class="main">
                                    <img class="avatar" width="30" height="30" src="/img/user.png">
                                    <div>
                                        <div class="user_name">${str[0]}</div>
                                        <div class="text">${str[1]}</div>
                                    </div>                       
                                   </div>
                              </li>`);
        // 置底
        setBottom();
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
  • 关闭与错误处理

    ws.onclose = function (){
        console.log("连接关闭")
    }
    
    ws.onerror = function (){
        console.log("连接异常")
    }
    
    1
    2
    3
    4
    5
    6
    7

# 运行效果

image-20220502001145851

![image-20220502001159603](../../../../../../../Library/Application Support/typora-user-images/image-20220502001159603.png)

image-20220502001220081

编辑 (opens new window)
#netty#websocket#chat#web
上次更新: 2023/02/22, 13:47:25
Netty高级进阶之基于Netty的HTTP服务器开发
Netty高级进阶之Netty中的粘包和拆包的解决方案

← Netty高级进阶之基于Netty的HTTP服务器开发 Netty高级进阶之Netty中的粘包和拆包的解决方案→

最近更新
01
解决css部分border被圆角切掉之后圆角的边框消失问题
03-18
02
使用TypeScript开发一个自定义的Node-js前端开发脚手架
03-08
03
Github-Actions使用release-please实现自动发版
03-06
更多文章>
Theme by Vdoing | Copyright © 2011-2023 Terwer Green | MIT License | 粤ICP备2022020721号-1 | 百度统计
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式