netty 支持多种通讯协议

时间:2023-03-09 09:19:14
netty 支持多种通讯协议

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

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

  • Person通讯协议:二进制流与Person对象间的互相转换。
  • String通讯协议:二进制流与有固定格式要求的String的相互转换。String格式表示的也是一个Person对象,格式规定为:name:xx;age:xx;sex:xx;
这时候,来自客户端的请求,会依次传递给两个通讯解析接口进行解析,每个通讯接口判断是否是匹配的协议,如果是则进行解析,如果不是则传递给其它通讯接口进行解析。
实体类:Person
  1. package com.guowl.testobjcoder;
  2. import java.io.Serializable;
  3. public class Person implements Serializable{
  4. private static final long   serialVersionUID    = 1L;
  5. private String  name;
  6. private String  sex;
  7. private int     age;
  8. public String toString() {
  9. return "name:" + name + " sex:" + sex + " age:" + age;
  10. }
  11. public String getName() {
  12. return name;
  13. }
  14. public void setName(String name) {
  15. this.name = name;
  16. }
  17. public String getSex() {
  18. return sex;
  19. }
  20. public void setSex(String sex) {
  21. this.sex = sex;
  22. }
  23. public int getAge() {
  24. return age;
  25. }
  26. public void setAge(int age) {
  27. this.age = age;
  28. }
  29. }

Server端的类为:Server PersonDecoder StringDecoder BusinessHandler

1、Server 开启Netty服务
  1. package com.guowl.testobjcoder;
  2. import io.netty.bootstrap.ServerBootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.ChannelOption;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioServerSocketChannel;
  10. // 测试coder 和 handler 的混合使用
  11. public class Server {
  12. public void start(int port) throws Exception {
  13. EventLoopGroup bossGroup = new NioEventLoopGroup();
  14. EventLoopGroup workerGroup = new NioEventLoopGroup();
  15. try {
  16. ServerBootstrap b = new ServerBootstrap();
  17. b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)
  18. .childHandler(new ChannelInitializer<SocketChannel>() {
  19. @Override
  20. public void initChannel(SocketChannel ch) throws Exception {
  21. ch.pipeline().addLast(new PersonDecoder());
  22. ch.pipeline().addLast(new StringDecoder());
  23. ch.pipeline().addLast(new BusinessHandler());
  24. }
  25. }).option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
  26. ChannelFuture f = b.bind(port).sync();
  27. f.channel().closeFuture().sync();
  28. } finally {
  29. workerGroup.shutdownGracefully();
  30. bossGroup.shutdownGracefully();
  31. }
  32. }
  33. public static void main(String[] args) throws Exception {
  34. Server server = new Server();
  35. server.start(8000);
  36. }
  37. }

2、PersonDecoder  把二进制流转换成Person对象

  1. package com.guowl.testobjcoder;
  2. import io.netty.buffer.ByteBuf;
  3. import io.netty.channel.ChannelHandlerContext;
  4. import io.netty.handler.codec.ByteToMessageDecoder;
  5. import java.util.List;
  6. import com.guowl.utils.ByteBufToBytes;
  7. import com.guowl.utils.ByteObjConverter;
  8. public class PersonDecoder extends ByteToMessageDecoder {
  9. @Override
  10. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
  11. byte n = "n".getBytes()[0];
  12. byte p = in.readByte();
  13. in.resetReaderIndex();
  14. if (n != p) {
  15. // 把读取的起始位置重置
  16. ByteBufToBytes reader = new ByteBufToBytes();
  17. out.add(ByteObjConverter.byteToObject(reader.read(in)));
  18. } else {
  19. // 执行其它的decode
  20. ctx.fireChannelRead(in);
  21. }
  22. }
  23. }

3、StringDecoder 把满足条件的字符串转换成Person对象

  1. package com.guowl.testobjcoder;
  2. import io.netty.buffer.ByteBuf;
  3. import io.netty.channel.ChannelHandlerContext;
  4. import io.netty.handler.codec.ByteToMessageDecoder;
  5. import java.util.List;
  6. import com.guowl.utils.ByteBufToBytes;
  7. public class StringDecoder extends ByteToMessageDecoder {
  8. @Override
  9. protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
  10. // 判断是否是String协议
  11. byte n = "n".getBytes()[0];
  12. byte p = in.readByte();
  13. // 把读取的起始位置重置
  14. in.resetReaderIndex();
  15. if (n == p) {
  16. ByteBufToBytes reader = new ByteBufToBytes();
  17. String msg = new String(reader.read(in));
  18. Person person = buildPerson(msg);
  19. out.add(person);
  20. //in.release();
  21. } else {
  22. ctx.fireChannelRead(in);
  23. }
  24. }
  25. private Person buildPerson(String msg) {
  26. Person person = new Person();
  27. String[] msgArray = msg.split(";|:");
  28. person.setName(msgArray[1]);
  29. person.setAge(Integer.parseInt(msgArray[3]));
  30. person.setSex(msgArray[5]);
  31. return person;
  32. }
  33. }
4、BusinessHandler 展现客户端请求的内容
  1. package com.guowl.testobjcoder;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. public class BusinessHandler extends ChannelInboundHandlerAdapter {
  7. private Logger  logger  = LoggerFactory.getLogger(BusinessHandler.class);
  8. @Override
  9. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  10. Person person = (Person) msg;
  11. logger.info("BusinessHandler read msg from client :" + person);
  12. }
  13. @Override
  14. public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
  15. ctx.flush();
  16. }
  1. <span style="white-space:pre">    </span>// 解决注意事项1中的问题。
  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 
  1. package com.guowl.testobjcoder;
  2. import io.netty.bootstrap.Bootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.ChannelOption;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioSocketChannel;
  10. public class Client {
  11. public void connect(String host, int port) throws Exception {
  12. EventLoopGroup workerGroup = new NioEventLoopGroup();
  13. try {
  14. Bootstrap b = new Bootstrap();
  15. b.group(workerGroup);
  16. b.channel(NioSocketChannel.class);
  17. b.option(ChannelOption.SO_KEEPALIVE, true);
  18. b.handler(new ChannelInitializer<SocketChannel>() {
  19. @Override
  20. public void initChannel(SocketChannel ch) throws Exception {
  21. ch.pipeline().addLast(new PersonEncoder());
  22. Person person = new Person();
  23. person.setName("guowl");
  24. person.setSex("man");
  25. person.setAge(30);
  26. ch.pipeline().addLast(new ClientInitHandler(person));
  27. }
  28. });
  29. ChannelFuture f = b.connect(host, port).sync();
  30. f.channel().closeFuture().sync();
  31. } finally {
  32. workerGroup.shutdownGracefully();
  33. }
  34. }
  35. public static void main(String[] args) throws Exception {
  36. Client client = new Client();
  37. client.connect("127.0.0.1", 8000);
  38. }
  39. }
2、ClientInitHandler 向服务端发送Person对象
  1. package com.guowl.testobjcoder;
  2. import io.netty.channel.ChannelHandlerContext;
  3. import io.netty.channel.ChannelInboundHandlerAdapter;
  4. import org.slf4j.Logger;
  5. import org.slf4j.LoggerFactory;
  6. public class ClientInitHandler extends ChannelInboundHandlerAdapter {
  7. private static Logger   logger  = LoggerFactory.getLogger(ClientInitHandler.class);
  8. private Person person;
  9. public ClientInitHandler(Person person){
  10. this.person = person;
  11. }
  12. @Override
  13. public void channelActive(ChannelHandlerContext ctx) throws Exception {
  14. logger.info("ClientInitHandler.channelActive");
  15. ctx.write(person);
  16. ctx.flush();
  17. }
  18. }
3、PersonEncoder 把Person对象转换成二进制进行传送
  1. package com.guowl.testobjcoder;
  2. import com.guowl.utils.ByteObjConverter;
  3. import io.netty.buffer.ByteBuf;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.handler.codec.MessageToByteEncoder;
  6. public class PersonEncoder extends MessageToByteEncoder<Person>  {
  7. @Override
  8. protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception {
  9. out.writeBytes(ByteObjConverter.objectToByte(msg));
  10. }
  11. }
客户端2发送String格式的协议:Client2 StringEncoder 同样使用了客户端1中定义的ClientInitHandler 进行数据发送操作。
1、Client2 
  1. package com.guowl.testobjcoder.client2;
  2. import io.netty.bootstrap.Bootstrap;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelInitializer;
  5. import io.netty.channel.ChannelOption;
  6. import io.netty.channel.EventLoopGroup;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.SocketChannel;
  9. import io.netty.channel.socket.nio.NioSocketChannel;
  10. import com.guowl.testobjcoder.ClientInitHandler;
  11. import com.guowl.testobjcoder.Person;
  12. public class Client2 {
  13. public void connect(String host, int port) throws Exception {
  14. EventLoopGroup workerGroup = new NioEventLoopGroup();
  15. try {
  16. Bootstrap b = new Bootstrap();
  17. b.group(workerGroup);
  18. b.channel(NioSocketChannel.class);
  19. b.option(ChannelOption.SO_KEEPALIVE, true);
  20. b.handler(new ChannelInitializer<SocketChannel>() {
  21. @Override
  22. public void initChannel(SocketChannel ch) throws Exception {
  23. ch.pipeline().addLast(new StringEncoder());
  24. Person person = new Person();
  25. person.setName("guoxy");
  26. person.setSex("girl");
  27. person.setAge(4);
  28. ch.pipeline().addLast(new ClientInitHandler(person));
  29. }
  30. });
  31. ChannelFuture f = b.connect(host, port).sync();
  32. f.channel().closeFuture().sync();
  33. } finally {
  34. workerGroup.shutdownGracefully();
  35. }
  36. }
  37. public static void main(String[] args) throws Exception {
  38. Client2 client = new Client2();
  39. client.connect("127.0.0.1", 8000);
  40. }
  41. }
2、StringEncoder 把Person对象转换成固定格式的String的二进制流进行传送
  1. package com.guowl.testobjcoder.client2;
  2. import io.netty.buffer.ByteBuf;
  3. import io.netty.channel.ChannelHandlerContext;
  4. import io.netty.handler.codec.MessageToByteEncoder;
  5. import com.guowl.testobjcoder.Person;
  6. public class StringEncoder extends MessageToByteEncoder<Person> {
  7. @Override
  8. protected void encode(ChannelHandlerContext ctx, Person msg, ByteBuf out) throws Exception {
  9. // 转成字符串:name:xx;age:xx;sex:xx;
  10. StringBuffer sb = new StringBuffer();
  11. sb.append("name:").append(msg.getName()).append(";");
  12. sb.append("age:").append(msg.getAge()).append(";");
  13. sb.append("sex:").append(msg.getSex()).append(";");
  14. out.writeBytes(sb.toString().getBytes());
  15. }
  16. }
其它:工具类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,所以判断协议类型时候是读取二进制流的第一个字符进行判断,当然这种判断方式非常幼稚,以后有机会可以进行改善。

相关文章