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>() {@Overrideprotected 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> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {System.out.println("服务器收到消息: "+msg.text());//回复消息ctx.channel().writeAndFlush( new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + "" + msg.text()) );}//当Web客户端连接后就会被触发@Overridepublic 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());}@Overridepublic void handlerRemoved(ChannelHandlerContext ctx) throws Exception {System.out.println("HandlerRemoved被调用!"+ctx.channel().id().asLongText());}//异常发生时处理@Overridepublic 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;//当前浏览器是否支持websocketif(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方法
