前言
本章将会介绍如何使用Netty搭建一个TCP服务器,本系列不会详细介绍Netty本身框架。
TCP 协议
传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
Netty 支持
导入依赖包
// gradle
compile group: 'io.netty', name: 'netty-all', version: '4.1.50.Final'
// maven
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>
Server 服务端
创建事件线程组
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(6);
bossGroup 里面初始化了一条线程(这里就是NioEventLoop,包含一个NIO SELECTOR,一个队列,一条线程),其作用就是负责处理所有客户端连接的请求,创建新连接(channel),然后注册到workerGroup 的其中一个NioEventLoop。之后该channel的所有读/写操作都由与Channel绑定的NioEventLoop来处理。
workerGroup 如上所说,主要是负责处理读/写操作,这里初始化了6条线程(NioEventLoop)去执行。
Channel的整个生命周期都由同一个NioEventLoop处理。
服务启动器
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup,workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//添加自定义处理器
socketChannel.pipeline()
.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(new TcpNettyHandler());
}
});
- serverBootstrap:为服务启动引导器,可以设置处理器,启动端口,绑定IP以及Channel配置等等。
- group:绑定线程组。
- NioServerSocketChannel:NIO模式。
- option(ChannelOption.SO_BACKLOG, 1024):标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度。
- childOption(ChannelOption.SO_KEEPALIVE, true):启用心跳保活机制
这里说明下option和childOption的区别(下面childHandler同理):
- option主要是作用于boss线程,用于处理新连接。
- childOption主要作用与worker线程,也就是已创建的channel。
handler处理器
以上都是对Channel的配置,到这里,才是处理数据的关键。
编解码器
上面有提到,TCP是基于字节流的传输协议,因此我们必须要用到编解码器来处理字节流。本文我们当其是字符串处理。
Netty提供了很多协议编解码器,无需我们自己去编写。
StringDecoder:字符串解码器,它会把输入的字节流解码成字符串。
StringEncoder:字符串编码器,它会把输出的String编码为字节流。
源码可见,它们其实也是属于handler。
TcpServerNettyHandler
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* Created by chenws on 2020/3/24.
*/
@ChannelHandler.Sharable
public class TcpServerNettyHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(msg);
//把消息写到缓冲区
ctx.write("I get the message " + msg);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
//刷新缓冲区,把消息发出去
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//异常处理,如果该handler不处理,将会传递到下一个handler
cause.printStackTrace();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("connect success!");
}
}
该handler继承了SimpleChannelInboundHandler,它是继承了ChannelInboundHandlerAdapter,其实就是帮我们转化了数据的类型,体现在泛型上。这里我们指定了< String >。
- channelRead0:当channel接收到数据时,该方法就会被执行。
- channelReadComplete:本次数据传输接收完后,该方法就会被执行。
- exceptionCaught:在接收数据过程中发生异常,该方法会被执行,如果不实现,则异常会传递到下一个handler。
- channelActive:channel建立成功后,该方法就会被执行。
那这里有个问题,channelReadComplete什么时候触发呢?换种说法也就是Netty是怎么知道本次传输已经读完呢?有两个条件,符合其一即会执行:
- read 返回0字节
- read 传递给它的缓冲区有1024个字节要填充,但最终少于1024个字节被填充。
Bind
ChannelFuture channelFuture = serverBootstrap.bind(8080).sync();
最后一步就是绑定端口,默认绑定本机的IP 地址。
至此,TCP Netty的服务端已经完成,下面来看看客户端。
Client 客户端
大部分代码的逻辑都跟server服务端差不多,这也是netty的强大之处,下面我就直接贴
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
try{
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new StringEncoder())
.addLast(new StringDecoder())
.addLast(new TcpClientNettyHandler());
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 8080).sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TcpClientNettyHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("接受来自服务器的消息:{}", msg);
ctx.write(msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
//刷出通道
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
}
}
主要区别是不再用ServerBootstrap,改用Bootstrap来启动客户端。代码没有什么难度,这里就不展开来说了。
总结
到此为止,Netty结合TCP已经介绍完,总的来说,Netty已经帮我们做了很多事情,如果对Netty感兴趣的,可以随时关注我的博客。
源码可以见:Netty-Learn