Netty学习笔记系列之六:多种通讯协议支持

通讯协议,指的是把Netty通讯管道中的二进制流转换为对象、把对象转换成二进制流的过程。转换过程追根究底还是ChannelInboundHandler、ChannelOutboundHandler的实现类在进行处理。ChannelInboundHandler负责把二进制流转换为对象,ChannelOutboundHandler负责把对象转换为二进制流。

接下来要构建一个Server,同时支持Person通讯协议和String通讯协议。

  • Person通讯协议:二进制流与Person对象间的互相转换。
  • String通讯协议:二进制流与有固定格式要求的String的相互转换。String格式表示的也是一个Person对象,格式规定为:name:xx;age:xx;sex:xx;
这时候,来自客户端的请求,会依次传递给两个通讯解析接口进行解析,每个通讯接口判断是否是匹配的协议,如果是则进行解析,如果不是则传递给其它通讯接口进行解析。

实体类:Person
[java]  view plain  copy
  1. package com.guowl.testobjcoder;  
  2.   
  3. import java.io.Serializable;  
  4.   
  5. public class Person implements Serializable{  
  6.     private static final long   serialVersionUID    = 1L;  
  7.     private String  name;  
  8.     private String  sex;  
  9.     private int     age;  
  10.   
  11.     public String toString() {  
  12.         return "name:" + name + " sex:" + sex + " age:" + age;  
  13.     }  
  14.   
  15.     public String getName() {  
  16.         return name;  
  17.     }  
  18.   
  19.     public void setName(String name) {  
  20.         this.name = name;  
  21.     }  
  22.   
  23.     public String getSex() {  
  24.         return sex;  
  25.     }  
  26.   
  27.     public void setSex(String sex) {  
  28.         this.sex = sex;  
  29.     }  
  30.   
  31.     public int getAge() {  
  32.         return age;  
  33.     }  
  34.   
  35.     public void setAge(int age) {  
  36.         this.age = age;  
  37.     }  
  38. }  

Server端的类为:Server PersonDecoder StringDecoder BusinessHandler
1、Server 开启Netty服务
[java]  view plain  copy
  1. package com.guowl.testobjcoder;  
  2.   
  3. import io.netty.bootstrap.ServerBootstrap;  
  4. import io.netty.channel.ChannelFuture;  
  5. import io.netty.channel.ChannelInitializer;  
  6. import io.netty.channel.ChannelOption;  
  7. import io.netty.channel.EventLoopGroup;  
  8. import io.netty.channel.nio.NioEventLoopGroup;  
  9. import io.netty.channel.socket.SocketChannel;  
  10. import io.netty.channel.socket.nio.NioServerSocketChannel;  
  11.   
  12. // 测试coder 和 handler 的混合使用  
  13. public class Server {  
  14.     public void start(int port) throws Exception {  
  15.         EventLoopGroup bossGroup = new NioEventLoopGroup();  
  16.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
  17.         try {  
  18.             ServerBootstrap b = new ServerBootstrap();  
  19.             b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)  
  20.                     .childHandler(new ChannelInitializer() {  
  21.                         @Override  
  22.                         public void initChannel(SocketChannel ch) throws Exception {  
  23.                             ch.pipeline().addLast(new PersonDecoder());  
  24.                             ch.pipeline().addLast(new StringDecoder());  
  25.                             ch.pipeline().addLast(new BusinessHandler());  
  26.                         }  
  27.                     }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);  
  28.   
  29.             ChannelFuture f = b.bind(port).sync();  
  30.   
  31.             f.channel().closeFuture().sync();  
  32.         } finally {  
  33.             workerGroup.shutdownGracefully();  
  34.             bossGroup.shutdownGracefully();  
  35.         }  
  36.     }  
  37.   
  38.     public static void main(String[] args) throws Exception {  
  39.         Server server = new Server();  
  40.         server.start(8000);  
  41.     }  
  42. }  
2、PersonDecoder  把二进制流转换成Person对象
[java]  view plain  copy
  1. package com.guowl.testobjcoder;  
  2.   
  3. import io.netty.buffer.ByteBuf;  
  4. import io.netty.channel.ChannelHandlerContext;  
  5. import io.netty.handler.codec.ByteToMessageDecoder;  
  6.   
  7. import java.util.List;  
  8.   
  9. import com.guowl.utils.ByteBufToBytes;  
  10. import com.guowl.utils.ByteObjConverter;  
  11.   
  12. public class PersonDecoder extends ByteToMessageDecoder {  
  13.     @Override  
  14.     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {  
  15.         byte n = "n".getBytes()[0];  
  16.         byte p = in.readByte();  
  17.         in.resetReaderIndex();  
  18.         if (n != p) {  
  19.             // 把读取的起始位置重置  
  20.             ByteBufToBytes reader = new ByteBufToBytes();  
  21.             out.add(ByteObjConverter.byteToObject(reader.read(in)));  
  22.         } else {  
  23.             // 执行其它的decode  
  24.             ctx.fireChannelRead(in);  
  25.         }  
  26.     }  
  27. }  
  28. 3、StringDecoder 把满足条件的字符串转换成Person对象
    [java]  view plain  copy
    1. package com.guowl.testobjcoder;  
    2.   
    3. import io.netty.buffer.ByteBuf;  
    4. import io.netty.channel.ChannelHandlerContext;  
    5. import io.netty.handler.codec.ByteToMessageDecoder;  
    6.   
    7. import java.util.List;  
    8.   
    9. import com.guowl.utils.ByteBufToBytes;  
    10.   
    11. public class StringDecoder extends ByteToMessageDecoder {  
    12.     @Override  
    13.     protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception {  
    14.         // 判断是否是String协议  
    15.         byte n = "n".getBytes()[0];  
    16.         byte p = in.readByte();  
    17.         // 把读取的起始位置重置  
    18.         in.resetReaderIndex();  
    19.         if (n == p) {  
    20.             ByteBufToBytes reader = new ByteBufToBytes();  
    21.             String msg = new String(reader.read(in));  
    22.             Person person = buildPerson(msg);  
    23.             out.add(person);  
    24.             //in.release();  
    25.         } else {  
    26.             ctx.fireChannelRead(in);  
    27.         }  
    28.     }  
    29.   
    30.     private Person buildPerson(String msg) {  
    31.         Person person = new Person();  
    32.         String[] msgArray = msg.split(";|:");  
    33.         person.setName(msgArray[1]);  
    34.         person.setAge(Integer.parseInt(msgArray[3]));  
    35.         person.setSex(msgArray[5]);  
    36.         return person;  
    37.     }  
    38. }  
    39. 4、BusinessHandler 展现客户端请求的内容
      [java]  view plain  copy
      1. package com.guowl.testobjcoder;  
      2.   
      3. import io.netty.channel.ChannelHandlerContext;  
      4. import io.netty.channel.ChannelInboundHandlerAdapter;  
      5.   
      6. import org.slf4j.Logger;  
      7. import org.slf4j.LoggerFactory;  
      8.   
      9. public class BusinessHandler extends ChannelInboundHandlerAdapter {  
      10.     private Logger  logger  = LoggerFactory.getLogger(BusinessHandler.class);  
      11.   
      12.     @Override  
      13.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {  
      14.         Person person = (Person) msg;  
      15.         logger.info("BusinessHandler read msg from client :" + person);  
      16.     }  
      17.   
      18.     @Override  
      19.     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {  
      20.         ctx.flush();  
      21.     }  
      [java]  view plain  copy
      1. "white-space:pre">    // 解决注意事项1中的问题。  
      [java]  view plain  copy
      1. "code" class="java">"white-space:pre">    @Override  
      2.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {  
      3.         ctx.close();  
      4.     }  
      }
       
         
      客户端1发送Person格式的协议:Client ClientInitHandler PersonEncoder
      1、Client 
      [java]  view plain  copy
      1. package com.guowl.testobjcoder;  
      2.   
      3. import io.netty.bootstrap.Bootstrap;  
      4. import io.netty.channel.ChannelFuture;  
      5. import io.netty.channel.ChannelInitializer;  
      6. import io.netty.channel.ChannelOption;  
      7. import io.netty.channel.EventLoopGroup;  
      8. import io.netty.channel.nio.NioEventLoopGroup;  
      9. import io.netty.channel.socket.SocketChannel;  
      10. import io.netty.channel.socket.nio.NioSocketChannel;  
      11.   
      12. public class Client {  
      13.     public void connect(String host, int port) throws Exception {  
      14.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
      15.   
      16.         try {  
      17.             Bootstrap b = new Bootstrap();   
      18.             b.group(workerGroup);   
      19.             b.channel(NioSocketChannel.class);   
      20.             b.option(ChannelOption.SO_KEEPALIVE, true);   
      21.             b.handler(new ChannelInitializer() {  
      22.                 @Override  
      23.                 public void initChannel(SocketChannel ch) throws Exception {  
      24.                     ch.pipeline().addLast(new PersonEncoder());  
      25.                     Person person = new Person();  
      26.                     person.setName("guowl");  
      27.                     person.setSex("man");  
      28.                     person.setAge(30);  
      29.                     ch.pipeline().addLast(new ClientInitHandler(person));  
      30.                 }  
      31.             });  
      32.   
      33.             ChannelFuture f = b.connect(host, port).sync();  
      34.             f.channel().closeFuture().sync();  
      35.         } finally {  
      36.             workerGroup.shutdownGracefully();  
      37.         }  
      38.   
      39.     }  
      40.   
      41.     public static void main(String[] args) throws Exception {  
      42.         Client client = new Client();  
      43.         client.connect("127.0.0.1"8000);  
      44.     }  
      45. }  
      2、ClientInitHandler 向服务端发送Person对象
      [java]  view plain  copy
      1. package com.guowl.testobjcoder;  
      2.   
      3. import io.netty.channel.ChannelHandlerContext;  
      4. import io.netty.channel.ChannelInboundHandlerAdapter;  
      5.   
      6. import org.slf4j.Logger;  
      7. import org.slf4j.LoggerFactory;  
      8.   
      9. public class ClientInitHandler extends ChannelInboundHandlerAdapter {  
      10.     private static Logger   logger  = LoggerFactory.getLogger(ClientInitHandler.class);  
      11.     private Person person;  
      12.     public ClientInitHandler(Person person){  
      13.         this.person = person;  
      14.     }  
      15.     @Override  
      16.     public void channelActive(ChannelHandlerContext ctx) throws Exception {  
      17.         logger.info("ClientInitHandler.channelActive");  
      18.         ctx.write(person);  
      19.         ctx.flush();  
      20.     }  
      21. }  
      3、PersonEncoder 把Person对象转换成二进制进行传送
      [java]  view plain  copy
      1. package com.guowl.testobjcoder;  
      2.   
      3. import com.guowl.utils.ByteObjConverter;  
      4.   
      5. import io.netty.buffer.ByteBuf;  
      6. import io.netty.channel.ChannelHandlerContext;  
      7. import io.netty.handler.codec.MessageToByteEncoder;  
      8.   
      9. public class PersonEncoder extends MessageToByteEncoder  {  
      10.   
      11.     @Override  
      12.     protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception {  
      13.         out.writeBytes(ByteObjConverter.objectToByte(msg));  
      14.     }  
      15. }  

      客户端2发送String格式的协议:Client2 StringEncoder 同样使用了客户端1中定义的ClientInitHandler 进行数据发送操作。
      1、Client2 
      [java]  view plain  copy
      1. package com.guowl.testobjcoder.client2;  
      2.   
      3. import io.netty.bootstrap.Bootstrap;  
      4. import io.netty.channel.ChannelFuture;  
      5. import io.netty.channel.ChannelInitializer;  
      6. import io.netty.channel.ChannelOption;  
      7. import io.netty.channel.EventLoopGroup;  
      8. import io.netty.channel.nio.NioEventLoopGroup;  
      9. import io.netty.channel.socket.SocketChannel;  
      10. import io.netty.channel.socket.nio.NioSocketChannel;  
      11.   
      12. import com.guowl.testobjcoder.ClientInitHandler;  
      13. import com.guowl.testobjcoder.Person;  
      14.   
      15. public class Client2 {  
      16.     public void connect(String host, int port) throws Exception {  
      17.         EventLoopGroup workerGroup = new NioEventLoopGroup();  
      18.   
      19.         try {  
      20.             Bootstrap b = new Bootstrap();   
      21.             b.group(workerGroup);   
      22.             b.channel(NioSocketChannel.class);   
      23.             b.option(ChannelOption.SO_KEEPALIVE, true);   
      24.             b.handler(new ChannelInitializer() {  
      25.                 @Override  
      26.                 public void initChannel(SocketChannel ch) throws Exception {  
      27.                     ch.pipeline().addLast(new StringEncoder());  
      28.                     Person person = new Person();  
      29.                     person.setName("guoxy");  
      30.                     person.setSex("girl");  
      31.                     person.setAge(4);  
      32.                     ch.pipeline().addLast(new ClientInitHandler(person));  
      33.                 }  
      34.             });  
      35.   
      36.             ChannelFuture f = b.connect(host, port).sync();  
      37.             f.channel().closeFuture().sync();  
      38.         } finally {  
      39.             workerGroup.shutdownGracefully();  
      40.         }  
      41.   
      42.     }  
      43.   
      44.     public static void main(String[] args) throws Exception {  
      45.         Client2 client = new Client2();  
      46.         client.connect("127.0.0.1"8000);  
      47.     }  
      48. }  
      2、StringEncoder 把Person对象转换成固定格式的String的二进制流进行传送
      [java]  view plain  copy
      1. package com.guowl.testobjcoder.client2;  
      2.   
      3. import io.netty.buffer.ByteBuf;  
      4. import io.netty.channel.ChannelHandlerContext;  
      5. import io.netty.handler.codec.MessageToByteEncoder;  
      6.   
      7. import com.guowl.testobjcoder.Person;  
      8.   
      9. public class StringEncoder extends MessageToByteEncoder {  
      10.   
      11.     @Override  
      12.     protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception {  
      13.         // 转成字符串:name:xx;age:xx;sex:xx;  
      14.         StringBuffer sb = new StringBuffer();  
      15.         sb.append("name:").append(msg.getName()).append(";");  
      16.         sb.append("age:").append(msg.getAge()).append(";");  
      17.         sb.append("sex:").append(msg.getSex()).append(";");  
      18.         out.writeBytes(sb.toString().getBytes());  
      19.     }  
      20. }  

      其它:工具类ByteBufToBytes(读取ByteBuf数据的工具类)、ByteObjConverter(Object与byte互转的工具类)在以前的文章中已经存在,在此省略。

      注意事项:
      1、该段代码能运行出结果,但是运行的时候会报 io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1 异常,已经解决。日志中的提示信息为:
      An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception
      说明缺少exceptionCaught方法,在server端最后一个Handler中增加这个方法即可。
      2、PersonDecoder和StringDecoder中有一个if判断,是为了判断消息究竟是什么协议。如果是String协议的话,格式是【name:xx;age:xx;sex:xx;】,第一个字母是英文字母n,所以判断协议类型时候是读取二进制流的第一个字符进行判断,当然这种判断方式非常幼稚,以后有机会可以进行改善。

      你可能感兴趣的:(java)