前言:用netty跑了那么久,,也没有真正细枝末节地认识过她,借着2020这年行业大环境,尽一份Coder应有的责任(总结),发现一个蛮优秀的coder有个系列netty一二三,和自己实际项目种用到的差无别二,就转过来(偷个小懒码了),也在必要地方加上了自己的示例图, 这学长内容亲试可关,昵称: 五月的仓颉;原文地址http://www.cnblogs.com/xrq730/p/5260294.html,转载请注明出处
------------------------------------------------------------------------------------------------------------------------
为什么使用Netty
Netty是业界最流行的NIO框架之一,它的健壮性、功能、性能、可定制性、可扩展性在同类框架中都是首屈一指的,它已经得到了成百上千的商用项目的证明。对于为什么使用Netty这个话题,我们先看一下使用原生的NIO有什么缺点:
也正是因为有种种缺点,因此不建议使用原生的NIO而是建议使用一些比较成熟的NIO框架例如Netty、Mina,这一系列文章讲的是Netty,Netty作为一款高性能NIO框架,其优点总结有:
正因为这些优点,Netty逐渐成为了Java NIO变成的首选框架。
Netty入门Demo
下面演示一下Netty的Demo(注:Demo来自Netty权威指南第三章),本文只写代码与演示结果,不做讲解,对Netty的使用基本讲解放在下一篇文章中,循序渐进,先感性地认识Netty,再理性地认识Netty中的东西。
提一下,本文及之后的文章Netty基于5.0.0.Alpha1这个版本,贴一下我自己的Maven配置(4.0.33.Final)两年开始用的版本,一直没升级过吧:
4.0.0
org.xrq.netty
netty-test
1.0.0
jar
UTF-8
junit
junit
4.11
test
io.netty
netty-all
5.0.0.Alpha1
org.slf4j
slf4j-api
1.7.25
ch.qos.logback
logback-classic
1.2.3
首先从服务端代码开始,定义一个TimeServer:
public class TimeServer {
public void bind(int port) throws Exception {
// NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());
// 绑定端口,同步等待成功
ChannelFuture f = b.bind(port).sync();
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放线程池资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
private class ChildChannelHandler extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel arg0) throws Exception {
arg0.pipeline().addLast(new TimeServerHandler());
}
}
}
TimeServerHandler这么定义:
public class TimeServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("The time server receive order:" + body);
String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(body) ? new Date(System.currentTimeMillis()).toString() : "BAD ORDER";
ByteBuf resp = Unpooled.copiedBuffer(currentTime.getBytes());
ctx.write(resp);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
即读取来自客户端的数据,如果是"QUERY TIME ORDER",则把当前时间写到Channel中去。至此,Netty服务端代码已经开发完毕。接下来是Netty客户端代码,首先还是TimeClient:
public class TimeClient {
public void connect(int port, String host) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer() {
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
};
});
// 发起异步连接操作
ChannelFuture f = b.connect(host, port).sync();
// 等待客户端连接关闭
f.channel().closeFuture().sync();
} finally {
// 优雅退出,释放NIO线程组
group.shutdownGracefully();
}
}
}
同样的,定义一个TimeClientHandler:
public class TimeClientHandler extends ChannelHandlerAdapter {
private static final Logger LOGGER = LoggerFactory.getLogger(TimeClientHandler.class);
private final ByteBuf firstMessage;
public TimeClientHandler() {
byte[] req = "QUERY TIME ORDER".getBytes();
firstMessage = Unpooled.buffer(req.length);
firstMessage.writeBytes(req);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMessage);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf)msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("Now is:" + body);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
LOGGER.warn("Unexcepted exception from downstream:" + cause.getMessage());
ctx.close();
}
}
客户端的操作为打印来自服务端的数据,这样,整个Netty Demo代码就写完了,结构比较清楚,都是一个Server+一个Handler的模式,Handler用于处理读取到的信息。
public class CoreTest {
@Test
public void timeServerTest() throws Exception {
new TimeServer().bind(8080);
}
@Test
public void timeClientTest() throws Exception {
new TimeClient().connect(8080, "127.0.0.1");
}
}
运行Demo
上面写完了Demo,接着写一下测试代码,很简单,分别运行bind方法和connect方法即可:
public class CoreTest {
@Test
public void timeServerTest() throws Exception {
new TimeServer().bind(8080);
}
@Test
public void timeClientTest() throws Exception {
new TimeClient().connect(8080, "127.0.0.1");
}
}
先运行timeServerTest让服务端先启动,再运行timeClientServer让客户端后启动,运行结果服务端的打印为:
The time server receive order:QUERY TIME ORDER
结合代码可以看到,服务端读取到了来自客户端的数据,数据内容为"QUERY TIME ORDER",接着服务端取自己的时间,传输给客户端,看一下客户端的打印:
Now is:Thu Apr 05 21:07:39 CST 2018
打印了来自服务端的时间,这样,利用Netty进行服务端+客户端的相互通信的Demo完成,有了这个Demo,对Netty有了感性上的认识,接着我们一点一点深入去学习Netty。
==================================================================================
我不能保证写的每个地方都是对的,每一行代码都经过了认真的推敲、仔细的斟酌。每一篇文章的背后,希望都能看到自己对于技术、对于生活的态度。
我相信乔布斯说的,只有那些疯狂到认为自己可以改变世界的人才能真正地改变世界。面对压力,我可以挑灯夜战、不眠不休;面对困难,我愿意迎难而上、永不退缩。
其实我想说的是,我只是一个程序员,这就是我现在纯粹人生的全部。
更多经历移步博客园:五月的仓颉
==================================================================================
另外补充两个自己项目种实际使用到的示例;
1:普通的 tcp-server。
2:基于标准的TLS1.2 双向验证的 tcp-server端启动初始化类
1:普通的 tcp-server:
/* *
* Netty 服务端代码
* @title SH
* @project
* @note 普通的tcp-server 启动初始化类
* @author alex
* @Date 2018/7/5
*/
@Component("tcpServer")
public class TcpServer extends Thread {
Logger logger = LoggerFactory.getLogger(this.getClass());
private final int MAX_LENGTH = 1024;
@Autowired
private VehMessageHandler vehMessageHandler;
/**
* netty服务端启动的入口
*
* @author alex
*/
public void run() {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
/**
* 最大连接数量设定 BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,
* 用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
*/
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 通过NoDelay禁用Nagle,使消息立即发出去,不用等待到一定的数据量才发出去
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChildChannelHandler());
//绑定端口,等待同步成功
ChannelFuture f = b.bind(NettyConfig.nettyPort).sync();
logger.info("服务监听启用成功,监听端口为:" + NettyConfig.nettyPort);
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.error("启动netty服务失败,请排查原因", e);
System.exit(1);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/* *
* 一个完整的业务可能会被TCP拆分成多个包进行发送(拆包),也有可能把多个小的包封装成一个大的数据包发送(粘包),
这个就是TCP的拆包和封包问题。
*/
class ChildChannelHandler extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ChannelPipeline channelPipeline = channel.pipeline();
/**
* @Deprecated
* 一。解决掉粘包、断包问题(GB23960 未把(0x23,0x23)标识符进行“转义处理”(body中),不能简单采用分隔符处理)。另:evready可以采用
* 式1:分隔符,按照guobiao消息中自定义的分隔符“0x23,0x23“,将消息报文分别拆开发送;
* 方式2:消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定, 这样即使粘包了通过接收方编程实现获取定长报文也能区分。
*/
// byte[] splitByte = new byte[]{0x23,0x23};
// ByteBuf delimiterStart = Unpooled.copiedBuffer(splitByte);
// channel.pipeline().addLast("framedecoder",new DelimiterBasedFrameDecoder(MAX_LENGTH, false,
// delimiterStart));
//方式2: 不需要去除分隔符,消息最大只能是1024
/*channel.pipeline().addLast( new DelimiterBasedFrameDecoder(1024, false, delimiter));*/
//这里需要加上把ByteBuf转换为Package对象的handler
// channel.pipeline().addLast(new StringDecoder());
// readIdle、writeIdle超时忽略,只启用一个AllIdle,这里设置为2min
/**
* 一。GB23960消息处理:采取根据body中数据数据单元长度截取长度 标识“整包数据”
*/
channelPipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
channelPipeline.addLast(new GBDelimiterMsgDecoder());
channelPipeline.addLast(new IdleStateHandler(0, 0,NettyConfig.timeOut,TimeUnit.SECONDS));
// channelPipeline.addLast("readTimeOut",new ReadTimeoutHandler(10));
channelPipeline.addLast("vehMessageHandler", vehMessageHandler);
}
}
}
2:基于标准的TLS1.2 双向验证的 tcp-server端
/* *
* Netty 服务端代码
* @title SH
* @project
* @note 基于标准的TLS1.2 双向验证的 tcp-server启动初始化类
* @author alex
* @Date 2018/7/5
*/
@Component("tcpTlsServer")
public class TcpTlsServer extends Thread {
Logger logger = LoggerFactory.getLogger(this.getClass());
private final int MAX_LENGTH = 1024;
@Autowired
private VehMessageHandler vehMessageHandler;
// @Autowired
// private SocketSSLHandler socketSSLHandler;
/**
* netty服务端启动的入口
*
* @author alex
*/
public void run() {
// 配置服务端的NIO线程组
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
/**
* 最大连接数量设定 BACKLOG用于构造服务端套接字ServerSocket对象,标识当服务器请求处理线程全满时,
* 用于临时存放已完成三次握手的请求的队列的最大长度。如果未设置或所设置的值小于1,Java将使用默认值50。
*/
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
// 通过NoDelay禁用Nagle,使消息立即发出去,不用等待到一定的数据量才发出去
.childOption(ChannelOption.TCP_NODELAY, true)
.childHandler(new ChildChannelHandler());
//绑定端口,等待同步成功
ChannelFuture f = b.bind(NettyConfig.nettyPort).sync();
logger.info("服务监听启用成功,监听端口为:" + NettyConfig.nettyPort);
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.error("启动netty服务失败,请排查原因", e);
System.exit(1);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/* *
* 一个完整的业务可能会被TCP拆分成多个包进行发送(拆包),也有可能把多个小的包封装成一个大的数据包发送(粘包),
这个就是TCP的拆包和封包问题。
*/
class ChildChannelHandler extends ChannelInitializer {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
/**
* 解决掉粘包、断包问题
* 方式1:分隔符,按照guobiao消息中自定义的分隔符“0x23,0x23“,将消息报文分别拆开发送;
* 方式2:消息定长,报文大小固定长度,不够空格补全,发送和接收方遵循相同的约定, 这样即使粘包了通过接收方编程实现获取定长报文也能区分。
*/
byte[] splitByte = new byte[]{0x23,0x23};
ByteBuf delimiterStart = Unpooled.copiedBuffer(splitByte);
// SSLEngine engine = SSLContextFactory.getSSLContext().createSSLEngine();
SSLEngine engine = SSLContextFactory.getSslContext().newEngine(channel.alloc());
// SSLEngine engine = SecureChatSslContextFactory.getServerContext().createSSLEngine();//客户端ca读取jks
engine.setUseClientMode(false);
engine.setNeedClientAuth(true); //需要客户端认证,默认为false
channel.pipeline().addLast("ssl",new SslHandler(engine));//不能添加true 否则偶尔出现unknow data未加密(Wireshark 抓包发现)
channel.pipeline().addLast(new StringEncoder(Charset.forName("UTF-8")));
channel.pipeline().addLast(new GBDelimiterMsgDecoder());
/*channel.pipeline().addLast("framedecoder",new DelimiterBasedFrameDecoder(MAX_LENGTH, false,
delimiterStart));*/
//方式2: 不需要去除分隔符,消息最大只能是1024
/*channel.pipeline().addLast( new DelimiterBasedFrameDecoder(1024, false, delimiter));*/
/**
* 2: 这里需要加上把ByteBuf转换为Package对象的handler
*/
// channel.pipeline().addLast(new StringDecoder());
// readIdle、writeIdle超时忽略,只启用一个AllIdle,这里设置为2min
channel.pipeline().addLast(new IdleStateHandler(0, 0,NettyConfig.timeOut,TimeUnit.SECONDS));
channel.pipeline().addLast("vehMessageHandler", vehMessageHandler);
}
}
}