WebSocket长连接是基于处理器 HttpObjectAggregator与WebSocketServerProtocolHandler的
HttpObjectAggregator
1.http数据在传输过程中是分段,HttpObjectAggregator就是可以将多个段聚合。
2.这就是为什么当浏览器发送大量数据时,就会发出多次http请求。
WebSocketServerProtocolHandler
1.对于webSocket是以帧的形式传递
2.可以看到webSocketFrame,下面有六个子类
3.浏览器请求时 ws: //localhost:7000/hello 表示请求得uri
4.WebSocketServerProtocolHandler核心功能,将http协议升级为ws协议,保持长连接
5.通过状态码101
具体实现代码:
服务端:
package com.jym.websocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
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.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
/**
* @program: NettyPro
* @description:
* @author: jym
* @create: 2020/02/10
*/
public class MyServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class)
// 在bossGroup增加日志处理器
.handler(new LoggingHandler(LogLevel.INFO))
.option(ChannelOption.SO_BACKLOG,128)
.childOption(ChannelOption.SO_KEEPALIVE,true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
// 因为基于http协议,使用http的编码和解码器
pipeline.addLast("JymHttpSeverCodec",new HttpServerCodec());
// 是以块方式写,添加ChunkedWriteHandler处理器
pipeline.addLast("JymChunkedWriteHandler",new ChunkedWriteHandler());
/**
说明:
1.http数据在传输过程中是分段,HttpObjectAggregator就是可以将多个段聚合
2.这就是为什么当浏览器发送大量数据时,就会发出多次http请求
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/**
说明:
1.对于webSocket是以帧的形式传递
2.可以看到webSocketFrame,下面有六个子类
3.浏览器请求时 ws: //localhost:7000/hello 表示请求得uri
4.WebSocketServerProtocolHandler核心功能,将http协议升级为ws协议,保持长连接
5.通过状态码101
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
// 自定义的handler,处理业务逻辑
pipeline.addLast(new MyServerHandler());
}
});
System.out.println("服务器已启动");
ChannelFuture sync = serverBootstrap.bind(9000).sync();
sync.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
处理器:
package com.jym.websocket;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.time.LocalDateTime;
/**
* @program: NettyPro
* @description: TextWebSocketFrame 表示一个文本帧(frame)
* @author: jym
* @create: 2020/02/10
*/
public class MyServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {
System.out.println("服务器端收到消息" + textWebSocketFrame.text());
// 回复消息
Channel channel = channelHandlerContext.channel();
channel.writeAndFlush(
new TextWebSocketFrame("服务器时间:"+ LocalDateTime.now()+""+textWebSocketFrame.text()));
}
/**
* web客户连接,触发此方法
*/
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
// id 表示唯一的一个值,LongText 是唯一的,shortText 不是唯一的
System.out.println("handlerAdded被调用"+ctx.channel().id().asLongText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("handlerAdded被调用"+ctx.channel().id().asLongText());
}
/**
* 处理异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常发生"+cause.getMessage());
}
}
客户端代码(通过html)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script>
var socket;
// 判断当前浏览器是否支持webSocket编程
if(window.WebSocket){
debugger;
socket = new WebSocket("ws://localhost:9000/hello");
// 相当于channelRead0,ev 收到服务器端回送的消息
socket.onmessage = function (ev) {
var rt = document.getElementById("responseText");
rt.value = rt.value + '\n' + ev.data;
};
socket.onopen = function (ev) {
// 相当于连接开启
var rt = document.getElementById("responseText");
alert(rt);
rt.value = "连接开启了..."
}
socket.onclose = function (ev) {
// 连接关闭
var rt = document.getElementById("responseText");
rt.value = rt.value + '\\n' +"连接关闭了..."
}
}else {
alert("当年浏览器不支持webSocket")
}
function send(message) {
if(!window.socket){
return;
}
if(socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert("连接未开启");
}
}
</script>
<body>
<form onsubmit="return false">
<label>
<textarea name="message" style="height: 300px; width: 300px" ></textarea>
</label>
<input type="button" value="发送消息" onclick="send(this.form.message.value)">
<label>
<textarea id="responseText" style="height: 300px; width: 300px" ></textarea>
</label>
<input type="button" value="清空内容"
onclick="document.getElementById('responseText').value=''">
</form>
</body>
</html>
启动客户端,并启动html后:
注意:
new WebSocketServerProtocolHandler("/hello")的/hello,必须与页面的url一致