使用Netty构建一个多线程服务器与客户端

来源:互联网 发布:大淘客联盟与淘宝联盟 编辑:程序博客网 时间:2024/06/05 15:26
首先是服务器的初步实现.因为Netty是基于事件的,再加上其无阻塞的特性.我们必须要牢记: 

数据发送后,代码不会被阻塞,而是顺序运行,也就是说,做了一件事件后,这件事情不一定已经成功,所以我们不能在下一行代码中百分百的确定其已经发送到了对方,因此,你会发行其很多方法都会返回一个"Future".只要注意到这一点,Netty的使用难度就不是很大了. 

(一)handler处理篇 
首先,是handler,初次接触netty的朋友要注意,handler不是一个单例.即每个channel下都会有自己的一个handler实例. 

Java代码  收藏代码
  1. public class ServerHandler extends SimpleChannelUpstreamHandler {  
  2.   
  3.     private static final Logger logger = Logger.getLogger(  
  4.             ServerHandler.class.getName());  
  5.     private final ThreadLocal<Boolean> COMMAND_FLAG = new ThreadLocal<Boolean>();  
  6.     private final ServerChannelGroup serverChannelGroup = ServerChannelGroup.newInstance();  
  7.   
  8.     @Override  
  9.     public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)  
  10.             throws Exception {  
  11.         if (e instanceof ChannelStateEvent) {  
  12.             logger.log(Level.INFO, "Channel state changed: {0}", e);  
  13.         }  
  14.         super.handleUpstream(ctx, e);  
  15.     }  
  16.   
  17.     @Override  
  18.     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)  
  19.             throws Exception {  
  20.         System.out.println(this);  
  21.         String request = (String) e.getMessage();  
  22.         //如果接受到客户端发送的bye指令,那么就给客户端回复一个bye指令,客户端接受到后,主动关闭连接  
  23.         //服务器端通过ChannelFutureListener.CLOSE,当它认为客户端已经接受到服务器发送的bye后,也主动关闭连接  
  24.         if (request.toLowerCase().equals("bye")) {  
  25.             ChannelFuture future = e.getChannel().write("bye\r\n");  
  26.             future.addListener(ChannelFutureListener.CLOSE);  
  27.         } else {  
  28.             //以下是我初步解析客户端发送过来的数据,然后决定处理方式  
  29.             RecevieData receivedaData = MessageDecoder.decode(request);  
  30.             if (null != receivedaData) {  
  31.                 //服务器第5版  
  32.                 if (VersionCode.V5.equals(receivedaData.getVersion())) {  
  33.                     //然后判断命令是否存在  
  34.                     for (String s : CommandCode.COMMANDS) {  
  35.                         if (s.equals(receivedaData.getActionType())) {  
  36.                             COMMAND_FLAG.set(true);  
  37.                             if (s.equals(CommandCode.KEEP_ALIVE)) {  
  38.                                 serverChannelGroup.addChannel(e.getChannel());  
  39.                             }  
  40.                             break;  
  41.                         } else {  
  42.                             COMMAND_FLAG.set(false);  
  43.                         }  
  44.                     }  
  45.                     if (COMMAND_FLAG.get()) {  
  46.                         COMMAND_FLAG.set(false);  
  47.                         //将这个命令传递给下一个handler来处理.  
  48.                         //这里的"下一个handler"即为用户自己定义的处理handler  
  49.                         ctx.sendUpstream(e);  
  50.                     } else {  
  51.                         e.getChannel().write(MessageEncoder.encode(receivedaData, StatusCode.NOT_FOUND, StatusCode.NOT_FOUND_TEXT));  
  52.                     }  
  53.                 } else {  
  54.                     //版本错误  
  55.                     e.getChannel().write(MessageEncoder.encode(receivedaData, StatusCode.VERSION_NOT_SUPPORTED, StatusCode.VERSION_NOT_SUPPORTED_TXET));  
  56.                 }  
  57.             } else {  
  58.                 //如果格式错误,那么直接返回  
  59.                 e.getChannel().write(MessageEncoder.encode(receivedaData, nullnull));  
  60.             }  
  61.         }  
  62.   
  63.     }  
  64.   
  65.     @Override  
  66.     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)  
  67.             throws Exception {  
  68.         logger.log(Level.WARNING, "Server side Unexpected exception from downstream.",  
  69.                 e.getCause());  
  70.         e.getChannel().close();  
  71.         ListenerManager.getListener(ConnectClosedByPeerListener.class).connectClosedByPeer(e.getCause());  
  72.     }  
  73. }  


在上面这个handler中,我使用了ctx.sendUpstream(e);来处理,个人觉得此处为了要实现执行运行时代码,也可以使用接口等方式.但既然netty提供了sendUpstream 的方法,我们用这个岂不是更方便^_^ 

下面是使用SSL连接的handler 
Java代码  收藏代码
  1. public class ServerSSLHandler extends SimpleChannelUpstreamHandler {  
  2.   
  3.     private static final Logger logger = Logger.getLogger(  
  4.             ServerSSLHandler.class.getName());  
  5.     private final ThreadLocal<Boolean> COMMAND_FLAG = new ThreadLocal<Boolean>();  
  6.     private final ServerChannelGroup serverChannelGroup = ServerChannelGroup.newInstance();  
  7.   
  8.     @Override  
  9.     public void handleUpstream(ChannelHandlerContext ctx, ChannelEvent e)  
  10.             throws Exception {  
  11.         if (e instanceof ChannelStateEvent) {  
  12.             logger.log(Level.INFO, "Channel state changed: {0}", e);  
  13.         }  
  14.         super.handleUpstream(ctx, e);  
  15.     }  
  16.   
  17.     @Override  
  18.     public void channelConnected(ChannelHandlerContext ctx, ChannelStateEvent e)  
  19.             throws Exception {  
  20.         //ssl握手  
  21.         SslHandler sslHandler = ctx.getPipeline().get(SslHandler.class);  
  22.         sslHandler.handshake();  
  23.     }  
  24.   
  25.     @Override  
  26.     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e)  
  27.             throws Exception {  
  28.         System.out.println(this);  
  29.         String request = (String) e.getMessage();  
  30.         //如果接受到客户端发送的bye指令,那么就给客户端回复一个bye指令,客户端接受到后,主动关闭连接  
  31.         //服务器端通过ChannelFutureListener.CLOSE,当它认为客户端已经接受到服务器发送的bye后,也主动关闭连接  
  32.         if (request.toLowerCase().equals("bye")) {  
  33.             ChannelFuture future = e.getChannel().write("bye\r\n");  
  34.             future.addListener(ChannelFutureListener.CLOSE);  
  35.         } else {  
  36.             //以下是我初步解析客户端发送过来的数据,然后决定处理方式  
  37.             RecevieData receivedaData = MessageDecoder.decode(request);  
  38.             if (null != receivedaData) {  
  39.                 //服务器第5版  
  40.                 if (VersionCode.V5.equals(receivedaData.getVersion())) {  
  41.                     //然后判断命令是否存在  
  42.                     for (String s : CommandCode.COMMANDS) {  
  43.                         if (s.equals(receivedaData.getActionType())) {  
  44.                             COMMAND_FLAG.set(true);  
  45.                             if (s.equals(CommandCode.KEEP_ALIVE)) {  
  46.                                 serverChannelGroup.addChannel(e.getChannel());  
  47.                             }  
  48.                             break;  
  49.                         } else {  
  50.                             COMMAND_FLAG.set(false);  
  51.                         }  
  52.                     }  
  53.                     if (COMMAND_FLAG.get()) {  
  54.                         COMMAND_FLAG.set(false);  
  55.                         //将这个命令传递给下一个handler来处理.  
  56.                         //这里的"下一个handler"即为用户自己定义的处理handler  
  57.                         ctx.sendUpstream(e);  
  58.                     } else {  
  59.                         e.getChannel().write(MessageEncoder.encode(receivedaData, StatusCode.NOT_FOUND, StatusCode.NOT_FOUND_TEXT));  
  60.                     }  
  61.                 } else {  
  62.                     //版本错误  
  63.                     e.getChannel().write(MessageEncoder.encode(receivedaData, StatusCode.VERSION_NOT_SUPPORTED, StatusCode.VERSION_NOT_SUPPORTED_TXET));  
  64.                 }  
  65.             } else {  
  66.                 //如果格式错误,那么直接返回  
  67.                 e.getChannel().write(MessageEncoder.encode(receivedaData, nullnull));  
  68.             }  
  69.         }  
  70.   
  71.     }  
  72.   
  73.     @Override  
  74.     public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)  
  75.             throws Exception {  
  76.         logger.log(Level.WARNING, "Server side Unexpected exception from downstream.",  
  77.                 e.getCause());  
  78.         e.getChannel().close();  
  79.         ListenerManager.getListener(ConnectClosedByPeerListener.class).connectClosedByPeer(e.getCause());  
  80.     }  
  81. }  


关于SSL连接需要用到的一些其他东西,稍后在介绍 

当我们有了2个handler后,当然就是要把他们添加到我们的Pipeline中 
Java代码  收藏代码
  1. public class ServerPipelineFactory implements  
  2.         ChannelPipelineFactory {  
  3.   
  4.     public ChannelPipeline getPipeline() {  
  5.         ChannelPipeline pipeline = pipeline();  
  6.         ServerConfig config = ServerConfig.getInstance();  
  7.         try {  
  8.             if (config.ssl()) {  
  9.                 SSLEngine engine =  
  10.                         SecureSslContextFactory.getServerContext().createSSLEngine();  
  11.                 //说明是服务器端SslContext  
  12.                 engine.setUseClientMode(false);  
  13.                 pipeline.addLast("ssl"new SslHandler(engine));  
  14.             }  
  15.   
  16.             //此Decoder可以自动解析一句以\r\n结束的命令,我为了方便,也用了这个Decoder  
  17.             //使用这个Decoder,我不用刻意发送命令长度用于解析,只要没有收到\r\n说明数据还  
  18.             //没有发送完毕.这个Decoder会等到收到\r\n后调用下个handler  
  19.             pipeline.addLast("framer"new DelimiterBasedFrameDecoder(  
  20.                     8192, Delimiters.lineDelimiter()));  
  21.             //字串解码,可以自己设置charset  
  22.             pipeline.addLast("decoder"new StringDecoder());  
  23.             //字串编码,可以自己设置charset  
  24.             pipeline.addLast("encoder"new StringEncoder());  
  25.   
  26.             if (config.ssl()) {  
  27.                 //如果开启了SSL,那么使用sslhandler  
  28.                 pipeline.addLast("sslhandler"new ServerSSLHandler());  
  29.             } else {  
  30.                 //如果没有开启SSL,那么使用普通handler  
  31.                 pipeline.addLast("handler"new ServerHandler());  
  32.             }  
  33.             //遍历配置文件中的服务器handler,将其添加进Pipeline链中  
  34.             for (Element e : config.handler()) {  
  35.                 pipeline.addLast(e.attribute(e.getQName("id")).getValue().trim(),  
  36.                         (ChannelHandler) Class.forName(e.attribute(e.getQName("class")).getValue().trim()).newInstance());  
  37.             }  
  38.         } catch (DocumentException ex) {  
  39.             Logger.getLogger(ServerPipelineFactory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);  
  40.         } catch (InstantiationException ex) {  
  41.             Logger.getLogger(ServerPipelineFactory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);  
  42.         } catch (IllegalAccessException ex) {  
  43.             Logger.getLogger(ServerPipelineFactory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);  
  44.         } catch (ClassNotFoundException ex) {  
  45.             Logger.getLogger(ServerPipelineFactory.class.getName()).log(Level.SEVERE, ex.getMessage(), ex);  
  46.         }  
  47.         return pipeline;  
  48.     }  
  49. }  


下面是xml处理类 
Java代码  收藏代码
  1. public class ServerConfig {  
  2.   
  3.     private static final String HOST = "host";  
  4.     private static final String PORT = "port";  
  5.     private static final String HANDLER = "handler";  
  6.     private static final String CLIENTHANDLER = "clienthandler";  
  7.     private static final String SSL = "ssl";  
  8.     private static final ServerConfig SERVER_CONFIG = new ServerConfig();  
  9.     private static final String XML_PATH = "lib/server.xml";  
  10.     private static final SAXReader SAR_READER = new SAXReader();  
  11.   
  12.     private ServerConfig() {  
  13.         super();  
  14.     }  
  15.   
  16.     public String host() throws DocumentException {  
  17.         return this.rootElement().element(HOST).getTextTrim().trim();  
  18.     }  
  19.   
  20.     public int port() throws DocumentException {  
  21.         return Integer.parseInt(this.rootElement().element(PORT).getTextTrim().trim());  
  22.     }  
  23.   
  24.     public boolean ssl() throws DocumentException {  
  25.         return Integer.parseInt(this.rootElement().element(SSL).getTextTrim().trim()) == 1 ? true : false;  
  26.     }  
  27.   
  28.     public List<Element> handler() throws DocumentException {  
  29.         return this.rootElement().elements(HANDLER);  
  30.     }  
  31.   
  32.      public List<Element> clienthandler() throws DocumentException {  
  33.         return this.rootElement().elements(CLIENTHANDLER);  
  34.     }  
  35.   
  36.     private Element rootElement() throws DocumentException {  
  37.         return SAR_READER.read(new File(XML_PATH)).getRootElement();  
  38.     }  
  39.   
  40.     public static ServerConfig getInstance() {  
  41.         return SERVER_CONFIG;  
  42.     }  
  43. }  


server.xml,放到lib下即可,注意其中的handler 以及clienthandler 项,如果你新建了自己的handler,那么需要在此xml中配置一下. 
Xml代码  收藏代码
  1. <?xml version="1.0" encoding="UTF-8"?>  
  2.   
  3. <root>  
  4.     <!-- 配置主机地址 -->  
  5.     <host>127.0.0.1</host>  
  6.     <!-- 配置服务端口 -->  
  7.     <port>8080</port>  
  8.     <!-- 是否启用ssl,1为启用,0为停用 -->  
  9.     <ssl>0</ssl>  
  10.   
  11.     <!--服务器业务handler -->  
  12.     <handler id="timeHandler" class="com.chinatenet.nio.server.handler.ServerTimeHandler" />  
  13.       
  14.     <!--客户端业务handler -->  
  15.     <clienthandler id="timeHandler" class="com.chinatenet.nio.client.handler.ClientTimeHandler" />  
  16. </root>  


到此,一个简单的可扩展handler的服务器雏形就出来了 

下面,我们添加一个自定义的服务器处理handler进来 
Java代码  收藏代码
  1. public class ServerTimeHandler extends SimpleChannelUpstreamHandler {  
  2.   
  3.     @Override  
  4.     public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {  
  5.         RecevieData receivedaData = MessageDecoder.decode((String) e.getMessage());  
  6.         if (CommandCode.GET_TIME.equals(receivedaData.getActionType())  
  7.                 || CommandCode.KEEP_ALIVE.equals(receivedaData.getActionType())) {  
  8.             if (VersionCode.V5.equals(receivedaData.getVersion())) {  
  9.                 //回复客户端后,即可进行自己的业务.当然.这里可以根据需要,看  
  10.                 //是先回复再处理还是等处理结果出来后,将结果返回客户端  
  11.                 e.getChannel().write(MessageEncoder.encode(receivedaData, StatusCode.OK,  
  12.                         System.currentTimeMillis() / 1000 + ""));  
  13.             } else {  
  14.                 //版本错误  
  15.                 e.getChannel().write(MessageEncoder.encode(receivedaData, StatusCode.VERSION_NOT_SUPPORTED,  
  16.                         StatusCode.VERSION_NOT_SUPPORTED_TXET));  
  17.             }  
  18.         } else {  
  19.             //如果不是此handler处理的命令,那么流下去  
  20.             ctx.sendUpstream(e);  
  21.         }  
  22.     }  
  23.       
  24. }  


最后测试一下 
Java代码  收藏代码
  1. public class Server {  
  2.   
  3.     public static void main(String[] args) throws DocumentException {  
  4.         ServerBootstrap bootstrap = new ServerBootstrap(  
  5.                 new NioServerSocketChannelFactory(  
  6.                 Executors.newCachedThreadPool(),  
  7.                 Executors.newCachedThreadPool()));  
  8.   
  9.         bootstrap.setPipelineFactory(new ServerPipelineFactory());  
  10.   
  11.         //因为我要用到长连接  
  12.         bootstrap.setOption("child.tcpNoDelay"true);  
  13.         bootstrap.setOption("child.keepAlive"true);  
  14.           
  15.         ServerConfig config = ServerConfig.getInstance();  
  16.         bootstrap.bind(new InetSocketAddress(Integer.valueOf(config.port())));  
  17.     }  
  18. }  


总结:在整个服务器编码中,刚开始会遇到"远程主机强迫关闭了一个现有的连接。"等这类错误,最后修改成"相互告知对方我要关闭了"再进行关闭就可以了. 

最后再完善一下异常处理即可. 
原创粉丝点击