• 9.基于netty实现WebSocket服务器


    【README】

    1.本文总结自B站《netty-尚硅谷》,很不错;

    2.本文示例代码基于netty实现 WebSocket服务器功能;

    • 其中, html作为WebSocket客户端;

    3.WebSocket协议介绍:

    •  对于 WebSocket,它的数据是以帧 frame 的形式传递的
    •  可以看到 WebSocketFrame 下面有6个子类
    •  浏览器发送请求时: ws://localhost:8089/hello 表示请求的uri
    •  WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接; 是通过一个状态码 101 来切换的;

    4.本文末尾po出了 WebSocket  请求响应报文截图;


    【1】基于Netty 实现WebSocket服务器和客户端长连接:

    1)WebSocket概述:

    • 同http类似,WebSocket也是一种应用层传输协议,基于WebSocket,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

    2)WebSocket协议特点(转自阮一峰 WebSocket 教程 - 阮一峰的网络日志):

    1. 建立在 TCP 协议之上,服务器端的实现比较容易。
    2. 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
    3. 数据格式比较轻量,性能开销小,通信高效。
    4. 可以发送文本,也可以发送二进制数据。
    5. 没有同源限制,客户端可以与任意服务器通信
    6. 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。

     

    3)需求描述:

    • ① Http协议是无状态的, 浏览器和服务器间的请求响应一次,下一次会重新创建连接;
    • 要求:实现基于webSocket的长连接的全双工的交互
    • ③ 改变Http协议多次请求的约束,实现长连接了, 服务器可以发送消息给浏览器;
    • 客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知;

    【1.1】WebSocket服务器

    1. /**
    2. * @Description 基于netty的webSocket服务器
    3. * @author xiao tang
    4. * @version 1.0.0
    5. * @createTime 2022年09月04日
    6. */
    7. public class NettyWebSocketServer68 {
    8. public static void main(String[] args) {
    9. try {
    10. new NettyWebSocketServer68().run();
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. public void run() throws InterruptedException {
    16. // 创建线程池执行器
    17. EventLoopGroup bossGroup = new NioEventLoopGroup(1);
    18. EventLoopGroup workerGroup = new NioEventLoopGroup(8);
    19. try {
    20. // 服务器启动引导对象
    21. ServerBootstrap serverBootstrap = new ServerBootstrap();
    22. serverBootstrap.group(bossGroup, workerGroup)
    23. .channel(NioServerSocketChannel.class)
    24. .option(ChannelOption.SO_BACKLOG, 128)
    25. .option(ChannelOption.SO_KEEPALIVE, true)
    26. .handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
    27. .childHandler(new ChannelInitializer() {
    28. @Override
    29. protected void initChannel(SocketChannel socketChannel) throws Exception {
    30. ChannelPipeline pipeline = socketChannel.pipeline();
    31. // 因为使用http协议,所以需要使用http的编码器,解码器
    32. pipeline.addLast(new HttpServerCodec());
    33. // 以块方式写,添加 chunkedWriter 处理器
    34. pipeline.addLast(new ChunkedWriteHandler());
    35. /**
    36. * 说明:
    37. * 1. http数据在传输过程中是分段的,HttpObjectAggregator可以把多个段聚合起来;
    38. * 2. 这就是为什么当浏览器发送大量数据时,就会发出多次 http请求的原因
    39. */
    40. pipeline.addLast(new HttpObjectAggregator(8192));
    41. /**
    42. * 说明:
    43. * 1. 对于 WebSocket,它的数据是以帧frame 的形式传递的;
    44. * 2. 可以看到 WebSocketFrame 下面有6个子类
    45. * 3. 浏览器发送请求时: ws://localhost:7000/hello 表示请求的uri
    46. * 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接;
    47. * 是通过一个状态码 101 来切换的
    48. */
    49. pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
    50. // 自定义handler ,处理业务逻辑
    51. pipeline.addLast(new NettyWebSocketServerHandler());
    52. }
    53. });
    54. // 启动服务器,监听端口,阻塞直到启动成功
    55. ChannelFuture channelFuture = serverBootstrap.bind(8089).sync();
    56. // 阻塞直到channel关闭
    57. channelFuture.channel().closeFuture().sync();
    58. } finally {
    59. bossGroup.shutdownGracefully().sync();
    60. workerGroup.shutdownGracefully().sync();
    61. }
    62. }
    63. }

    WebSocket服务器处理器: 

    1. /**
    2. * @Description 基于netty的WebSocket服务器处理器
    3. * , TextWebSocketFrame 表示一个文本帧
    4. * @author xiao tang
    5. * @version 1.0.0
    6. * @createTime 2022年09月04日
    7. */
    8. public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler {
    9. // 读取客户端发送的请求报文
    10. @Override
    11. protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
    12. System.out.println("服务器端收到消息 = " + msg.text());
    13. // 回复消息
    14. ctx.channel().writeAndFlush(new TextWebSocketFrame(DateUtils.getNowTimestamp() + "服务器回复:" + msg.text()));
    15. }
    16. // 当web客户端连接后,触发该方法
    17. @Override
    18. public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
    19. // ctx.channel().id() 表示唯一的值
    20. System.out.println("handlerAdded 被调用, channel.id.longText = " + ctx.channel().id().asLongText());
    21. System.out.println("handlerAdded 被调用, channel.id.shortText = " + ctx.channel().id().asShortText());
    22. }
    23. // 客户端离线
    24. @Override
    25. public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
    26. // ctx.channel().id() 表示唯一的值
    27. System.out.println("handlerRemoved 被调用, channel.id.longText = " + ctx.channel().id().asLongText());
    28. }
    29. // 处理异常
    30. @Override
    31. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
    32. System.out.println("异常发生,异常消息 = " + cause.getMessage());
    33. // 关闭连接
    34. // ctx.close();
    35. ctx.channel().close();
    36. }
    37. }

    【1.2】WebSocket客户端-html(浏览器)

    hello.html:

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <title>WebSocket客户端title>
    6. <script>
    7. var socket;
    8. // 判断当前浏览器是否支持 WebSocket 编程
    9. if (!window.WebSocket) {
    10. alert("当前浏览器不支持websocket");
    11. } else {
    12. socket = new WebSocket("ws://localhost:8089/hello");
    13. // 把服务器回复的消息回显
    14. socket.onmessage = function(eval) {
    15. var widget = document.getElementById("responseText");
    16. widget.value = widget.value + "\n" + eval.data;
    17. }
    18. // 连接服务器成功
    19. socket.onopen = function(eval) {
    20. console.log(eval);
    21. var widget = document.getElementById("responseText");
    22. widget.value = "连接开启了";
    23. }
    24. // 连接关闭了
    25. socket.onclose = function(eval) {
    26. var widget = document.getElementById("responseText");
    27. widget.value = eval.value + "\n" + "连接关闭了";
    28. }
    29. }
    30. // 发送消息到服务器
    31. function send(msg) {
    32. if(!window.socket) return ;
    33. if (socket.readyState == WebSocket.OPEN) {
    34. // 通过socket 发送消息
    35. socket.send(msg);
    36. } else {
    37. alert("连接没有开启");
    38. }
    39. }
    40. script>
    41. head>
    42. <body>
    43. <form onsubmit="return false">
    44. <textarea id="message" style="height:300px; width:300px">textarea>
    45. <input type="button" value="发送消息" onclick="send(this.form.message.value)">
    46. <textarea id="responseText" style="height:300px; width:300px">textarea>
    47. <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
    48. form>
    49. body>
    50. html>

     


     【1.3】演示效果:

    1)后端运行日志

    handlerAdded 被调用, channel.id.longText = 005056fffec00001-00006848-0000000b-0f972ff18d1b6997-7d33882d
    handlerAdded 被调用, channel.id.shortText = 7d33882d
    服务器端收到消息 = hello 世界

    2)前端交互页面

     3)浏览器关闭时,后端反应:(客户端关闭,WebSocket服务器可以识别到)

    handlerRemoved 被调用, channel.id.longText = 005056fffec00001-00006848-0000000b-0f972ff18d1b6997-7d33882d

    4)服务器关闭后,前端反应:(服务器关闭,WebSocket客户端可以识别到)

     


    【1.4】WebSocket请求响应报文

    1.请求html

     2.请求WebSocket服务器

     

  • 相关阅读:
    微量元素农业主导-国稻种芯-李喜贵:功能性农业两会档案
    差分约束学习笔记
    软件测试技术之地图导航的测试用例
    Java枚举详解
    ElasticSearch中实际操作细节点
    oracle常用命令
    Android View转换为Bitmap,实现截屏效果
    义乌外贸新趋势:跨境电商引领“内外兼修”
    分层注入的设计模式-上下层文件相互包含解决办法
    手把手教你用 Milvus 和 Towhee 搭建一个 AI 聊天机器人
  • 原文地址:https://blog.csdn.net/PacosonSWJTU/article/details/126687237