Netty 初体验

学习Netty之前,需要先了解Java IO、NIO、AIO。Netty可以说是Java NIO的集成框架,将Java NIO的能力进行升华,更具备快捷、高可用。

在学习《Netty权威指南 第2版》的第四章后,进行总结。

以下是具体示例,示例中主要介绍如何通过Netty框架搭建服务端、客户端,并如何通过LineBaseFrameDecoder+StringDecoder处理TCP的粘包和拆包。

第一步:先创建服务中心TimeServer

package com.netty.demo;

import com.netty.demo.handler.TimeServerHandler;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class TimeServer {

	public void bind(int port) throws InterruptedException {
		// 配置服务端的NIO线程组
		// NioEventLoopGroup也是Reactor线程组,专门用于网络事件处理
		// 创建两个的原因,一个是用来服务端接受客户端的连接;一个用来进行SocketChannel的网络读写
		EventLoopGroup parentGroup = new NioEventLoopGroup();

		EventLoopGroup childGroup = new NioEventLoopGroup();

		try {
			// ServerBootstrap是启动Nio服务端的辅助启动类,目的是降低服务端的开发复杂度
			ServerBootstrap bootstrap = new ServerBootstrap();
			bootstrap.group(parentGroup, childGroup).channel(NioServerSocketChannel.class)
					.option(ChannelOption.SO_BACKLOG, 1024).childHandler(new TimeServerChannelInitializer());

			// 绑定端口,并同步等待成功
			ChannelFuture cFuture = bootstrap.bind(port).sync();

			// 等待服务监听端口关闭
			cFuture.channel().closeFuture().sync();

		} finally {
			// 释放线程资源
			parentGroup.shutdownGracefully();

			childGroup.shutdownGracefully();
		}
	}
	
	/**
	 * 初始化SocketChannel对象,设置业务处理类、解码器等
	 * @author Administrator
	 *
	 */
	private class TimeServerChannelInitializer extends ChannelInitializer{

		@Override
		protected void initChannel(SocketChannel socketChannel) throws Exception {
			// LineBasedFrameDecoder解码器,工作原理:能依次遍历ByteBuf中可读取字节,
			// 判断是否有“\n”或者“\r\n”,如果有,就以此位置为结束位置,从可读取索引到结束位置区间的字节组成一行。
			// 它是以换行符为结束标志的解码器,支持携带结束符或者不携带结束符两种解码方式,同时支持配置单行的最大长度。
			// 如果连续读取到最大长度后仍然没有发现换行符,就会抛出异常,同时忽略掉之前读到的异常码流。
			socketChannel.pipeline().addLast(new LineBasedFrameDecoder(1024));
			// StringDecoder:将读取到的对象转为String
			// LineBasedFrameDecoder+StringDecoder:按行切换的文本解码器,被设计用来解决TCP的粘包和拆包。
			socketChannel.pipeline().addLast(new StringDecoder());
			// 绑定TimeServer处理逻辑
			socketChannel.pipeline().addLast(new TimeServerHandler());
			
		}
		
	}

	public static void main(String[] args) throws InterruptedException {

		TimeServer timeServer = new TimeServer();
		// 监听8088端口
		timeServer.bind(8088);
	}

}

第二步:实现TimeServer的业务逻辑类,需要继承ChannelHandlerAdapter类。

package com.netty.demo.handler;

import java.util.Date;

import com.demo.util.PrintCountUtil;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class TimeServerHandler extends ChannelHandlerAdapter {

	private int count;

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 接收客户端发送的消息
            String body = (String) msg;
		System.out.println(
				"第" + PrintCountUtil.getCount() + "步:The time server receive order: " + body + "  count:" + (++count));
		String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString()
				: "BAD ORDER";

		currentTime = currentTime + System.getProperty("line.separator");
		ByteBuf writeBuf = Unpooled.copiedBuffer(currentTime.getBytes());
		ctx.write(writeBuf);
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		// 作用:将消息发送队列中的消息写入到SocketChannel中发送给对象。从性能角度考虑,为了放置频繁唤醒Selector进行消息发送。
		// Netty的write方法并不直接将消息写入到SocketChannel中,调用write方法只是把待发送的消息放到发送缓冲数组中,再通过调用flush方法,
		// 将发送缓冲区中的消息全部写到SocketChannel中。
		ctx.flush();
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println("异常:" + cause.getMessage());
		ctx.close();
	}

}

第三步:创建客户端TimeClient。

package com.netty.demo;

import com.demo.util.PrintCountUtil;
import com.netty.demo.handler.TimeClientHandler;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;

public class TimeClient {

	public void connect(String host, int port) throws InterruptedException {

		// 配置客户端的线程组
		EventLoopGroup clientGroup = new NioEventLoopGroup();

		try {

			Bootstrap bootstrap = new Bootstrap();
			bootstrap.group(clientGroup).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
					.handler(new ChannelInitializer() {

						@Override
						protected void initChannel(SocketChannel channel) throws Exception {
							channel.pipeline().addLast(new LineBasedFrameDecoder(1024));
							channel.pipeline().addLast(new StringDecoder());
							channel.pipeline().addLast(new TimeClientHandler());
						}
					});

			System.out.println("第" + PrintCountUtil.getCount() + "步:发起异步连接-Bootstrap.connect(host, port)");
			// 发起异步连接
			ChannelFuture cFuture = bootstrap.connect(host, port).sync();
			// 等待客户端连接关闭
			cFuture.channel().closeFuture().sync();

		} finally {
			clientGroup.shutdownGracefully();
		}
	}

	public static void main(String[] args) throws InterruptedException {
		new TimeClient().connect("127.0.0.1", 8088);
	}
}

第四步:创建客户端的业务逻辑处理类,需要继承ChannelHandlerAdapter

package com.netty.demo.handler;

import com.demo.util.PrintCountUtil;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;

public class TimeClientHandler extends ChannelHandlerAdapter {
	
	private byte[] req;
	
	private int count;

	public TimeClientHandler() {
		req = ("QUERY TIME ORDER" + System.getProperty("line.separator")).getBytes();
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("第" + PrintCountUtil.getCount() + "步:Clinet: 执行channelActive");
		// 当客户端与服务端TCP链路建立成功后,Netty的NIO线程会调用channelActive
		// 向服务端发送指令
		//ctx.writeAndFlush(firstMessage);
		ByteBuf messageBuf = null;
		for(int i=0;i<100;i++) {
			messageBuf = Unpooled.buffer(req.length);
			messageBuf.writeBytes(req);
			ctx.writeAndFlush(messageBuf);
		}
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		// 当服务端返回消息时,执行
		String body = (String) msg;
		System.out.println("NOW is : " + body+" count:"+(++count));
	}
	
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		System.out.println("异常:" + cause.getMessage());
		ctx.close();
	}
}

执行结果

第0步:通过ServerBootstrap注册服务
第1步:ServerBootstrap.bind(port)  绑定监听端口
>>>>>>>>>>>>>>
第2步:发起异步连接-Bootstrap.connect(host, port)
第3步:Client: ChannelInitializer.initChannel()
Client: ChannelPipeline增加ChannelHandlerAdapter对象
第4步:Server: ChannelInitializer.initChannel
Server: ChannelPipeline增加ChannelHandlerAdapter对象
第5步:Clinet: 执行channelActive
第6步:Server: 执行channelRead
第7步:The time server receive order: QUERY TIME ORDER  count:1
第8步:Server: 执行channelRead
第9步:The time server receive order: QUERY TIME ORDER  count:2
第10步:Server: 执行channelRead
第11步:The time server receive order: QUERY TIME ORDER  count:3
第12步:Server: 执行channelRead
第13步:The time server receive order: QUERY TIME ORDER  count:4
...
第204步:Server: 执行channelRead
第205步:The time server receive order: QUERY TIME ORDER  count:100
第206步:Server: 执行channelReadComplete
第207步:Clinet: 执行channelRead
第208步:NOW is : Tue Jun 25 15:35:14 CST 2019 count:1
第209步:Clinet: 执行channelRead
第210步:NOW is : Tue Jun 25 15:35:14 CST 2019 count:2
第211步:Clinet: 执行channelRead
第212步:NOW is : Tue Jun 25 15:35:14 CST 2019 count:3
...
第401步:Clinet: 执行channelRead
第402步:NOW is : Tue Jun 25 15:35:14 CST 2019 count:98
第403步:Clinet: 执行channelRead
第404步:NOW is : Tue Jun 25 15:35:14 CST 2019 count:99
第405步:Clinet: 执行channelRead
第406步:NOW is : Tue Jun 25 15:35:14 CST 2019 count:100

 

你可能感兴趣的:(Netty 初体验)