Netty+WebSocket实现简单网页群聊

Netty+WebSocket实现简单网页群聊

这两天看了下WebSocket的RFC文档,对WebSocket协议有了基本的认识,顺便写了篇博客做点笔记  WebSocket 协议

例子说明:每个网页一个websocket连接,点发送消息后,消息会发送给除了自己之外的其它在线的websocket客户端,简单实现群聊

服务端

采用Netty实现,Netty版本是4.1.2.Final. 

 

 

服务端共有以下4个类:

 

 

WebSocketServer实现IHttpService和IWebSocketService,WebSocketServerHandler持有IHttpService和 IWebSocketService的引用,若收到FullHttpRequest则交给IHttpService其处理,若收到WebSocketFrame则交给IWebSocketService去处理。


IHttpService.java

Java代码 
  1. package cc.lixiaohui.demo.netty4.websocket;  
  2.   
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.handler.codec.http.FullHttpRequest;  
  5.   
  6. /** 
  7.  * @author lixiaohui 
  8.  * @date 2016年9月24日 下午3:58:31 
  9.  *  
  10.  */  
  11. public interface IHttpService {  
  12.       
  13.     void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request);  
  14.       
  15. }  

 

IWebSocketService.java

Java代码 
  1. package cc.lixiaohui.demo.netty4.websocket;  
  2.   
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.handler.codec.http.websocketx.WebSocketFrame;  
  5.   
  6. /** 
  7.  * @author lixiaohui 
  8.  * @date 2016年9月24日 下午3:46:07 
  9.  *  
  10.  */  
  11. public interface IWebSocketService {  
  12.       
  13.     void handleFrame(ChannelHandlerContext ctx, WebSocketFrame frame);  
  14.       
  15. }  

 

WebSocketServerHandler.java

Java代码 
  1. package cc.lixiaohui.demo.netty4.websocket;  
  2.   
  3. import io.netty.channel.ChannelHandlerContext;  
  4. import io.netty.channel.SimpleChannelInboundHandler;  
  5. import io.netty.handler.codec.http.FullHttpRequest;  
  6. import io.netty.handler.codec.http.websocketx.WebSocketFrame;  
  7.   
  8. import org.slf4j.Logger;  
  9. import org.slf4j.LoggerFactory;  
  10.   
  11. /** 
  12.  * @author lixiaohui 
  13.  * @date 2016年9月24日 下午2:22:33 
  14.  *  
  15.  */  
  16. public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {  
  17.   
  18.     @SuppressWarnings("unused")  
  19.     private static final Logger logger = LoggerFactory.getLogger(WebSocketServerHandler.class);  
  20.       
  21.     private IWebSocketService websocketService;  
  22.       
  23.     private IHttpService httpService;  
  24.   
  25.     public WebSocketServerHandler(IWebSocketService websocketService, IHttpService httpService) {  
  26.         super();  
  27.         this.websocketService = websocketService;  
  28.         this.httpService = httpService;  
  29.     }  
  30.   
  31.     /* 
  32.      * @see 
  33.      * io.netty.channel.SimpleChannelInboundHandler#channelRead0(io.netty.channel 
  34.      * .ChannelHandlerContext, java.lang.Object) 
  35.      */  
  36.     @Override  
  37.     protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {  
  38.         if (msg instanceof FullHttpRequest) {  
  39.             httpService.handleHttpRequest(ctx, (FullHttpRequest) msg);  
  40.         } else if (msg instanceof WebSocketFrame) {  
  41.             websocketService.handleFrame(ctx, (WebSocketFrame) msg);  
  42.         }  
  43.     }  
  44.       
  45.     /*  
  46.      * @see io.netty.channel.ChannelInboundHandlerAdapter#channelReadComplete(io.netty.channel.ChannelHandlerContext) 
  47.      */  
  48.     @Override  
  49.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {  
  50.         ctx.flush();  
  51.     }  
  52.       
  53. }  

 

 

WebSocketServer.java

Java代码 
  1. package cc.lixiaohui.demo.netty4.websocket;  
  2.   
  3. import io.netty.bootstrap.ServerBootstrap;  
  4. import io.netty.channel.Channel;  
  5. import io.netty.channel.ChannelFuture;  
  6. import io.netty.channel.ChannelFutureListener;  
  7. import io.netty.channel.ChannelHandlerContext;  
  8. import io.netty.channel.ChannelId;  
  9. import io.netty.channel.ChannelInitializer;  
  10. import io.netty.channel.ChannelPipeline;  
  11. import io.netty.channel.EventLoopGroup;  
  12. import io.netty.channel.nio.NioEventLoopGroup;  
  13. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  14. import io.netty.handler.codec.http.FullHttpRequest;  
  15. import io.netty.handler.codec.http.HttpHeaderNames;  
  16. import io.netty.handler.codec.http.HttpHeaders;  
  17. import io.netty.handler.codec.http.HttpMethod;  
  18. import io.netty.handler.codec.http.HttpObjectAggregator;  
  19. import io.netty.handler.codec.http.HttpServerCodec;  
  20. import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;  
  21. import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;  
  22. import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;  
  23. import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;  
  24. import io.netty.handler.codec.http.websocketx.WebSocketFrame;  
  25. import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;  
  26. import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;  
  27. import io.netty.handler.stream.ChunkedWriteHandler;  
  28. import io.netty.util.AttributeKey;  
  29.   
  30. import java.util.Map;  
  31. import java.util.concurrent.ConcurrentHashMap;  
  32.   
  33. import org.slf4j.Logger;  
  34. import org.slf4j.LoggerFactory;  
  35.   
  36. /** 
  37.  * @author lixiaohui 
  38.  * @date 2016年9月24日 下午2:08:58 
  39.  *  
  40.  */  
  41. public class WebSocketServer implements IWebSocketService, IHttpService {  
  42.   
  43.     public static void main(String[] args) {  
  44.         new WebSocketServer(9999).start();  
  45.     }  
  46.       
  47.     // ----------------------------static fields -----------------------------  
  48.       
  49.     private static final Logger logger = LoggerFactory.getLogger(WebSocketServer.class);  
  50.   
  51.     private static final String HN_HTTP_CODEC = "HN_HTTP_CODEC";  
  52.     private static final String HN_HTTP_AGGREGATOR = "HN_HTTP_AGGREGATOR";  
  53.     private static final String HN_HTTP_CHUNK = "HN_HTTP_CHUNK";  
  54.     private static final String HN_SERVER = "HN_LOGIC";  
  55.       
  56.     // handshaker attachment key  
  57.     private static final AttributeKey<WebSocketServerHandshaker> ATTR_HANDSHAKER = AttributeKey.newInstance("ATTR_KEY_CHANNELID");  
  58.       
  59.     private static final int MAX_CONTENT_LENGTH = 65536;  
  60.       
  61.     private static final String WEBSOCKET_UPGRADE = "websocket";  
  62.     private static final String WEBSOCKET_CONNECTION = "Upgrade";  
  63.     private static final String WEBSOCKET_URI_ROOT_PATTERN = "ws://%s:%d";  
  64.   
  65.     // ------------------------ member fields -----------------------  
  66.       
  67.     private String host; // 绑定的地址  
  68.     private int port; // 绑定的端口  
  69.       
  70.     /** 
  71.      * 保存所有WebSocket连接 
  72.      */  
  73.     private Map<ChannelId, Channel> channelMap = new ConcurrentHashMap<ChannelId, Channel>();  
  74.       
  75.     private final String WEBSOCKET_URI_ROOT;  
  76.       
  77.     public WebSocketServer(int port) {  
  78.         this("localhost", port);  
  79.     }  
  80.       
  81.     public WebSocketServer(String host, int port) {  
  82.         this.host = host;  
  83.         this.port = port;  
  84.         WEBSOCKET_URI_ROOT = String.format(WEBSOCKET_URI_ROOT_PATTERN, host, port);  
  85.     }  
  86.   
  87.     public void start() {  
  88.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  89.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  90.         ServerBootstrap b = new ServerBootstrap();  
  91.         b.group(bossGroup, workerGroup);  
  92.         b.channel(NioServerSocketChannel.class);  
  93.         b.childHandler(new ChannelInitializer<Channel>() {  
  94.   
  95.             @Override  
  96.             protected void initChannel(Channel ch) throws Exception {  
  97.                 ChannelPipeline pl = ch.pipeline();  
  98.                 // 保存该Channel的引用  
  99.                 channelMap.put(ch.id(), ch);  
  100.                 logger.info("new channel {}", ch);  
  101.                 ch.closeFuture().addListener(new ChannelFutureListener() {  
  102.                       
  103.                     public void operationComplete(ChannelFuture future) throws Exception {  
  104.                         logger.info("channel close {}", future.channel());  
  105.                         // Channel 关闭后不再引用该Channel  
  106.                         channelMap.remove(future.channel().id());  
  107.                     }  
  108.                 });  
  109.   
  110.                 pl.addLast(HN_HTTP_CODEC, new HttpServerCodec());  
  111.                 pl.addLast(HN_HTTP_AGGREGATOR, new HttpObjectAggregator(MAX_CONTENT_LENGTH));  
  112.                 pl.addLast(HN_HTTP_CHUNK, new ChunkedWriteHandler());  
  113.                 pl.addLast(HN_SERVER, new WebSocketServerHandler(WebSocketServer.this, WebSocketServer.this));  
  114.             }  
  115.   
  116.         });  
  117.   
  118.         try {  
  119.             // 绑定端口  
  120.             ChannelFuture future = b.bind(host, port).addListener(new ChannelFutureListener() {  
  121.   
  122.                 public void operationComplete(ChannelFuture future) throws Exception {  
  123.                     if (future.isSuccess()) {  
  124.                         logger.info("websocket started.");  
  125.                     }  
  126.                 }  
  127.             }).sync();  
  128.               
  129.             future.channel().closeFuture().addListener(new ChannelFutureListener() {  
  130.   
  131.                 public void operationComplete(ChannelFuture future) throws Exception {  
  132.                     logger.info("server channel {} closed.", future.channel());  
  133.                 }  
  134.   
  135.             }).sync();  
  136.         } catch (InterruptedException e) {  
  137.             logger.error(e.toString());  
  138.         } finally {  
  139.             bossGroup.shutdownGracefully();  
  140.             workerGroup.shutdownGracefully();  
  141.         }  
  142.         logger.info("websocket server shutdown");  
  143.     }  
  144.   
  145.     /*  
  146.      * @see cc.lixiaohui.demo.netty4.websocket.IHttpService#handleHttpRequest(io.netty.handler.codec.http.FullHttpRequest) 
  147.      */  
  148.     public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) {  
  149.         if (isWebSocketUpgrade(req)) { // 该请求是不是websocket upgrade请求   
  150.             logger.info("upgrade to websocket protocol");  
  151.               
  152.             String subProtocols = req.headers().get(HttpHeaderNames.SEC_WEBSOCKET_PROTOCOL);  
  153.               
  154.             WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(WEBSOCKET_URI_ROOT, subProtocols, false);  
  155.             WebSocketServerHandshaker handshaker = factory.newHandshaker(req);  
  156.               
  157.             if (handshaker == null) {// 请求头不合法, 导致handshaker没创建成功  
  158.                 WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());  
  159.             } else {  
  160.                 // 响应该请求  
  161.                 handshaker.handshake(ctx.channel(), req);  
  162.                 // 把handshaker 绑定给Channel, 以便后面关闭连接用  
  163.                 ctx.channel().attr(ATTR_HANDSHAKER).set(handshaker);// attach handshaker to this channel  
  164.             }  
  165.             return;  
  166.         }  
  167.           
  168.         // TODO 忽略普通http请求  
  169.         logger.info("ignoring normal http request");  
  170.     }  
  171.       
  172.     /* 
  173.      * @see 
  174.      * cc.lixiaohui.demo.netty4.websocket.IWebSocketService#handleFrame(io.netty 
  175.      * .channel.Channel, io.netty.handler.codec.http.websocketx.WebSocketFrame) 
  176.      */  
  177.     public void handleFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {  
  178.         // text frame  
  179.         if (frame instanceof TextWebSocketFrame) {  
  180.             String text = ((TextWebSocketFrame) frame).text();  
  181.             TextWebSocketFrame rspFrame = new TextWebSocketFrame(text);  
  182.             logger.info("recieve TextWebSocketFrame from channel {}", ctx.channel());  
  183.             // 发给其他所有channel  
  184.             for (Channel ch : channelMap.values()) {  
  185.                 if (ctx.channel().equals(ch)) {   
  186.                     continue;   
  187.                 }  
  188.                 ch.writeAndFlush(rspFrame);  
  189.                 logger.info("write text[{}] to channel {}", text, ch);  
  190.             }  
  191.             return;  
  192.         }  
  193.           
  194.         // ping frame, 回复pong frame即可  
  195.         if (frame instanceof PingWebSocketFrame) {  
  196.             logger.info("recieve PingWebSocketFrame from channel {}", ctx.channel());  
  197.             ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));  
  198.             return;  
  199.         }  
  200.           
  201.         if (frame instanceof PongWebSocketFrame) {  
  202.             logger.info("recieve PongWebSocketFrame from channel {}", ctx.channel());  
  203.             return;  
  204.         }  
  205.         // close frame,   
  206.         if (frame instanceof CloseWebSocketFrame) {  
  207.             logger.info("recieve CloseWebSocketFrame from channel {}", ctx.channel());  
  208.             WebSocketServerHandshaker handshaker = ctx.channel().attr(ATTR_HANDSHAKER).get();  
  209.             if (handshaker == null) {  
  210.                 logger.error("channel {} have no HandShaker", ctx.channel());  
  211.                 return;  
  212.             }  
  213.             handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());  
  214.             return;  
  215.         }  
  216.         // 剩下的是binary frame, 忽略  
  217.         logger.warn("unhandle binary frame from channel {}", ctx.channel());  
  218.     }  
  219.       
  220.     //三者与:1.GET? 2.Upgrade头 包含websocket字符串?  3.Connection头 包含 Upgrade字符串?  
  221.     private boolean isWebSocketUpgrade(FullHttpRequest req) {  
  222.         HttpHeaders headers = req.headers();  
  223.         return req.method().equals(HttpMethod.GET)   
  224.                 && headers.get(HttpHeaderNames.UPGRADE).contains(WEBSOCKET_UPGRADE)  
  225.                 && headers.get(HttpHeaderNames.CONNECTION).contains(WEBSOCKET_CONNECTION);  
  226.     }  
  227.   
  228. }  

 

客户端 

客户端采用浏览器,代码:

Js代码 
  1. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">  
  2. <html xmlns="http://www.w3.org/1999/xhtml">  
  3. <head>  
  4. <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />  
  5. <title></title>  
  6. </head>  
  7.   </head>  
  8.   <script type="text/javascript">  
  9.   var socket;  
  10.     
  11.   if(!window.WebSocket){  
  12.       window.WebSocket = window.MozWebSocket;  
  13.   }  
  14.    
  15.   if(window.WebSocket){  
  16.       socket = new WebSocket("ws://localhost:9999");  
  17.         
  18.       socket.onmessage = function(event){             
  19.             appendln("接收:" + event.data);  
  20.       };  
  21.    
  22.       socket.onopen = function(event){  
  23.             appendln("WebSocket 连接已建立");  
  24.               
  25.       };  
  26.    
  27.       socket.onclose = function(event){  
  28.             appendln("WebSocket 连接已关闭");  
  29.       };  
  30.   }else{  
  31.         alert("浏览器不支持WebSocket协议");  
  32.   }  
  33.    
  34.   function send(message){  
  35.     if(!window.WebSocket){return;}  
  36.     if(socket.readyState == WebSocket.OPEN){  
  37.         socket.send(message);  
  38.         appendln("发送:" + message);  
  39.     }else{  
  40.         alert("WebSocket连接建立失败");  
  41.     }  
  42.       
  43.   }  
  44.     
  45.   function appendln(text) {  
  46.     var ta = document.getElementById('responseText');  
  47.     ta.value += text + "\r\n";  
  48.   }  
  49.     
  50.   function clear() {  
  51.     var ta = document.getElementById('responseText');  
  52.     ta.value = "";  
  53.   }  
  54.         
  55.   </script>  
  56.   <body>  
  57.     <form onSubmit="return false;">  
  58.         <input type = "text" name="message" value="你好啊"/>  
  59.         <br/><br/>  
  60.         <input type="button" value="发送 WebSocket 请求消息" onClick="send(this.form.message.value)"/>  
  61.         <hr/>  
  62.         <h3>服务端返回的应答消息</h3>  
  63.         <textarea id="responseText" style="width: 800px;height: 300px;"></textarea>  
  64.     </form>  
  65.   </body>  
  66. </html>  

测试

打开客户端,可以看到能接收到其他客户端发的消息

网页A:

 

 

 网页B:

 

猜你喜欢

转载自lixiaohui.iteye.com/blog/2328183