9.基于netty实现WebSocket服务器
Posted PacosonSWJTU
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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 教程 - 阮一峰的网络日志):
- 建立在 TCP 协议之上,服务器端的实现比较容易。
- 与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
- 数据格式比较轻量,性能开销小,通信高效。
- 可以发送文本,也可以发送二进制数据。
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
3)需求描述:
- ① Http协议是无状态的, 浏览器和服务器间的请求响应一次,下一次会重新创建连接;
- ② 要求:实现基于webSocket的长连接的全双工的交互;
- ③ 改变Http协议多次请求的约束,实现长连接了, 服务器可以发送消息给浏览器;
- ④ 客户端浏览器和服务器端会相互感知,比如服务器关闭了,浏览器会感知,同样浏览器关闭了,服务器会感知;
【1.1】WebSocket服务器
/**
* @Description 基于netty的webSocket服务器
* @author xiao tang
* @version 1.0.0
* @createTime 2022年09月04日
*/
public class NettyWebSocketServer68
public static void main(String[] args)
try
new NettyWebSocketServer68().run();
catch (InterruptedException e)
e.printStackTrace();
public void run() throws InterruptedException
// 创建线程池执行器
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
try
// 服务器启动引导对象
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioserverSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new LoggingHandler(LogLevel.INFO)) // 为 bossGroup 添加 日志处理器
.childHandler(new ChannelInitializer<SocketChannel>()
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception
ChannelPipeline pipeline = socketChannel.pipeline();
// 因为使用http协议,所以需要使用http的编码器,解码器
pipeline.addLast(new HttpServerCodec());
// 以块方式写,添加 chunkedWriter 处理器
pipeline.addLast(new ChunkedWriteHandler());
/**
* 说明:
* 1. http数据在传输过程中是分段的,HttpObjectAggregator可以把多个段聚合起来;
* 2. 这就是为什么当浏览器发送大量数据时,就会发出多次 http请求的原因
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/**
* 说明:
* 1. 对于 WebSocket,它的数据是以帧frame 的形式传递的;
* 2. 可以看到 WebSocketFrame 下面有6个子类
* 3. 浏览器发送请求时: ws://localhost:7000/hello 表示请求的uri
* 4. WebSocketServerProtocolHandler 核心功能是把 http协议升级为 ws 协议,保持长连接;
* 是通过一个状态码 101 来切换的
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
// 自定义handler ,处理业务逻辑
pipeline.addLast(new NettyWebSocketServerHandler());
);
// 启动服务器,监听端口,阻塞直到启动成功
ChannelFuture channelFuture = serverBootstrap.bind(8089).sync();
// 阻塞直到channel关闭
channelFuture.channel().closeFuture().sync();
finally
bossGroup.shutdownGracefully().sync();
workerGroup.shutdownGracefully().sync();
WebSocket服务器处理器:
/**
* @Description 基于netty的WebSocket服务器处理器
* , TextWebSocketFrame 表示一个文本帧
* @author xiao tang
* @version 1.0.0
* @createTime 2022年09月04日
*/
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame>
// 读取客户端发送的请求报文
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception
System.out.println("服务器端收到消息 = " + msg.text());
// 回复消息
ctx.channel().writeAndFlush(new TextWebSocketFrame(DateUtils.getNowTimestamp() + "服务器回复:" + msg.text()));
// 当web客户端连接后,触发该方法
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception
// ctx.channel().id() 表示唯一的值
System.out.println("handlerAdded 被调用, channel.id.longText = " + ctx.channel().id().asLongText());
System.out.println("handlerAdded 被调用, channel.id.shortText = " + ctx.channel().id().asShortText());
// 客户端离线
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception
// ctx.channel().id() 表示唯一的值
System.out.println("handlerRemoved 被调用, channel.id.longText = " + ctx.channel().id().asLongText());
// 处理异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception
System.out.println("异常发生,异常消息 = " + cause.getMessage());
// 关闭连接
// ctx.close();
ctx.channel().close();
【1.2】WebSocket客户端-html(浏览器)
hello.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket客户端</title>
<script>
var socket;
// 判断当前浏览器是否支持 WebSocket 编程
if (!window.WebSocket)
alert("当前浏览器不支持websocket");
else
socket = new WebSocket("ws://localhost:8089/hello");
// 把服务器回复的消息回显
socket.onmessage = function(eval)
var widget = document.getElementById("responseText");
widget.value = widget.value + "\\n" + eval.data;
// 连接服务器成功
socket.onopen = function(eval)
console.log(eval);
var widget = document.getElementById("responseText");
widget.value = "连接开启了";
// 连接关闭了
socket.onclose = function(eval)
var widget = document.getElementById("responseText");
widget.value = eval.value + "\\n" + "连接关闭了";
// 发送消息到服务器
function send(msg)
if(!window.socket) return ;
if (socket.readyState == WebSocket.OPEN)
// 通过socket 发送消息
socket.send(msg);
else
alert("连接没有开启");
</script>
</head>
<body>
<form onsubmit="return false">
<textarea id="message" style="height:300px; width:300px"></textarea>
<input type="button" value="发送消息" onclick="send(this.form.message.value)">
<textarea id="responseText" style="height:300px; width:300px"></textarea>
<input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
</form>
</body>
</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服务器
以上是关于9.基于netty实现WebSocket服务器的主要内容,如果未能解决你的问题,请参考以下文章
探讨Netty获取并检查Websocket握手请求的两种方式
Spring Boot + Netty + WebSocket 实现消息推送