Netty权威指南——WebSocket协议开发

article/2025/9/14 14:23:33

一、简介

由于HTTP协议的开销,导致他们不适于用于低延迟应用,为了解决这些问题,WebSocket将网络套接字引入到了客户端和服务端,浏览器和服务器之间可以通过套接字建立持久的连接,双方随时可以互发数据给对方,而不是之前由客户端控制的一请求一应答模式。

1.1 HTTP协议的弊端

  1. HTTP协议为半双工协议,半双工协议指数据可以在客户端和服务端两个方向上传输,但是不能同时传输,它意味着在同一时刻只有一个方向上的数据传送。
  2. HTTP消息冗长而繁琐,HTTP消息包含消息头、消息体、换行符等,通常情况采用文本方式传输,相比于其他的二进制通信协议,冗长而繁琐。
  3. 针对服务器推送的黑客攻击,例如长时间轮询。

现在很多的网站为了实现消息推送,所用的技术都是轮询,轮询是指在特定的时间间隔(如 每1秒),由浏览器对服务器发出HTTP request,然后由服务器返回最新的数据给客户端浏览器。这种传统的模式具有很明显的缺点,即浏览器需要不断地向服务器发出请求,然而HTTP request 的Header是非常冗长的,里面包含的可用数据比例可能非常低。这回占用很多的带宽和服务器资源。

为了解决HTTP协议效率低下的问题,HTML5定义了WebSocket协议,可以更好的节省服务器资源和带宽并达到实时通信。

二、WebSocket入门

2.1 WebSocket的特点

在WebSocket API中,浏览器和服务器只需要做一个握手的动作,浏览器和服务器之间就形成了一条快速通道,两者就可以直接不想传送数据了。WebSocket基于TCP双向全双工进行消息传递,在同一时刻,既可以发送,也可以接收消息。相比于HTTP的半双工协议,性能得到很大的提升。

  • 单一的TCP连接,采用双全工模式通信;
  • 对代理、防火墙、和路由器透明;
  • 无头部信息、Cookie、和身份验证;
  • 无安全开销;
  • 通过ping/pong帧保持链路激活;
  • 服务器可以主动传递消息给客户端,不在需要客户端轮询;

浏览器通过JavaScript向服务器发出建立WebSocket连接的请求,连接建立后,客户端和服务端可以通过TCP连接直接交换数据,因为WebSocket连接本质上就是一个TCP连接,所以在数据传输的稳定性和数据传输量的大小方面,和轮询以及Comet技术相比,具有很大的优势,所以可以说WebSocket是未来实时Web应用的首选方案。

2.2 WebSocket 连接建立

握手请求
握手响应
客户端
服务端

为建立一个WebSocket链接,客户端浏览器首先要向服务器发送发起一个HTTP请求;这个请求和通常的请求不同,包含了一些附加头信息,其中附加头信息”Upgrade:WebSocket“ 表名这是一个申请协议升级的HTTP请求。服务端解析这些附加的头信息,然后生成应答消息返回给客户端,客户端和服务端的WebSocket连接就建立起来了。双方可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务端的某一方主动关闭连接。

请求消息中的”Sec-WebSocket-Key“ 是随机的,服务器端会用这些数据来构造出一个 SHA-1 的信息摘要,把”Sec-WebSocket-Key“ 加上一个字符串,使用SHA-1 加密,然后进行BASE-64编码,将结果作为”Sec-WebSocket-Accept“头的值,返回给客户端。

2.3 WebSocket 生命周期

握手成功后,服务器端和客户端就可以通过”message“的方式进行通信了,一个消息由一个或者多个组成,WebSocket 的消息并不一定对应一个特定的网络层的帧,它可以被分割成多个帧或者被合并。

都有自己对应的类型,属于同一个消息的多个帧具有相同类型的数据,从广义上讲,数据类型可以是文本数据、二进制数据和控制帧(协议级信令,如信号)

2.4 WebSocket 连接关闭

为关闭WebSocket连接,客户端和服务端需要通过一个安全的方法关闭底层TCP连接以及TLS会话,如果合适,丢弃任何可能已经接受的字节,必要时(比如收到攻击时)可以通过任何可用的手段关闭连接。
底层的TCP连接,在正常情况下,应该首先由服务器关闭,在异常情况下(例如在一个合理的时间周期后没有接收到服务器的TCP Close),客户端可以发起TCP Close。因此,当服务器被指示关闭WebSocket连接时,它应该立即发送一个TCP Close操作;客户端应该等待服务器的TCP Close。
WebSocket的握手关闭消息带有一个状态码和一个可选的关闭原因,它必须按照协议要求发送一个Close控制帧,当对端接收到关闭控制帧指令时,需要主动关闭WebSocket连接。

三、Netty WebSocket 协议开发

3.1 WebSocketServer

package com.lsh.netty.websocket;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;/*** @author :LiuShihao* @date :Created in 2021/6/28 12:23 下午* @desc :* WebSocketServer服务端功能介绍:* 支持WebSocket的浏览器通过WebSocket协议发送请求消息给服务端,服务端对请求消息进行判断,如果是合法的WebSocket请求* 则获取请求消息文本,并在后面追加字符串"欢迎使用Netty WebSocket 服务,现在时刻:系统时间"*/
public class WebSocketServer {public void run(int port) throws Exception{NioEventLoopGroup boosGroup = new NioEventLoopGroup();NioEventLoopGroup workerGroup = new NioEventLoopGroup();try{ServerBootstrap b = new ServerBootstrap();b.group(boosGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//添加HttpServerCodec:将请求和应答消息解码或者解码为HTTP消息pipeline.addLast("http-codec",new HttpServerCodec());//增加HttpObjectAggregator:目的是将HTTP消息的多个部分组合成一条完整的HTTP消息pipeline.addLast("aggregator",new HttpObjectAggregator(65536));// ChunkedWriteHandler:来向客户端发送HTML5文件,它主要用于支持浏览器和服务端进行WebSocket通信ch.pipeline().addLast("http-chunked",new ChunkedWriteHandler());//增加WebSocket服务端Handlerpipeline.addLast("handler",new WebSocketServerHandler());}});Channel ch = b.bind(port).sync().channel();System.out.println("Web socket server started at port "+port);System.out.println("open your browser and navigate to http://localhost:"+port+"/");ch.closeFuture().sync();}finally {boosGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}public static void main(String[] args) throws Exception {int port = 8080;new WebSocketServer().run(port);}
}

3.2 WebSocketServerHandler

package com.lsh.netty.websocket;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import lombok.extern.slf4j.Slf4j;import java.util.Date;/*** @author :LiuShihao* @date :Created in 2021/6/28 12:28 下午* @desc :*/
@Slf4j
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {private WebSocketServerHandshaker handshaker;@Overrideprotected void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {//传统http接入:第一次握手消息有HTTP协议承载,所以它是一个HTTP消息,,执行handleHTTPRequest方法来处理WebSocket握手请求if (msg instanceof FullHttpRequest){handleHttpRequest(ctx,(FullHttpRequest) msg);}else if (msg instanceof WebSocketFrame){//WebSocket接入:链路建立成功后的操作handleWebSocketFrame(ctx,(WebSocketFrame) msg);}}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.flush();}/**** 处理WebSocket握手请求* @param ctx* @param req* @throws Exception*/private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {// 如果解码失败返回异常// 对握手消息进行判断,消息头中没有包含Upgrade字段或者它的值不是websocket 则返回HTTP400响应if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))){sendHttpResponse(ctx,req,new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));return;}//握手请求简单校验通过后,开始构造握手工厂,WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket", null, false);//创建握手处理类WebSocketServerHandshaker,通过它构造握手响应消息返回给客户端//同时将WebSocket相关的编码和解码类动态的添加到 ChannelPipeline中,用于WebSocket消息的编解码//添加了WebSocket Encoder 和 WebSocket Decoder 之后,服务端就可以自动对WebSocket消息进行编解码了,后面的业务handler可以直接对WebSocket对象进行操作handshaker = wsFactory.newHandshaker(req);if (handshaker == null){WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());}else {handshaker.handshake(ctx.channel(),req);}}/*** 客户端通过文本框提交请求消息给服务端,WebSocketServerHandler接收到的已经是解码后的WebSocketFrame消息了(WebSocket握手应答时动态增加了编解码handler)。* 首先需要对控制帧进行判断:如果是关闭链路的控制消息,就调用WebSocketServerHandlershaker的close方法关闭WebSocket连接* 如果是维持链路的Ping消息,则构造Pong消息返回* 本例程的WebSocket通信双方使用的都是文本消息,所以对请求消息的类型进行判断,不是文本的抛出异常。* 最后,从TextWebSocketFrame中获取请求消息字符串,对它处理后构造新的TextWebSocketFrame消息返回给客户端,* 由于握手应答时动态增加了TextWebSocketFrame的编码类,所以,可以直接发送TextWebSocketFrame对象。* @param ctx* @param fream* @throws Exception*/private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame fream) throws Exception {//判断是否关闭链路的指令if (fream instanceof CloseWebSocketFrame){handshaker.close(ctx.channel(),((CloseWebSocketFrame) fream).retain());return;}//判断是否是Ping消息if (fream instanceof PingWebSocketFrame){ctx.channel().write(new PongWebSocketFrame(fream.content().retain()));return;}//本实例只支持文本消息,不支持二进制消息if (! (fream instanceof TextWebSocketFrame)){throw new UnsupportedOperationException(String.format("%s frame types not supported",fream.getClass().getName()));}//返回应答消息String request = ((TextWebSocketFrame) fream).text();System.out.println("request:"+request);ctx.channel().write(new TextWebSocketFrame(request+", 欢迎使用Netty WebSocket 服务,现在时刻:"+new Date().toString()));}/*** 返回响应* @param ctx* @param req* @param response*/private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, DefaultFullHttpResponse response) {//返回应答给客户端if(response.status().code() != 200){ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);response.content().writeBytes(buf);buf.release();//注意:此处设置请求头 response.content().readableBytes() 返回int类型response.headers().set("Content-Length",String.valueOf(response.content().readableBytes()));}//如果是非Keep-Alive ,关闭连接ChannelFuture f = ctx.channel().writeAndFlush(response);if (!req.headers().get("Connection").equals("keep-alive") || response.status().code() != 200){f.addListener(ChannelFutureListener.CLOSE);}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

3.3 WebSocketServer.html

<html>
<head><meta charset="UTF-8">netty websocket 时间服务器
</head>
<br>
<body>
<br>
<script type="text/javascript">var socket;if(!window.WebSocket){window.WebSocket = window.MozWebSocket;}if(window.WebSocket){var  socket = new WebSocket("ws://localhost:8080/websocket");socket.onmessage = function(event){var ta = document.getElementById('responseText');ta.value="";ta.value = event.data};socket.onopen = function(event){var ta = document.getElementById('responseText');ta.value = '';ta.value = "打开websocket服务正常,浏览器支持websocket!";};socket.onclose = function(event){var ta = document.getElementById('responseText');ta.value = '';ta.value = "websocket关闭!";};}else{alert("抱歉,您的浏览器不支持WebSocket 协议!");}function send(message){if(!window.WebSocket){return;}if(socket.readyState == window.WebSocket.OPEN){socket.send(message);}else{alert("WebSocket 还没有建立连接!")}}
</script>
<form onsubmit="return false;"><input type="text" name="message" vaule="Netty权威指南 WebSocket协议开发"/><br><br><input type="button" value="发送 WebSocket 请求消息" onclick="send(this.form.message.value)"><hr color="blue"/><h3>服务端返回的应答消息</h3><textarea id="responseText" style="width:500px;height:300px;"></textarea>
</form>
</body>
</html>

3.4 运行测试

在浏览器中打开html页面:

后台服务器还没有启动:
在这里插入图片描述
现在启动WebSocketServer
在这里插入图片描述

在页面发送请求:
在这里插入图片描述

控制台日志:
在这里插入图片描述

页面返回:
在这里插入图片描述
流程结束!

四、总结

源码

代码已上传仓库:https://gitee.com/L1692312138/netty-repository


http://chatgpt.dhexx.cn/article/FpmdbTep.shtml

相关文章

netty权威指南 微云_《Netty权威指南》(二)NIO 入门

2.1 BIO 采用 BIO 通信模型的服务器&#xff0c;通常由一个独立的 Acceptor 线程负责监听客户端的连接&#xff0c;它接收到客户端连接请求之后为每个客户端创建一个新的线程进行处理&#xff0c;处理完成后&#xff0c;通过输出流返回应答给客户端&#xff0c;线程销毁。 grap…

netty 权威指南~第11章——WebSoket协议开发

本章主要学习内容如下&#xff1a; 1、WebSocket入门 2、Netty WebSocket协议开发 第节一&#xff1a;WebSocket入门 WebSocket是HTML5开始提供的一种浏览器与服务器进行全双通信的网络技术&#xff0c;WebSocket通信协议与2011年被IEIF定位标准RFC6455&#xff0c;WebsSock…

Netty权威指南 第2版

https://www.cnblogs.com/plxz/p/9910493.html 第一章  Java的I/O演进之路 1.I/O基础入门 1.Linux网络I/O模型简介 1.阻塞I/O模型&#xff1a;最常用的I/O模型&#xff0c;缺省情况下&#xff0c; 所有文件操作都是阻塞的 2.非阻塞I/O模型&#xff1a;recvform从应用层到内…

Netty权威指南总结(一)

一、为什么选择Netty&#xff1a; API使用简单&#xff0c;开发门槛低&#xff0c;屏蔽了NIO通信的底层细节。 功能强大&#xff0c;预制了很多种编解码功能&#xff0c;支持主流协议。 定制能力强&#xff0c;可以通过ChannelHandler对通信框架进行灵活地拓展。 性能高、成熟、…

Netty权威指南

Chapter1.java I/O演进之路 1.1I/O基础入门 在java 1.4之前&#xff0c;java程序员在开发高性能I/O程序的时候&#xff0c;会面临的问题主要有&#xff1a; 1.没有数据缓冲区&#xff0c;I/O性能存在问题 2.没有c或者c中的Channel概念&#xff0c;只有输入和输出流 3.同步…

三维视觉传感器的类型

三角法测量原理 视觉传感器的坐标系统 单一摄像机二维传感器 点结构光视觉传感器 线结构光视觉传感器 条纹结构光视觉传感器 条纹编码三维视觉传感器 彩色编码视觉传感器 被动双目视觉传感器 编码照明双目视觉传感器

传感器sensor

传感器分类 转换原理 传感器名称 典型应用 转换形式 中间参量 电 参 数 电 阻 移动电位器角点 改变电阻 电位器传感器 位移 改变电阻丝或片尺寸 电阻丝应变传感器、半导体应变传感器 微应变、力、负荷 利用电阻的温度效应 热丝传感器 气流速度、液体流量 电阻…

多任务多传感器数据融合实现3D目标检测

转载自&#xff1a;自动驾驶之心 01 引言 本文介绍一篇uber公司在CVPR上发表的一篇论文&#xff0c;即使用多种传感器&#xff08;LiDAR和RGB相机&#xff09;数据&#xff0c;以及多任务进行数据融合&#xff0c;实现准确高效的3D目标检测。简而言之&#xff0c;自动驾驶领域…

压力传感器

压力传感器 压力传感器是最常用的一种传感器&#xff0c;其应用范围有各种工业互通环境&#xff0c;涉及航空&#xff0c;航天&#xff0c;军工&#xff0c;石化&#xff0c;电力等。按照不同的测试&#xff0c;压力类型可分表压传感器&#xff0c;差压传感器&#xff0c;绝压…

多传感器融合定位(一)——3D激光里程计

目录 一、点云地图整体流程 二、激光里程计方案 2.1 ICP点到点 2.1.1 ICP推导 2.1.2 ICP改进 2.2 NDT 2.2.1 NDT推导 2.2.2 NDT改进 2.3 LOAM系 2.3.1 LOAM 2.3.2 A-LOAM 2.3.3 LEGO-LOAM 2.4 数据集及评价指标 2.4.1 KITTI简介 2.4.2 指标 一、点云地图整体流程…

MMDetection3D 1.1:统一易用的 3D 感知平台

自从两年前 MMDetection3D 发布以来&#xff0c;我们收到了很多用户的反馈&#xff0c;其中有不少有价值的建议&#xff0c;同时也有很多吐槽&#xff08;当然我们非常欢迎大家来吐槽&#xff09;&#xff0c;也有很多社区用户参与到我们代码库的完善中&#xff0c;我们也非常高…

关于传感器

人的眼睛就是传感器。人对光的感应靠眼睛&#xff0c;在光亮条件下&#xff0c;人眼能分辨各种颜色。首先从人眼构造开始讲起。眼睛最里面的膜是视网膜&#xff0c;它布满了整个眼睛后部的内壁。当眼球适当地聚焦时&#xff0c;来自眼睛外部的光在视网膜上成像。在视网膜表面分…

ToF 3D视觉传感技术详解、应用场景和市场前景

‍转载自&#xff1a;3d tof 现行的深度传感镜头作为智能手机的一大创新&#xff0c;已在目前主流智能手机上广泛应用。现因苹果在最新版iPad Pro上搭载了D-ToF&#xff08;直接飞行时间法&#xff09;深度传感镜头引起了极大的关注&#xff0c;推动了3D视觉在消费场景的新应用…

奥比中光Orbbec Astra Pro RGBD 3D视觉传感器在ROS(indigo和kinetic)使用说明

作者&#xff1a;童虎 编辑&#xff1a;3D视觉开发者社区 Orbbec Astra Pro传感器在ROS&#xff08;indigo和kinetic&#xff09;使用说明 这款摄像头使用uvc输入彩色信息&#xff0c;需要libuvc和libuvc_ros这样才能在ROS正常使用彩色功能。 请在下面网址&#xff0c;分别下载…

3D传感相关

这里涉及了各种3D传感相关的工作&#xff0c;包含单目、双目、ToF等传感器&#xff0c;涉及了计算摄影、ToF深度测量、双目深度估计、多视角深度估计、相机离线标定、相机在线标定、NeRF等技术&#xff0c;可谓3D视觉集大成者。 这里简单介绍下本文将会提到的两种3D成像技术&a…

3D激光雷达SLAM算法学习02——3D激光雷达传感器

1.本篇思维导图 2. 3D激光雷达传感器分类 3. 机械激光雷达 直观视频感受&#xff1a;Velodyne 优点&#xff1a;360视野&#xff0c;精度高&#xff0c;工作稳定&#xff0c;成像快 缺点&#xff1a;成本较高&#xff0c;不符合自动驾驶车规&#xff0c;生命周期短&#xff0c…

Lidar 3D传感器点云数据与2D图像数据的融合标注

2D&3D融合 以自动驾驶场景为例&#xff0c;自动驾驶汽车需要使用传感器来识别车辆周围的物理环境&#xff0c;用来捕获2D视觉数据&#xff0c;同时在车辆顶部安装雷达,用以捕捉精确目标定位的3D位置数据。 激光雷达生成的点云数据可用于测量物体的形状和轮廓&#xff0c;估…

3D视觉传感器产业现状-2018年

总结一下&#xff0c;主要参考于MEMS市场调研 MEMS.ME&#xff0c; 必须找专业的分析和咨询公司才行&#xff0c;或者活跃在前沿的资深行业专家。市场分析只能给出大致的销售状况&#xff0c;还不能给出详细的技术数据对比。 从三维重建的算法、技术原理和效果参数上分析&#…

三种常见的3D传感器比较

作者 | dianyunPCL 编辑 | 点云PCL 本文只做学术分享&#xff0c;如有侵权&#xff0c;联系删文 点击下方卡片&#xff0c;关注“自动驾驶之心”公众号 ADAS巨卷干货&#xff0c;即可获取 点击进入→自动驾驶之心【硬件交流】技术交流群 摘要 在过去的十年里&#xff0c;3D传感…

3D视觉|了解下工业上常见的3D相机

说起相机&#xff0c;大家估计都很熟悉了。那么相对于平常使用的2D相机&#xff0c;3D相机又有哪些区别呢&#xff0c;顾名思义&#xff0c;3D相机可以让我们获取我们物理世界的空间信息&#xff0c;即立体三维的物理信息。多了一个维度的信息&#xff0c;似乎打开了一扇大门&a…