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<SocketChannel>() {  
  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<Object> 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. }  
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<Object> 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. }  
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. <span style="white-space:pre">    </span>// 解决注意事项1中的问题。  
[java] view plain copy
  1. <pre name="code" class="java"><span style="white-space:pre">    </span>@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<SocketChannel>() {  
  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<Person>  {  
  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<SocketChannel>() {  
  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<Person> {  
  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,所以判断协议类型时候是读取二进制流的第一个字符进行判断,当然这种判断方式非常幼稚,以后有机会可以进行改善。
posted on 2018-03-01 22:35  武胜-阿伟  阅读(2041)  评论(0编辑  收藏  举报