11.3 实现

    WebSocket使用HTTP升级机制从一个普通的HTTP连接WebSocket,因为这个应用程序使用WebSocket总是开始于HTTP(s),然后再升级。什么时候升级取决于应用程序本身。直接执行升级作为第一个操作一般是使用特定的url请求。

    在这里,如果url的结尾以/ws结束,我们将只会升级到WebSocket,否则服务器将发送一个网页给客户端。升级后的连接将通过WebSocket传输所有数据。逻辑图如下:

http://img.blog.csdn.net/20140731213833293?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

11.3.1 处理http请求

    服务器将作为一种混合式以允许同时处理http和websocket,所以服务器还需要html页面,html用来充当客户端角色,连接服务器并交互消息。因此,如果客户端不发送/ws的uri,我们需要写一个ChannelInboundHandler用来处理FullHttpRequest。看下面代码:

[java] view plaincopy

  1. package netty.in.action;
  2. import io.netty.channel.ChannelFuture;
  3. import io.netty.channel.ChannelFutureListener;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.DefaultFileRegion;
  6. import io.netty.channel.SimpleChannelInboundHandler;
  7. import io.netty.handler.codec.http.DefaultFullHttpResponse;
  8. import io.netty.handler.codec.http.DefaultHttpResponse;
  9. import io.netty.handler.codec.http.FullHttpRequest;
  10. import io.netty.handler.codec.http.FullHttpResponse;
  11. import io.netty.handler.codec.http.HttpHeaders;
  12. import io.netty.handler.codec.http.HttpResponse;
  13. import io.netty.handler.codec.http.HttpResponseStatus;
  14. import io.netty.handler.codec.http.HttpVersion;
  15. import io.netty.handler.codec.http.LastHttpContent;
  16. import io.netty.handler.ssl.SslHandler;
  17. import io.netty.handler.stream.ChunkedNioFile;
  18. import java.io.RandomAccessFile;
  19. /**
    • WebSocket,处理http请求
    • @author c.k
  20. */
  21. public class HttpRequestHandler extends
  22. SimpleChannelInboundHandler<FullHttpRequest> {
  23. //websocket标识
  24. private final String wsUri;
  25. public HttpRequestHandler(String wsUri) {
  26. this.wsUri = wsUri;
  27. }
  28. @Override
  29. protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest msg)
  30. throws Exception {
  31. //如果是websocket请求,请求地址uri等于wsuri
  32. if (wsUri.equalsIgnoreCase(msg.getUri())) {
  33. //将消息转发到下一个ChannelHandler
  34. ctx.fireChannelRead(msg.retain());
  35. } else {//如果不是websocket请求
  36. if (HttpHeaders.is100ContinueExpected(msg)) {
  37. //如果HTTP请求头部包含Expect: 100-continue,
  38. //则响应请求
  39. FullHttpResponse response = new DefaultFullHttpResponse(
  40. HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
  41. ctx.writeAndFlush(response);
  42. }
  43. //获取index.html的内容响应给客户端
  44. RandomAccessFile file = new RandomAccessFile(
  45. System.getProperty("user.dir") + "/index.html", "r");
  46. HttpResponse response = new DefaultHttpResponse(
  47. msg.getProtocolVersion(), HttpResponseStatus.OK);
  48. response.headers().set(HttpHeaders.Names.CONTENT_TYPE,
  49. "text/html; charset=UTF-8");
  50. boolean keepAlive = HttpHeaders.isKeepAlive(msg);
  51. //如果http请求保持活跃,设置http请求头部信息
  52. //并响应请求
  53. if (keepAlive) {
  54. response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,
  55. file.length());
  56. response.headers().set(HttpHeaders.Names.CONNECTION,
  57. HttpHeaders.Values.KEEP_ALIVE);
  58. }
  59. ctx.write(response);
  60. //如果不是https请求,将index.html内容写入通道
  61. if (ctx.pipeline().get(SslHandler.class) == null) {
  62. ctx.write(new DefaultFileRegion(file.getChannel(), 0, file
  63. .length()));
  64. } else {
  65. ctx.write(new ChunkedNioFile(file.getChannel()));
  66. }
  67. //标识响应内容结束并刷新通道
  68. ChannelFuture future = ctx
  69. .writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
  70. if (!keepAlive) {
  71. //如果http请求不活跃,关闭http连接
  72. future.addListener(ChannelFutureListener.CLOSE);
  73. }
  74. file.close();
  75. }
  76. }
  77. @Override
  78. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
  79. throws Exception {
  80. cause.printStackTrace();
  81. ctx.close();
  82. }
  83. }

11.3.2 处理WebSocket框架

    WebSocket支持6种不同框架,如下图:

http://img.blog.csdn.net/20140731215506204?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

我们的程序只需要使用下面4个框架:

  • CloseWebSocketFrame
  • PingWebSocketFrame
  • PongWebSocketFrame
  • TextWebSocketFrame

    我们只需要显示处理TextWebSocketFrame,其他的会自动由WebSocketServerProtocolHandler处理,看下面代码:
    

[java] view plaincopy

  1. package netty.in.action;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.SimpleChannelInboundHandler;
  4. import io.netty.channel.group.ChannelGroup;
  5. import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
  6. import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
  7. /**
    • WebSocket,处理消息
    • @author c.k
  8. *
  9. */
  10. public class TextWebSocketFrameHandler extends
  11. SimpleChannelInboundHandler<TextWebSocketFrame> {
  12. private final ChannelGroup group;
  13. public TextWebSocketFrameHandler(ChannelGroup group) {
  14. this.group = group;
  15. }
  16. @Override
  17. public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
  18. throws Exception {
  19. //如果WebSocket握手完成
  20. if (evt == WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE) {
  21. //删除ChannelPipeline中的HttpRequestHandler
  22. ctx.pipeline().remove(HttpRequestHandler.class);
  23. //写一个消息到ChannelGroup
  24. group.writeAndFlush(new TextWebSocketFrame("Client " + ctx.channel()
    • " joined"));
  25. //将Channel添加到ChannelGroup
  26. group.add(ctx.channel());
  27. }else {
  28. super.userEventTriggered(ctx, evt);
  29. }
  30. }
  31. @Override
  32. protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg)
  33. throws Exception {
  34. //将接收的消息通过ChannelGroup转发到所以已连接的客户端
  35. group.writeAndFlush(msg.retain());
  36. }
  37. }

11.3.3 初始化ChannelPipeline

    看下面代码:

[java] view plaincopy

  1. package netty.in.action;
  2. import io.netty.channel.Channel;
  3. import io.netty.channel.ChannelInitializer;
  4. import io.netty.channel.ChannelPipeline;
  5. import io.netty.channel.group.ChannelGroup;
  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. /**
    • WebSocket,初始化ChannelHandler
    • @author c.k
  11. *
  12. */
  13. public class ChatServerInitializer extends ChannelInitializer<Channel> {
  14. private final ChannelGroup group;
  15. public ChatServerInitializer(ChannelGroup group){
  16. this.group = group;
  17. }
  18. @Override
  19. protected void initChannel(Channel ch) throws Exception {
  20. ChannelPipeline pipeline = ch.pipeline();
  21. //编解码http请求
  22. pipeline.addLast(new HttpServerCodec());
  23. //写文件内容
  24. pipeline.addLast(new ChunkedWriteHandler());
  25. //聚合解码HttpRequest/HttpContent/LastHttpContent到FullHttpRequest
  26. //保证接收的Http请求的完整性
  27. pipeline.addLast(new HttpObjectAggregator(64 * 1024));
  28. //处理FullHttpRequest
  29. pipeline.addLast(new HttpRequestHandler("/ws"));
  30. //处理其他的WebSocketFrame
  31. pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
  32. //处理TextWebSocketFrame
  33. pipeline.addLast(new TextWebSocketFrameHandler(group));
  34. }
  35. }

    WebSocketServerProtcolHandler不仅处理Ping/Pong/CloseWebSocketFrame,还和它自己握手并帮助升级WebSocket。这是执行完成握手和成功修改ChannelPipeline,并且添加需要的编码器/解码器和删除不需要的ChannelHandler。

    看下图:

http://img.blog.csdn.net/20140731215752609?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast ChannelPipeline通过ChannelInitializer的initChannel(...)方法完成初始化,完成握手后就会更改事情。一旦这样做了,WebSocketServerProtocolHandler将取代HttpRequestDecoder、WebSocketFrameDecoder13和HttpResponseEncoder、WebSocketFrameEncoder13。另外也要删除所有不需要的ChannelHandler已获得最佳性能。这些都是HttpObjectAggregator和HttpRequestHandler。下图显示ChannelPipeline握手完成:

http://img.blog.csdn.net/20140731215818178?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvYWJjX2tleQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast

    我们甚至没注意到它,因为它是在底层执行的。以非常灵活的方式动态更新ChannelPipeline让单独的任务在不同的ChannelHandler中实现。

results matching ""

    No results matching ""