Netty5 + WebSocket 练习

1. 了解WebSocket知识
  略
2. websocket实现系统简单反馈时间

  WebSocketServerHandler.java

 1 package com.jieli.nettytest.websocketserver;
 2 
 3 import io.netty.bootstrap.ServerBootstrap;
 4 import io.netty.channel.ChannelFuture;
 5 import io.netty.channel.ChannelInitializer;
 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.stream.ChunkedWriteHandler;
13 
14 public class WebSocketServer {
15     
16     public void run(int port){
17         EventLoopGroup bossGroup = new NioEventLoopGroup();
18         EventLoopGroup workerGroup = new NioEventLoopGroup();
19         try {
20             ServerBootstrap b = new ServerBootstrap();
21             b.group(bossGroup, workerGroup)
22              .channel(NioServerSocketChannel.class)
23              .childHandler(new ChannelInitializer<SocketChannel>() {
24                 @Override
25                 protected void initChannel(SocketChannel ch) throws Exception {
26                     //HttpServerCodec将请求和应答消息编码或解码为HTTP消息
27                     //通常接收到的http是一个片段,如果想要完整接受一次请求所有数据,我们需要绑定HttpObjectAggregator
28                     //然后就可以收到一个FullHttpRequest完整的请求信息了
29                     //ChunkedWriteHandler 向客户端发送HTML5文件,主要用于支持浏览器和服务器进行WebSocket通信
30                     //WebSocketServerHandler自定义Handler
31                     ch.pipeline().addLast("http-codec", new HttpServerCodec())
32                                  .addLast("aggregator", new HttpObjectAggregator(65536)) //定义缓冲大小
33                                  .addLast("http-chunked", new ChunkedWriteHandler())
34                                  .addLast("handler", new WebSocketServerHandler());
35                 }
36             });
37             
38             ChannelFuture f = b.bind(port).sync();
39             System.out.println("start...");
40             f.channel().closeFuture().sync();
41         } catch (Exception e) {
42             e.printStackTrace();
43         } finally {
44             workerGroup.shutdownGracefully();
45             bossGroup.shutdownGracefully();
46         }
47     }
48     
49     public static void main(String[] args) {
50         new WebSocketServer().run(7777);
51     }
52 }

  WebSocketServerHandler.java

  1 package com.jieli.nettytest.websocketserver;
  2 
  3 import java.util.logging.Level;
  4 import java.util.logging.Logger;
  5 
  6 import io.netty.buffer.ByteBuf;
  7 import io.netty.buffer.Unpooled;
  8 import io.netty.channel.ChannelFuture;
  9 import io.netty.channel.ChannelFutureListener;
 10 import io.netty.channel.ChannelHandlerContext;
 11 import io.netty.channel.SimpleChannelInboundHandler;
 12 import io.netty.handler.codec.http.DefaultFullHttpResponse;
 13 import io.netty.handler.codec.http.FullHttpRequest;
 14 import io.netty.handler.codec.http.FullHttpResponse;
 15 import io.netty.handler.codec.http.HttpHeaderUtil;
 16 import io.netty.handler.codec.http.HttpResponseStatus;
 17 import io.netty.handler.codec.http.HttpVersion;
 18 import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
 19 import io.netty.handler.codec.http.websocketx.PingWebSocketFrame;
 20 import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
 21 import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
 22 import io.netty.handler.codec.http.websocketx.WebSocketFrame;
 23 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
 24 import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
 25 import io.netty.util.CharsetUtil;
 26 
 27 public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object>{
 28 
 29     /**
 30      * 日志
 31      */
 32     private static final Logger logger = 
 33             Logger.getLogger(WebSocketServerHandler.class.getName());
 34     /**
 35      * 全局websocket
 36      */
 37     private WebSocketServerHandshaker handshaker;
 38     
 39     @Override
 40     protected void messageReceived(ChannelHandlerContext ctx, Object msg)
 41             throws Exception {
 42         //普通HTTP接入
 43         if(msg instanceof FullHttpRequest){
 44             handleHttpRequest(ctx, (FullHttpRequest) msg);
 45         }else if(msg instanceof WebSocketFrame){ //websocket帧类型 已连接
 46             //BinaryWebSocketFrame CloseWebSocketFrame ContinuationWebSocketFrame 
 47             //PingWebSocketFrame PongWebSocketFrame TextWebScoketFrame
 48             handleWebSocketFrame(ctx, (WebSocketFrame) msg);
 49         }
 50     }
 51     
 52     @Override
 53     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
 54         ctx.flush();
 55     }
 56     
 57     private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){
 58         //如果http解码失败 则返回http异常 并且判断消息头有没有包含Upgrade字段(协议升级)
 59         if(!request.decoderResult().isSuccess() 
 60                 || (!"websocket".equals( request.headers().get("Upgrade")))    ){
 61             sendHttpResponse(ctx, request, new DefaultFullHttpResponse(
 62                     HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
 63             return ;
 64         }
 65         //构造握手响应返回
 66         WebSocketServerHandshakerFactory ws = new WebSocketServerHandshakerFactory("", null, false);
 67         handshaker = ws.newHandshaker(request);
 68         if(handshaker == null){
 69             //版本不支持
 70             WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
 71         }else{
 72             handshaker.handshake(ctx.channel(), request);
 73         }
 74     }
 75     /**
 76      * websocket帧
 77      * @param ctx
 78      * @param frame
 79      */
 80     private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
 81         //判断是否关闭链路指令
 82         if(frame instanceof CloseWebSocketFrame){
 83             handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
 84             return ;
 85         }
 86         //判断是否Ping消息 -- ping/pong心跳包
 87         if(frame instanceof PingWebSocketFrame){
 88             ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
 89             return ;
 90         }
 91         //本程序仅支持文本消息, 不支持二进制消息
 92         if(!(frame instanceof TextWebSocketFrame)){
 93             throw new UnsupportedOperationException(
 94                     String.format("%s frame types not supported", frame.getClass().getName()));
 95         }
 96         
 97         //返回应答消息 text文本帧
 98         String request = ((TextWebSocketFrame) frame).text();
 99         //打印日志
100         if(logger.isLoggable(Level.FINE)){
101             logger.fine(String.format("%s received %s", ctx.channel(), request));
102         }
103         //发送到客户端websocket
104         ctx.channel().write(new TextWebSocketFrame(request 
105                 + ", 欢迎使用Netty WebSocket服务, 现在时刻:" 
106                 + new java.util.Date().toString()));
107     }
108     
109     /**
110      * response
111      * @param ctx
112      * @param request
113      * @param response
114      */
115     private static void sendHttpResponse(ChannelHandlerContext ctx, 
116             FullHttpRequest request, FullHttpResponse response){
117         //返回给客户端
118         if(response.status().code() != HttpResponseStatus.OK.code()){
119             ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
120             response.content().writeBytes(buf);
121             buf.release();
122             HttpHeaderUtil.setContentLength(response, response.content().readableBytes());
123         }
124         //如果不是keepalive那么就关闭连接
125         ChannelFuture f = ctx.channel().writeAndFlush(response);
126         if(!HttpHeaderUtil.isKeepAlive(response) 
127                 || response.status().code() != HttpResponseStatus.OK.code()){
128             f.addListener(ChannelFutureListener.CLOSE);
129         }
130     }
131     
132     /**
133      * 异常 出错
134      */
135     @Override
136     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
137             throws Exception {
138         cause.printStackTrace();
139         ctx.close();
140     }
141 }

  WebSocketServer.html (这个随便放都可以,到时候双击打开这个即可,不由服务器提供)

 1 <html>
 2     <head>
 3         <meta charset="utf-8">
 4         <title>Netty websocket 时间服务器</title>
 5     </head>
 6     <body>
 7         <form action="" onsubmit="return false;">
 8             <input type="text" name="message" value="..."/>
 9             <br>
10             <input type="button" value="send" value="发送websocket请求消息" onclick="send(this.form.message.value);" /> 
11             <hr color="blue">
12             <h3>服务器返回信息</h3>
13             <textarea id="responseText" rows="10" cols=""></textarea>
14         </form>
15     </body>
16     
17     <script type="text/javascript">
18         var socket;
19         if(!window.WebSocket){
20             window.WebSocket = window.MozWebSocket;
21         }
22         if(window.WebSocket){
23             socket = new WebSocket("ws://localhost:7777/websocket");
24             socket.onmessage = function(event){
25                 var ta = document.getElementById('responseText');
26                 ta.value="";
27                 ta.value=event.data;
28             };
29             socket.onopen = function(event){
30                 var ta = document.getElementById('responseText');
31                 ta.value = "打开websocket服务正常";
32             }
33             socket.onclose = function(event){
34                 var ta = document.getElementById('responseText');
35                 ta.value="";
36                 ta.value="websocket关闭";
37             }
38         }else{
39             alert("对不起,您的浏览器不支持WebSocket.");
40         }
41         
42         function send(message){
43             if(!window.WebSocket){
44                 return ;
45             }
46             if(socket.readyState == WebSocket.OPEN){
47                 socket.send(message);
48             }else{
49                 alert("WebSocket 连接创建失败.");
50             }
51         }
52     </script>
53 </html>

  运行结果

3. websocket实现简单聊天室

  WebSocketChatServer.java

 

 1 package com.jieli.nettytest.websocket;
 2 
 3 import io.netty.bootstrap.ServerBootstrap;
 4 import io.netty.channel.ChannelFuture;
 5 import io.netty.channel.ChannelOption;
 6 import io.netty.channel.EventLoopGroup;
 7 import io.netty.channel.nio.NioEventLoopGroup;
 8 import io.netty.channel.socket.nio.NioServerSocketChannel;
 9 
10 public class WebsocketChatServer {
11     
12     private int port;
13     
14     public WebsocketChatServer(int port){
15         this.port = port;
16     }
17     
18     public void run() throws Exception{
19         EventLoopGroup bossGroup = new NioEventLoopGroup();
20         EventLoopGroup workerGroup = new NioEventLoopGroup();
21         try {
22             ServerBootstrap b = new ServerBootstrap();
23             b.group(bossGroup, workerGroup)
24              .channel(NioServerSocketChannel.class)
25              .childHandler(new WebsocketChatServerInitializer())
26              .option(ChannelOption.SO_BACKLOG, 128)
27              .childOption(ChannelOption.SO_KEEPALIVE, true);
28             
29             System.out.println("websocket start..");
30 
31             ChannelFuture f = b.bind(port).sync();
32             
33             f.channel().closeFuture().sync();
34         } catch (Exception e) {
35             e.printStackTrace();
36         } finally {
37             workerGroup.shutdownGracefully();
38             bossGroup.shutdownGracefully();
39             System.out.println("websocket close.");
40         }
41     }
42     
43     public static void main(String[] args) throws Exception{
44         new WebsocketChatServer(8080).run();
45     }
46 }

 

  WebsocketChatServerInitializer.java

 1 package com.jieli.nettytest.websocket;
 2 
 3 import io.netty.channel.ChannelInitializer;
 4 import io.netty.channel.ChannelPipeline;
 5 import io.netty.channel.socket.SocketChannel;
 6 import io.netty.handler.codec.http.HttpObjectAggregator;
 7 import io.netty.handler.codec.http.HttpServerCodec;
 8 import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
 9 import io.netty.handler.stream.ChunkedWriteHandler;
10 
11 public class WebsocketChatServerInitializer extends ChannelInitializer<SocketChannel>{
12 
13     @Override
14     protected void initChannel(SocketChannel ch) throws Exception {
15         ChannelPipeline pipeline = ch.pipeline();
16         pipeline.addLast(new HttpServerCodec())
17                 .addLast(new HttpObjectAggregator(64*1024))
18                 .addLast(new ChunkedWriteHandler())
19                 .addLast(new HttpRequestHandler("/ws")) //如果访问的是RUI"/ws",处理WebSocket升级
20                 .addLast(new WebSocketServerProtocolHandler("/ws"))
21                 .addLast(new TextWebSocketFrameHandler());
22     }
23     
24 }

   HttpRequestHandler.java

  1 package com.jieli.nettytest.websocket;
  2 
  3 import java.io.File;
  4 import java.io.RandomAccessFile;
  5 import java.net.URL;
  6 
  7 import io.netty.channel.Channel;
  8 import io.netty.channel.ChannelFuture;
  9 import io.netty.channel.ChannelFutureListener;
 10 import io.netty.channel.ChannelHandlerContext;
 11 import io.netty.channel.DefaultFileRegion;
 12 import io.netty.channel.SimpleChannelInboundHandler;
 13 import io.netty.handler.codec.http.DefaultHttpResponse;
 14 import io.netty.handler.codec.http.FullHttpRequest;
 15 import io.netty.handler.codec.http.FullHttpResponse;
 16 import io.netty.handler.codec.http.HttpHeaderNames;
 17 import io.netty.handler.codec.http.HttpHeaderUtil;
 18 import io.netty.handler.codec.http.HttpHeaderValues;
 19 import io.netty.handler.codec.http.HttpResponse;
 20 import io.netty.handler.codec.http.HttpResponseStatus;
 21 import io.netty.handler.codec.http.HttpVersion;
 22 import io.netty.handler.codec.http.LastHttpContent;
 23 import io.netty.handler.ssl.SslHandler;
 24 import io.netty.handler.stream.ChunkedNioFile;
 25 
 26 public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest>{
 27     //扩展SimpleChannelInboundHandler用于处理FullHttpRequest信息
 28     private final String wsuri;
 29 //    private static final File index;
 30 //    
 31 //    static {
 32 //        URL location = HttpRequestHandler.class
 33 //                .getProtectionDomain().getCodeSource().getLocation();
 34 //        try {
 35 //            String path = location.toURI() + "html/index.html";
 36 //            path = !path.contains("file:") ? path : path.substring(5);
 37 //            index = new File(path);
 38 //        } catch (Exception e) {
 39 //            e.printStackTrace();
 40 //            throw new IllegalStateException("can't find index.html");
 41 //        }
 42 //    }
 43     
 44     public HttpRequestHandler(String wsuri){
 45         this.wsuri = wsuri;
 46     }
 47     
 48     @Override
 49     protected void messageReceived(ChannelHandlerContext ctx,
 50             FullHttpRequest request) throws Exception {
 51         if(wsuri.equalsIgnoreCase(request.uri())){
 52             //如果请求的是WebSocket升级,将其传递给在ChannelPipeline中的下一个ChannelInboundHandler处理
 53             //这里跟第一个例子的websocket协议升级判断方式是不同的 因为只只是判断uri路径而已
 54             //对应的js请求路径 socket = new WebSocket("ws://localhost:8080/ws");
 55             ctx.fireChannelRead(request.retain());
 56         }else{
 57             //处理100continue
 58             if(HttpHeaderUtil.is100ContinueExpected(request)){
 59                 send100Continue(ctx);
 60             }
 61             //读取默认页
 62             RandomAccessFile file = new RandomAccessFile("C:\\html\\index.html", "r");
 63             
 64             HttpResponse response = new DefaultHttpResponse(
 65                     request.protocolVersion(), HttpResponseStatus.OK);
 66             response.headers().set(HttpHeaderNames.CONTENT_TYPE, 
 67                     "text/html; charset=UTF-8");
 68             
 69             boolean keepAlive = HttpHeaderUtil.isKeepAlive(request);
 70             if(keepAlive){
 71                 response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, (int) file.length());
 72                 response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
 73             }
 74             ctx.write(response);
 75             
 76             if(ctx.pipeline().get(SslHandler.class) == null){
 77                 //如果不是https安全连接的话 要达到最大效率的话,可以通过把index.html直接存储在DefaultFileRegion中
 78                 //实现零拷贝传输 就是不拷贝到内存,直接读取文件通过文件输出流进行处理
 79                 ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
 80             }else{
 81                 //否则要读取全部文件,然后处理加密,在发送,只不过这一切都有netty内部处理
 82                 //A ChunkedInput that fetches data from a file chunk by chunk using NIO FileChannel.
 83                 //If your operating system supports zero-copy file transfer such as sendfile(), 
 84                 //you might want to use FileRegion instead.
 85                 ctx.write(new ChunkedNioFile(file.getChannel()));
 86             }
 87             ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
 88             
 89             if(!keepAlive){
 90                 future.addListener(ChannelFutureListener.CLOSE);
 91             }
 92             file.close();
 93         }
 94     }
 95 
 96     private static void send100Continue(ChannelHandlerContext ctx){
 97         FullHttpResponse response = (FullHttpResponse) new DefaultHttpResponse(
 98                 HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
 99         ctx.writeAndFlush(response);
100     }
101     
102     @Override
103     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
104             throws Exception {
105         Channel incoming = ctx.channel();
106         System.out.println("Client:"+incoming.remoteAddress()+"exception.");
107         cause.printStackTrace();
108         ctx.close();
109     }
110 }
View Code

  html/index.html

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4 <meta charset="UTF-8">
 5 <title>WebSocket Chat</title>
 6 </head>
 7 <body>
 8     <script type="text/javascript">
 9         var socket;
10         if (!window.WebSocket) {
11             window.WebSocket = window.MozWebSocket;
12         }
13         if (window.WebSocket) {
14             socket = new WebSocket("ws://localhost:8080/ws");
15             socket.onmessage = function(event) {
16                 var ta = document.getElementById('responseText');
17                 ta.value = ta.value + '\n' + event.data
18             };
19             socket.onopen = function(event) {
20                 var ta = document.getElementById('responseText');
21                 ta.value = "连接开启!";
22             };
23             socket.onclose = function(event) {
24                 var ta = document.getElementById('responseText');
25                 ta.value = ta.value + "连接被关闭";
26             };
27         } else {
28             alert("你的浏览器不支持 WebSocket!");
29         }
30 
31         function send(message) {
32             if (!window.WebSocket) {
33                 return;
34             }
35             if (socket.readyState == WebSocket.OPEN) {
36                 socket.send(message);
37             } else {
38                 alert("连接没有开启.");
39             }
40         }
41     </script>
42     <form onsubmit="return false;">
43         <h3>WebSocket 聊天室:</h3>
44         <textarea id="responseText" style="width: 500px; height: 300px;"></textarea>
45         <br> 
46         <input type="text" name="message"  style="width: 300px" value="Welcome to localhost">
47         <input type="button" value="发送消息" onclick="send(this.form.message.value)">
48         <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空聊天记录">
49     </form>
50     <br>
51         Netty SEO 优化 
52         Netty 是什么
53         Netty 怎么样
54         Netty4 Netty5 区别
55         Netty 效率
56         Netty 版本区别
57         Netty 和 Mina
58         Netty 网络编程
59         Netty Java 网络编程
60         Netty Java Socket NIO
61         NIO 编程
62         Netty NIO 开发
63         Netty3 Netty4 Netty5
64         Netty 好处
65         Netty 一般注意什么
66         Netty 例子程序
67         Netty Hello World
68         Netty 聊天程序
69         Netty Web HTML HTTP FTP SSL 
70         Netty UDP TCP WebSocket 练习
71         Netty 连接数
72         Netty 源码
73     <br>
74 </body>
75 </html>
View Code

  运行结果,依次运行1,2,3

  服务器打印结果

 

参考资料:
  《Netty 权威指南》
  https://zh.wikipedia.org/wiki/WebSocket
  https://www.zhihu.com/question/20215561
  http://waylau.com/netty-websocket-chat/

本文地址:http://www.cnblogs.com/wunaozai/p/5240006.html

posted @ 2016-03-07 08:44  无脑仔的小明  阅读(...)  评论(... 编辑 收藏