WebSocket的数据是以帧的形式传递,需要指定 WebSocketServerProtocolHandler 将原先的Http协议升级为WebSocket协议
编写一个浏览器页面的WebSocket服务案例,页面会根据状态码来确定WebSocket连接,当状态码为101时表示连接成功

代码:

客户端代码:

  1. package websocket;
  2. import io.netty.bootstrap.ServerBootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.ChannelPipeline;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioServerSocketChannel;
  10. import io.netty.handler.codec.http.HttpObjectAggregator;
  11. import io.netty.handler.codec.http.HttpServerCodec;
  12. import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
  13. import io.netty.handler.logging.LogLevel;
  14. import io.netty.handler.logging.LoggingHandler;
  15. import io.netty.handler.stream.ChunkedWriteHandler;
  16. import io.netty.handler.timeout.IdleStateHandler;
  17. import keepalive.MyServerHandler;
  18. import java.util.concurrent.TimeUnit;
  19. /* WebSocket服务端代码 */
  20. public class MyWebServer {
  21. public static void main(String[] args) throws InterruptedException {
  22. EventLoopGroup bossGroup = new NioEventLoopGroup(1); //管理连接请求线程组
  23. EventLoopGroup workGroup = new NioEventLoopGroup(); //进行业务处理线程组
  24. try {
  25. ServerBootstrap serverBootstrap = new ServerBootstrap();
  26. serverBootstrap.group(bossGroup,workGroup) //设置两个线程组
  27. .channel(NioServerSocketChannel.class) //使用 NioSocketChannel 作为服务器通道实现
  28. .handler(new LoggingHandler(LogLevel.INFO)) //在BossGroup添加一个日志处理器
  29. .childHandler(new ChannelInitializer<SocketChannel>() {
  30. @Override
  31. protected void initChannel(SocketChannel ch) throws Exception {
  32. ChannelPipeline pipeline = ch.pipeline();
  33. pipeline.addLast(new HttpServerCodec()); //因为基于Http协议,需要使用Http的编码解码器
  34. pipeline.addLast(new ChunkedWriteHandler()); //以块的方式写,需要添加ChunkedWrite处理器
  35. /*
  36. * Http数据在传输过程中是分段的,HttpObjectAggregator可以将多个段聚合起来
  37. * 所以浏览器发送大量数据时就会发出多次Http请求
  38. */
  39. pipeline.addLast(new HttpObjectAggregator(8192));
  40. /* WebSocket的数据是以帧的形式传递,需要指定 WebSocketServerProtocolHandler和自定义处理帧数据Handler
  41. * WebSocketServerProtocolHandler的功能是将Http协议升级为WebSocket协议来保持长连接,并解析 websocket 访问地址
  42. * 浏览器访问WebSocket的形式是 ws://localhost:7070/访问路径,参数就为 “/访问路径”
  43. * 例如访问的是 ws://localhost:7070/rog16hp,这里的handler就要指定为 "/rog16hp"
  44. */
  45. pipeline.addLast(new WebSocketServerProtocolHandler("/hello"));
  46. pipeline.addLast(new MyTextWebSocketFrameHandler()); //处理WebSocket传递的数据(帧形式),需要指定对应WebSocketFrame的子类
  47. }
  48. });
  49. ChannelFuture future = serverBootstrap.bind(7070).sync();
  50. future.channel().closeFuture().sync(); //对关闭通道进行监听(当有关闭通道的消息时才进行监听)
  51. } finally {
  52. bossGroup.shutdownGracefully();
  53. workGroup.shutdownGracefully();
  54. }
  55. }
  56. }

帧数据处理Handler:

  1. package websocket;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.SimpleChannelInboundHandler;
  4. import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
  5. import java.time.LocalDateTime;
  6. /* 传递的是文本数据,指定 WebSocketFrame 的子类 TextWebSocketFrame 进行帧处理 */
  7. public class MyTextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
  8. @Override
  9. protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
  10. System.out.println("服务器收到消息: "+msg.text());
  11. //回复消息
  12. ctx.channel().writeAndFlush( new TextWebSocketFrame("服务器时间" + LocalDateTime.now() + "" + msg.text()) );
  13. }
  14. //当Web客户端连接后就会被触发
  15. @Override
  16. public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  17. //ID标识唯一的值,LongText是唯一的,shutText并不是唯一的
  18. System.out.println("HandlerAdder被调用!"+ctx.channel().id().asLongText());
  19. System.out.println("HandlerAdder被调用!"+ctx.channel().id().asShortText());
  20. }
  21. @Override
  22. public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
  23. System.out.println("HandlerRemoved被调用!"+ctx.channel().id().asLongText());
  24. }
  25. //异常发生时处理
  26. @Override
  27. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
  28. System.out.println("异常发生原因: "+cause.getMessage());
  29. ctx.close(); //关闭通道
  30. }
  31. }

页面:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>WebSocket测试页面</title>
  6. </head>
  7. <body>
  8. <script>
  9. var socket;
  10. //当前浏览器是否支持websocket
  11. if(window.WebSocket){
  12. socket = new WebSocket("ws://localhost:7070/hello");
  13. //相当于channelReado,msg就是收到服务器端回送的消息
  14. socket.onmessage = function (ev){
  15. var rt = document.getElementById("responseText");
  16. rt.value = rt.value = "\n" + ev.data;
  17. }
  18. //相当于连接开启
  19. socket.onopen = function (ev){
  20. var rt = document.getElementById("responseText");
  21. rt.value = "连接开启了";
  22. }
  23. //感知到连接关闭
  24. socket.onclose = function (ev){
  25. var rt = document.getElementById("responseText");
  26. rt.value = rt.value = "\n" + "连接关闭.....";
  27. }
  28. //发生消息到服务器
  29. function send(message){
  30. if(!window.socket){
  31. return; //socket没有创建好
  32. }
  33. if(socket.readyState == WebSocket.OPEN){
  34. socket.send(message) //通过Socket发送消息,消息类型需要和 帧处理类型保持一直
  35. }else{
  36. alert("连接未开启!")
  37. }
  38. }
  39. }else{
  40. alert("当前浏览器不支持WebSocket")
  41. }
  42. </script>
  43. <form onsubmit="return false">
  44. <textarea name="message" style="height:300px;width:300px"></textarea>
  45. <input type="button" value="发送消息" onclick="send(this.form.message.value)">
  46. <textarea id="responseText" style="height:300px;width:300px"></textarea>
  47. <input type="button" value="清空内容" onclick="document.getElementById('responseText').value=''">
  48. </form>
  49. </body>
  50. </html>

测试:

运行客户端代码后打开页面,输入消息查看服务端内容
image.png
101状态码代表连接成功
image.png
当有消息输入时,服务端将收到数据并返回
image.png
关闭浏览器时将会触发handlerRemoved方法