学习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