利用Netty来构建WebSocket后端服务系统的例子程序

最近在研究Netty来构建SOA架构,其中也包括了前端接入的HTTP/WebSocket方面的接入响应,而WebSocket方面的接入响应对于移动端的消息推送研发至关重要,这里就将在这块研发时的异步socket响应服务例子程序笔记记录下来,系统总共分为4个处理类,即:

HttpRequestHandler  --  HTTP请求处理类
TextWebSocketFrameHandler -- 对应Text消息的处理类
WebSocketServer  -- 系统主类
WebSocketServerInitializer -- 服务主程序的初始化类

WebSocketServer 类代码:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public final class WebSocketServer {

 private int port = 0;

 public WebSocketServer(int port) {
  this.port = port;
 }

 public void run() throws Exception {
  EventLoopGroup bossGroup = new NioEventLoopGroup();
  EventLoopGroup workerGroup = new NioEventLoopGroup();
  try {
   ServerBootstrap b = new ServerBootstrap();
   b.group(bossGroup, workerGroup)
     .channel(NioServerSocketChannel.class)
     .childHandler(new WebSocketServerInitializer())
     .option(ChannelOption.SO_BACKLOG, 128)
     .childOption(ChannelOption.SO_KEEPALIVE, true);
   
   System.out.println("WebsocketChatServer 启动了");

   // 绑定端口,开始接收进来的连接
   ChannelFuture f = b.bind(port).sync();

   // 等待服务器 socket 关闭 。在这个例子中,这不会发生,但你可以优雅地关闭你的服务器。
   f.channel().closeFuture().sync();
  } finally {
   workerGroup.shutdownGracefully();
   bossGroup.shutdownGracefully();

   System.out.println("WebsocketChatServer 关闭了");
  }
 }
 
 public static void main(String[] args) throws Exception {
  int port = 0;
  
        if (args.length > 0) {
            port = Integer.parseInt(args[0]);
        } else {
            port = 8080;
        }
       
        // start current instance
        new WebSocketServer(port).run();
 }
 
}

HttpRequestHandler类代码:

import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.DefaultFileRegion;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.DefaultHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpResponse;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.LastHttpContent;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedNioFile;

import java.io.File;
import java.io.RandomAccessFile;
import java.net.URISyntaxException;
import java.net.URL;

public class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

 private final String wsUri;
 private static final File INDEX;
 
 // static HTTP request handling operation.
 static {
  URL location = HttpRequestHandler.class.getProtectionDomain()
    .getCodeSource().getLocation();
  try {
   String path = location.toURI() + "WebSocketClient.html";
   path = !path.contains("file:") ? path : path.substring(5);
   INDEX = new File(path);
  } catch (URISyntaxException e) {
   throw new IllegalStateException("Unable to locate WebsocketChatClient.html", e);
  }
 }
 
 // constructor function call for current class
 public HttpRequestHandler(String wsUri) {
  this.wsUri = wsUri;
 }

 @Override
 public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request)
   throws Exception {
  if (wsUri.equalsIgnoreCase(request.getUri())) {
   ctx.fireChannelRead(request.retain());
  } else {
   if (HttpHeaders.is100ContinueExpected(request)) {
    send100Continue(ctx);
   }
   
   RandomAccessFile file = new RandomAccessFile(INDEX, "r");
   HttpResponse response = new DefaultHttpResponse(
     request.getProtocolVersion(), HttpResponseStatus.OK);
   response.headers().set(HttpHeaders.Names.CONTENT_TYPE,
     "text/html; charset=UTF-8");

   boolean keepAlive = HttpHeaders.isKeepAlive(request);
   if (keepAlive) {
    response.headers().set(HttpHeaders.Names.CONTENT_LENGTH,  file.length());
    response.headers().set(HttpHeaders.Names.CONNECTION, HttpHeaders.Values.KEEP_ALIVE);
   }
   ctx.write(response);

   if (ctx.pipeline().get(SslHandler.class) == null) {
    ctx.write(new DefaultFileRegion(file.getChannel(), 0, file.length()));
   } else {
    ctx.write(new ChunkedNioFile(file.getChannel()));
   }
   ChannelFuture future = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
   if (!keepAlive) {
    future.addListener(ChannelFutureListener.CLOSE);
   }

   file.close();
  }
 }

 private static void send100Continue(ChannelHandlerContext ctx) {
  FullHttpResponse response = new DefaultFullHttpResponse(
    HttpVersion.HTTP_1_1, HttpResponseStatus.CONTINUE);
  ctx.writeAndFlush(response);
 }

 @Override
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
   throws Exception {
  Channel incoming = ctx.channel();
  System.out.println("Client:" + incoming.remoteAddress() + "异常");
  
  // 当出现异常就关闭连接
  cause.printStackTrace();
  ctx.close();
 }

}


TextWebSocketFrameHandler类代码:

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.concurrent.GlobalEventExecutor;

public class TextWebSocketFrameHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {
 
 public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
 
 @Override
 protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception {
  Channel incoming = ctx.channel();
  for (Channel channel : channels) {
   if (channel != incoming) {
    //channel.writeAndFlush(new TextWebSocketFrame("[" + incoming.remoteAddress() + "]" + msg.text()));
   } else {
    channel.writeAndFlush(new TextWebSocketFrame("[服务器端返回]:" + msg.text()));
    
    //output current message to context.
    StringBuffer sb = new StringBuffer();
    sb.append(incoming.remoteAddress()).append("->").append(msg.text());
    System.out.println(sb.toString());
   }
  }
 }
 
 @Override
 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
  Channel incoming = ctx.channel();
  for (Channel channel : channels) {
   channel.writeAndFlush(new TextWebSocketFrame("[SERVER] - "  + incoming.remoteAddress() + " 加入"));
  }
  channels.add(ctx.channel());
  System.out.println("Client:" + incoming.remoteAddress() + "加入");
 }
 
 @Override
 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
  Channel incoming = ctx.channel();
  for (Channel channel : channels) {
   channel.writeAndFlush(new TextWebSocketFrame("[SERVER] - "  + incoming.remoteAddress() + " 离开"));
  }
  System.out.println("Client:" + incoming.remoteAddress() + "离开");
  channels.remove(ctx.channel());
 }
 
 @Override
 public void channelActive(ChannelHandlerContext ctx) throws Exception {
  Channel incoming = ctx.channel();
  System.out.println("Client:" + incoming.remoteAddress() + "在线");
 }
 
 @Override
 public void channelInactive(ChannelHandlerContext ctx) throws Exception {
  Channel incoming = ctx.channel();
  System.out.println("Client:" + incoming.remoteAddress() + "掉线");
 }

 @Override
 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
   throws Exception {
  Channel incoming = ctx.channel();
  System.out.println("Client:" + incoming.remoteAddress() + "异常");
  
  // 当出现异常就关闭连接
  cause.printStackTrace();
  ctx.close();
 }

}


WebSocketServerInitializer类代码:

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
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.stream.ChunkedWriteHandler;

public class WebSocketServerInitializer extends ChannelInitializer<SocketChannel> {
 
 @Override
    public void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
       
        pipeline.addLast(new HttpServerCodec());
        pipeline.addLast(new HttpObjectAggregator(64*1024));
        pipeline.addLast(new ChunkedWriteHandler());
        pipeline.addLast(new HttpRequestHandler("/ws"));
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        pipeline.addLast(new TextWebSocketFrameHandler());
       
    }
 
}



客户端将通过HTML5的WebSocket来撰写,特别注意WebSocket需要Firefox/Chrome/IE 比较高的版本才能够支持,譬如IE11.

<!DOCTYPE html>
<html>
<head>
<meta charset="GBK">
<title>WebSocket聊天程序</title>
</head>
<body>
    <script type="text/javascript">
        var socket;
       
        function connectServer() {
         if (!window.WebSocket) {
             window.WebSocket = window.MozWebSocket;
         }
         if (window.WebSocket) {
             socket = new WebSocket("ws://localhost:8080/ws");
             socket.onmessage = function(event) {
                 var ta = document.getElementById('responseText');
                 ta.value = ta.value + '\n' + event.data
             };
             socket.onopen = function(event) {
                 var ta = document.getElementById('responseText');
                 ta.value = "连接开启!";
             };
             socket.onclose = function(event) {
                 var ta = document.getElementById('responseText');
                 ta.value = ta.value + "连接被关闭";
             };
         } else {
             alert("你的浏览器不支持 WebSocket!");
         }
        }
       
        function send(message) {
            if (!window.WebSocket) {
                return;
            }
            if (socket.readyState == WebSocket.OPEN) {
                socket.send(message);
            } else {
                alert("连接没有开启.");
            }
        }
    </script>
    <form οnsubmit="return false;">
     <input type="button" οnclick="javascript:connectServer()" value="连接服务器">
        <h3>WebSocket 聊天室:</h3>
        <textarea id="responseText" style="width: 500px; height: 300px;"></textarea>
        <br>
        <input type="text" name="message"  style="width: 300px" value="聊天文字在此....">
        <input type="button" value="发送消息" οnclick="send(this.form.message.value)">
        <input type="button" οnclick="javascript:document.getElementById('responseText').value=''" value="清空聊天记录">
    </form>
    <br>
    <br>
</body>
</html>




  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值