前言:Netty入门系列文章大多参考尚硅谷韩顺平老师的《Netty教程》和《Netty in Action》
中文版,以及bugstack虫洞栈的netty 4.x系列教程。
长连接:长连接,指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。
HTTP是不支持长连接的,所以本次我们需要使用到WebSocket。
WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket是基于HTTP协议的,下面是HTTP升级到WebSocket的过程示意图。
下面正式开始本章的内容—基于netty
的websocket
长连接。
项目结果图
代码实现
服务端
public class MyServer {
public static void main(String[] args) throws Exception{
// 负责连接的NioEventLoopGroup线程数为1
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup wokerGroup = new NioEventLoopGroup();
try{
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,wokerGroup).channel(NioServerSocketChannel.class)
// 加入日志
.handler(new LoggingHandler(LogLevel.INFO))
// 自定义channel初始化器
.childHandler(new WebSocketChannelInitializer());
// 绑定本机的8885端口
ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8885)).sync();
// 异步回调-关闭事件
channelFuture.channel().closeFuture().sync();
}finally {
bossGroup.shutdownGracefully();
wokerGroup.shutdownGracefully();
}
}
}
再看看WebSocketChannelInitializer
的代码
/**
* websocket通道初始化器
*
* @author linzhe
* @date 2021/03/12
*/
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
//websocket协议本身是基于http协议的,所以这边也要使用http解编码器
pipeline.addLast(new HttpServerCodec());
//以块的方式来写的处理器
pipeline.addLast(new ChunkedWriteHandler());
//netty是基于分段请求的,HttpObjectAggregator的作用是将请求分段再聚合,参数是聚合字节的最大长度
pipeline.addLast(new HttpObjectAggregator(8192));
// 使用websocket协议
//ws://server:port/context_path
//参数指的是contex_path
pipeline.addLast(new WebSocketServerProtocolHandler("/world"));
//websocket定义了传递数据的中frame类型
pipeline.addLast(new TextWebSocketFrameHandler())
;
}
}
ChunkedWriteHandler:一个ChannelHandler,它增加了对异步写入大数据流的支持,既不花费大量内存也不获取OutOfMemoryError。诸如文件传输之类的大数据流需要在ChannelHandler实现中进行复杂的状态管理。 ChunkedWriteHandler管理这些复杂的状态,以便您可以毫无困难地发送大型数据流。
TextWebSocketFrameHandler
是我们自定义处理长连接的handler
public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("收到消息:"+msg.text());
/**
* writeAndFlush接收的参数类型是Object类型,但是一般我们都是要传入管道中传输数据的类型,比如我们当前的demo
* 传输的就是TextWebSocketFrame类型的数据
*/
ctx.channel().writeAndFlush(new TextWebSocketFrame("服务时间:"+ LocalDateTime.now()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常发生");
ctx.close();
}
}
前端
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket客户端</title>
</head>
<body>
<script type="text/javascript">
var socket;
//如果浏览器支持WebSocket
if(window.WebSocket){
//参数就是与服务器连接的地址
socket = new WebSocket("ws://localhost:8885/world");
//客户端收到服务器消息的时候就会执行这个回调方法
socket.onmessage = function (event) {
var ta = document.getElementById("responseText");
ta.value = ta.value + "n"+event.data;
}
//连接建立的回调函数
socket.onopen = function(event){
var ta = document.getElementById("responseText");
ta.value = "连接开启";
}
//连接断掉的回调函数
socket.onclose = function (event) {
var ta = document.getElementById("responseText");
ta.value = ta.value +"n"+"连接关闭";
}
}else{
alert("浏览器不支持WebSocket!");
}
//发送数据
function send(message){
if(!window.WebSocket){
return;
}
//当websocket状态打开
if(socket.readyState == WebSocket.OPEN){
socket.send(message);
}else{
alert("连接没有开启");
}
}
</script>
<form onsubmit="return false">
<textarea name = "message" style="width: 400px;height: 200px"></textarea>
<input type ="button" value="发送数据" onclick="send(this.form.message.value);">
<h3>服务器输出:</h3>
<textarea id ="responseText" style="width: 400px;height: 300px;"></textarea>
<input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空数据">
</form>
</body>
</html>
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。