WebSocket的数据是以帧的形式传递,需要指定 WebSocketServerProtocolHandler 将原先的Http协议升级为WebSocket协议
编写一个浏览器页面的WebSocket服务案例,页面会根据状态码来确定WebSocket连接,当状态码为101时表示连接成功
代码:
客户端代码:
package websocket;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
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;
import io.netty.handler.timeout.IdleStateHandler;
import keepalive.MyServerHandler;
import java.util.concurrent.TimeUnit;
/* WebSocket服务端代码 */
public class MyWebServer {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup bossGroup = new NioEventLoopGroup(1); //管理连接请求线程组
EventLoopGroup workGroup = new NioEventLoopGroup(); //进行业务处理线程组
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workGroup) //设置两个线程组
.channel(NioServerSocketChannel.class) //使用 NioSocketChannel 作为服务器通道实现
.handler(new LoggingHandler(LogLevel.INFO)) //在BossGroup添加一个日志处理器
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new HttpServerCodec()); //因为基于Http协议,需要使用Http的编码解码器
pipeline.addLast(new ChunkedWriteHandler()); //以块的方式写,需要添加ChunkedWrite处理器
/*
* Http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合起来
* 所以浏览器发送大量数据时就会发出多次Http请求
*/
pipeline.addLast(new HttpObjectAggregator(8192));
/* WebSocket的数据是以帧的形式传递,需要指定 WebSocketServerProtocolHandler和自定义处理帧数据Handler
* WebSocketServerProtocolHandler的功能是将Http协议升级为WebSocket协议来保持长连接,并解析 websocket 访问地址
* 浏览器访问WebSocket的形式是 ws://localhost:7070/访问路径,参数就为 “/访问路径”
* 例如访问的是 ws://localhost:7070/rog16hp,这里的handler就要指定为 "/rog16hp"
*/
pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
pipeline.addLast(new MyTextWebSocketFrameHandler()); //处理WebSocket传递的数据(帧形式),需要指定对应WebSocketFrame的子类
}
});
ChannelFuture future = serverBootstrap.bind(7070).sync();
future.channel().closeFuture().sync(); //对关闭通道进行监听(当有关闭通道的消息时才进行监听)
} finally {
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
}
帧数据处理Handler:
package websocket;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import java.time.LocalDateTime;
/* 传递的是文本数据,指定 WebSocketFrame 的子类 TextWebSocketFrame 进行帧处理 */
public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
System.out.println("服务器收到消息: "+msg.text());
//回复消息
ctx.channel().writeAndFlush( new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + "" + msg.text()) );
}
//当Web客户端连接后就会被触发
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//ID标识唯一的值,LongText是唯一的,shutText并不是唯一的
System.out.println("HandlerAdder被调用!"+ctx.channel().id().asLongText());
System.out.println("HandlerAdder被调用!"+ctx.channel().id().asShortText());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
System.out.println("HandlerRemoved被调用!"+ctx.channel().id().asLongText());
}
//异常发生时处理
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("异常发生原因: "+cause.getMessage());
ctx.close(); //关闭通道
}
}
页面:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>WebSocket测试页面</title>
</head>
<body>
<script>
var socket;
//当前浏览器是否支持websocket
if(window.WebSocket){
socket = new WebSocket("ws://localhost:7070/hello");
//相当于channelReado,msg就是收到服务器端回送的消息
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");
rt.value = "连接开启了";
}
//感知到连接关闭
socket.onclose = function (ev){
var rt = document.getElementById("responseText");
rt.value = rt.value = "\n" + "连接关闭.....";
}
//发生消息到服务器
function send(message){
if(!window.socket){
return; //socket没有创建好
}
if(socket.readyState == WebSocket.OPEN){
socket.send(message) //通过Socket发送消息,消息类型需要和 帧处理类型保持一直
}else{
alert("连接未开启!")
}
}
}else{
alert("当前浏览器不支持WebSocket")
}
</script>
<form onsubmit="return false">
<textarea name="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>
测试:
运行客户端代码后打开页面,输入消息查看服务端内容
101状态码代表连接成功
当有消息输入时,服务端将收到数据并返回
关闭浏览器时将会触发handlerRemoved方法