netty 二之pipeline

一 netty简单示例

先写一个简单的netty长连接Demo

服务端主要包括连接类(bootstrap)和业务处理类(channelHandler),另外一个server启动类,可以与连接类合并。

公用的类为消息和消息编码,消息解码类。

消息类:

public class RequestInfoVO {
	
	private String body;
	
	private int  Type;
	private int  Sequence;
	
	public String getBody() {
		return body;
	}
	public void setBody(String body) {
		this.body = body;
	}
	public int getType() {
		return Type;
	}
	public void setType(int type) {
		Type = type;
	}
	public int getSequence() {
		return Sequence;
	}
	public void setSequence(int sequence) {
		Sequence = sequence;
	}
}

消息解码器:

public class MessageDecoder extends ByteToMessageDecoder {
	private static final int MAGIC_NUMBER = 0x0CAFFEE0;
	public MessageDecoder() {
 
	}
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		if (in.readableBytes() < 14) {
			return;
		}
		// 标记开始读取位置
		in.markReaderIndex();
 
		int magic_number = in.readInt();
 
		if (MAGIC_NUMBER != magic_number) {
			ctx.close();
			return;
		}
 
		@SuppressWarnings("unused")
		byte version = in.readByte();
 
		byte type = in.readByte();
		int squence = in.readInt();
		int length = in.readInt();
 
		if (length < 0) {
			ctx.close();
			return;
		}
 
		if (in.readableBytes() < length) {
			// 重置到开始读取位置
			in.resetReaderIndex();
			return;
		}
 
		byte[] body = new byte[length];
		in.readBytes(body);
		RequestInfoVO req = new RequestInfoVO();
		req.setBody(new String(body, "utf-8"));
		req.setType(type);
		req.setSequence(squence);
		out.add(req);
	}
}

消息编码器:

public class MessageEncoder extends MessageToByteEncoder<RequestInfoVO> {
	 
	private static final String DEFAULT_ENCODE = "utf-8";
 
	private static final int MAGIC_NUMBER = 0x0CAFFEE0;
 
	public MessageEncoder() {
	}
 
	@Override
	protected void encode(ChannelHandlerContext ctx, RequestInfoVO msg, ByteBuf out) throws Exception {
 
		@SuppressWarnings("resource")
		ByteBufOutputStream writer = new ByteBufOutputStream(out);
		byte[] body = null;
 
		if (null != msg && null != msg.getBody() && "" != msg.getBody()) {
			body = msg.getBody().getBytes(DEFAULT_ENCODE);
		}
 
		writer.writeInt(MAGIC_NUMBER);
 
		writer.writeByte(1);
 
		writer.writeByte(msg.getType());
 
		writer.writeInt(msg.getSequence());
 
		if (null == body || 0 == body.length) {
			writer.writeInt(0);
		} else {
			writer.writeInt(body.length);
			writer.write(body);
		}
	}
 
}

服务端连接类:

public class NettyServerBootstrap {
	 
	private Integer port;
	private SocketChannel socketChannel;
	public NettyServerBootstrap(Integer port) throws Exception {
		this.port = port;
		bind(port);
	}
	public Integer getPort() {
		return port;
	}
	public void setPort(Integer port) {
		this.port = port;
	}
	public SocketChannel getSocketChannel() {
		return socketChannel;
	}
	public void setSocketChannel(SocketChannel socketChannel) {
		this.socketChannel = socketChannel;
	}
	private void bind(int serverPort) throws Exception {
		// 连接处理group
		EventLoopGroup boss = new NioEventLoopGroup();
		// 事件处理group
		EventLoopGroup worker = new NioEventLoopGroup();
		ServerBootstrap bootstrap = new ServerBootstrap();
		// 绑定处理group
		bootstrap.group(boss, worker);
		bootstrap.channel(NioServerSocketChannel.class);
		// 保持连接数
		bootstrap.option(ChannelOption.SO_BACKLOG, 1024 * 1024);
		// 有数据立即发送
		bootstrap.option(ChannelOption.TCP_NODELAY, true);
		// 保持连接
		bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
       // 处理新连接
		bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel sc) throws Exception {
				// 增加任务处理
				ChannelPipeline p = sc.pipeline();
				/**
				 * 这里要注意的地方:
				 * 1.顺序是要注意的,解码器必须排在入境处理类之前,因为入境数据的流动时从做到右;
				 * 编码器必须放在出境处理类之前,因为出境数据的流动时从右到左
				 * 2.同时配置几个解码器或者同时配置几个编码器都很容易出现问题,一般只配置一个解码器和一个编码器
				 */
				p.addLast(new MessageDecoder(), new MessageEncoder(), new NettyServerHandler());
			 }
		});
 
		ChannelFuture f = bootstrap.bind(serverPort).sync();
		if (f.isSuccess()) {
			System.out.println("long connection started success");
		} else {
			System.out.println("long connection started fail");
		}
	}

服务端业务处理handler:

public class NettyServerHandler extends SimpleChannelInboundHandler<RequestInfoVO> {
//	private static final Log log = LogFactory.getLog(NettyServerHandler.class);
 
	@Override
	protected void channelRead0(ChannelHandlerContext ctx, RequestInfoVO msg) throws Exception {
		System.out.println(msg.getBody());
		//
	}
 
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		
	}
 
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		
	}
 
 
	@Override
	public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
		
	}
 
}

客户端:

客户端与服务端共用消息类和解码器,编码器。

客户端连接类:

public class NettyClientBootstrap {
	private int port;
	private String host;
	private SocketChannel socketChannel;
	public NettyClientBootstrap(int port, String host) throws Exception {
		this.host = host;
		this.port = port;
		start();
	}
	private void start() throws Exception {
		EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
		Bootstrap bootstrap = new Bootstrap();
		bootstrap.channel(NioSocketChannel.class);
		bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
		bootstrap.option(ChannelOption.TCP_NODELAY, true);
		bootstrap.group(eventLoopGroup);
		bootstrap.remoteAddress(this.host, this.port);
		bootstrap.handler(new ChannelInitializer<SocketChannel>() {
			@Override
			protected void initChannel(SocketChannel socketChannel) throws Exception {
				socketChannel.pipeline().addLast(new MessageDecoder(), new MessageEncoder(), new NettyClientHandler());
			}
		});
		ChannelFuture future = bootstrap.connect(this.host, this.port).sync();
		if (future.isSuccess()) {
			socketChannel = (SocketChannel) future.channel();
			System.out.println("connect server  success|");
		}
	}
	public int getPort() {
		return this.port;
	}
	public void setPort(int port) {
		this.port = port;
	}
 
	public SocketChannel getSocketChannel() {
		return socketChannel;
	}
	public void setSocketChannel(SocketChannel socketChannel) {
		this.socketChannel = socketChannel;
	}
	public String getHost() {
		return host;
	}
	public void setHost(String host) {
		this.host = host;
	}
}

客户端业务处理handler:

public class NettyClientHandler extends SimpleChannelInboundHandler<RequestInfoVO> {

	@Override
	protected void channelRead0(ChannelHandlerContext ctx, RequestInfoVO msg)
			throws Exception {
		System.out.println(msg.getBody());
		RequestInfoVO req = new RequestInfoVO();
		req.setSequence(msg.getSequence());
		req.setType(msg.getType());
		if (2 == msg.getType()) {
			req.setBody("client");
			ctx.channel().writeAndFlush(req);
		} else if (3 == msg.getType()) {
			req.setBody("zpksb");
			ctx.channel().writeAndFlush(req);
		}
		
	}
 
}

最后是启动类:

服务端:

public class Server {
	public static void main(String[] args) {
		try {
			new NettyServerBootstrap(9999);
		} catch (Exception e) {
			
			e.printStackTrace();
		}
}


}

客户端:

public class Client {
	
	public static void main(String[] args) throws Exception {
		NettyClientBootstrap bootstrap = new NettyClientBootstrap(9999, "127.0.0.1");
		int i = 1;
 
		while (true) {
			TimeUnit.SECONDS.sleep(2);
			System.out.println("send heartbeat!");
			
			RequestInfoVO req = new RequestInfoVO();
			req.setSequence(123456);
			req.setType((byte) 1);
			req.setSequence(0);
			req.setBody(String.valueOf((new Date()).getTime()));
			
			TestVO vo = new TestVO();
			vo.setReq(1);
			vo.setName("582552252525");
			
//			bootstrap.getSocketChannel().write(req);
			bootstrap.getSocketChannel().writeAndFlush(vo);
			i++;
		}
	}


}

上面代码的主要功能为一个实现长连接,同时客户端定时会向服务端发送心跳包类检测连接是否正常。
有几个需要注意的地方:

1.服务器的连接类中有一行关键的代码:

p.addLast(new MessageDecoder(), new MessageEncoder(), new NettyServerHandler());

这一行代码是一行关键的代码,理解了这一行代码,对netty的整个工作流程的理解非常有帮助:

解码器MessageDecoder,编码器MessageEncoder,业务处理NettyServerHandler三个都是ChannelHandler,那么一条消息发送到服务端以后是如何处理的一个流程呢,先要了解一下netty的消息处理机制

对于一条消息,netty是通过pipeline事件传递的方式处理。

netty是采用双向链表的方式来处理消息事件,用到了责任链模式。还有一个概念,就是消息也有分为:入境(inbound)消息和出境(outbound)消息,而handler则也分为入境(inbound)处理handler和出境(outbound)处理handler。

处理过程为:

一个入境消息是从左到右(header开始向后遍历),依次被每个为入境类型handler处理(处理过程中的handler也可以选择终止消息传播,直接返回)。最后一个handler处理后返回。即一条入境消息沿着handler链条从左到右处理。

一个出境消息是从右到左(tail开始向前遍历),依次被每个为出境类型handler处理(处理过程中的handler也可以选择终止消息传播,直接返回)。最后一个handler处理后返回。一条出境消息沿着handler链条从右到左处理。

再来看上面的情况:

上面的三个handler组成一条链条:MessageDecoder->MessageEncoder->NettyServerHandler

其中两个入境handler:MessageDecoder和NettyServerHandler,一个出境handler:MessageEncoder

一个客户端发送一个消息过来,对于服务端来说这是一个入境事件,所以先后会经过MessageDecoder和NettyServerHandler处理,MessageDecoder先将网络发送过来的字节数组进行解码(反序列化)成java对象。然后交给业务处理类NettyServerHandler处理

现在如果假设把顺序替换一下。成下面这样

p.addLast(new NettyServerHandler(),new MessageDecoder(), new MessageEncoder());

然业务处理类NettyServerHandler在解码类MessageDecoder前面,会发现什么状况?

这是消息首先经过NettyServerHandler,但是这个时候消息并不是对象形式的,而是字节数组形式的,所以NettyServerHandler并不处理消息,而是将其传播到解码器MessageDecoder。这时候入境消息就没有被处理。

可见在加载handler链条的时候,要确保解码器类在入境业务处理类的前面(入境消息从左到右处理);同理编码器要放在出境业务处理类的前面(出境消息从右到左处理)。

还有一点,业务处理类NettyServerHandler中有RequestInfoVO的泛型,这个表示该业务处理类只会拦截解码后为RequestInfoVO类型的对象进行处理,如果解码后为其他类型的对象,则不处理,向下一个入境handler传播消息。

多个解码器的情况

上面是一个RequestInfoVO消息的情况,其可以处理查询用户信息请求,假设用户现在需要退出登录,有两种方式:

1.将RequestInfoVO扩展,让其也可以表示退出登陆消息

这个时候只需要对解码器进行相应的修改,让其处理扩展后的对象

2.新建一个消息类RequetLogoutVO类,然其表示退出登陆消息

这种情况下,需要新增一个解码器MessageDecoder2,然解码器来处理RequetLogoutVO类型的消息

这就引申出了一个问题,当存在多个解码器的时候流程如何处理?

假设现在新增了一个解码器,变为:

p.addLast(new MessageDecoder(),new MessageDecoder2(),new MessageEncoder(),new NettyServerHandler());

现在RequetLogoutVO类字节数组消息发送到服务端后:

先经过MessageDecoder解码,这个时候消息变为RequetLogoutVO类JAVA对象,其再经过解码器MessageDecoder2,这个时候解码器会对JAVA对象RequetLogoutVO进行再一次的解码,变为一个为止的消息,这个消息到达处理类NettyServerHandler已经变为乱码,无法正常处理。

所以在一个系统中,只定义一个解码器和一个编码器,要处理多种不同类型的消息是通过扩展消息类来实现,即一个通用的消息类可以表示各种类型的业务处理消息。

综合上面的分析,总结一下:

1.pipeline加载handler链的顺序有要求,解码器要在入境业务处理类前面;编码器要在出境业务处理类前面;

2.业务处理类拦截的是解码后为其类泛型中定义的java对象,解码不为其泛型定义的java对象的,其不处理,向后传播消息;

3.一个系统中只定义一个解码器和一个编码器,定义多个解码器和多个编码器会产生不可预知的错误。

参考:https://blog.csdn.net/weihao_/article/details/72780444

https://blog.csdn.net/zxhoo/article/details/17264263

猜你喜欢

转载自blog.csdn.net/w450093854/article/details/83930379