这是netty官方的用户指南,请各位读者先阅读并按照上述文档操作一遍。
本次学习的netty环境版本是4.1.45。
按照文档,我们会写丢弃服务器(Discard Server),应答服务器(Echo Server)和时间服务器(Time Serve)。
我们会发现他们都用到了ServerBootstrap这个类,这个类就是整合各种资源,建造服务器的类,他们创建的逻辑基本相似,但各自的handler会有所其别,这是服务器功能不同的根本原因。
以DiscardServer为例子。
public class DiscardServer {
private int port;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); // 这个是Acceptor的线程组
EventLoopGroup workerGroup = new NioEventLoopGroup(); // 这个是用来处理已接收连接的线程组
try {
ServerBootstrap b = new ServerBootstrap(); // (2)
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class) // (3)指定channel的类型
.childHandler(new ChannelInitializer<SocketChannel>() { // (4)指定每个worker channel需要进行哪些处理
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128) // (5)指定option参数,SO_BACKLOG指服务端接受连接的队列长度,如果队列已满,客户端连接将被拒绝
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)连接保活
// Bind and start to accept incoming connections.
ChannelFuture f = b.bind(port).sync(); // (7)绑定服务器端口,启动从这开始
// Wait until the server socket is closed.
// In this example, this does not happen, but you can do that to gracefully
// shut down your server.
f.channel().closeFuture().sync();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
int port = 8080;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
}
new DiscardServer(port).run();
}
}
上面在第7步之前的操作,全部是在配置服务器的行为,指定好服务器的接收连接的线程组,处理已连接channel的线程组,channel的类型,option参数,各个channel要经过怎样的handler。最后在bind函数这正式开始启动服务器。
//io.netty.bootstrap.ServerBootstrap#group
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
return this;
}
//io.netty.bootstrap.AbstractBootstrap#group
public ServerBootstrap group(EventLoopGroup parentGroup, EventLoopGroup childGroup) {
super.group(parentGroup);
if (this.childGroup != null) {
throw new IllegalStateException("childGroup set already");
}
this.childGroup = ObjectUtil.checkNotNull(childGroup, "childGroup");
return this;
}
以group为例,只是把两个group赋值到的引导类中,等待启动时使用。
bind 才是核心代码,io.netty.bootstrap.AbstractBootstrap#doBind,是干活的方法,其中initAndRegister()中初始化了bossgroup。后面绑定了端口,启动了服务器。因为只是想告诉大家netty引导类中使用的各个方法有啥作用,所以就不在往下细扣源码了。
private ChannelFuture doBind(final SocketAddress localAddress) {
final ChannelFuture regFuture = initAndRegister();
final Channel channel = regFuture.channel();
if (regFuture.cause() != null) {
return regFuture;
}
if (regFuture.isDone()) {
// At this point we know that the registration was complete and successful.
ChannelPromise promise = channel.newPromise();
doBind0(regFuture, channel, localAddress, promise);
return promise;
} else {
// Registration future is almost always fulfilled already, but just in case it's not.
final PendingRegistrationPromise promise = new PendingRegistrationPromise(channel);
regFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
Throwable cause = future.cause();
if (cause != null) {
// Registration on the EventLoop failed so fail the ChannelPromise directly to not cause an
// IllegalStateException once we try to access the EventLoop of the Channel.
promise.setFailure(cause);
} else {
// Registration was successful, so set the correct executor to use.
// See https://github.com/netty/netty/issues/2586
promise.registered();
doBind0(regFuture, channel, localAddress, promise);
}
}
});
return promise;
}
}
我们来对照网络交互的几个关键点来看,
1.首先是建立连接(这个由bossGroup 来完成,其内部实现还是用到了select,当连接进来后会把进来的channel注册给workerGroup 的select,之后的读写由workerGroup 完成)
2.然后是服务器根据连接的信息进行业务处理,包括返回数据(这步有对应上面的childHandler的pipeline的初始化操作,即由客户端发来的消息会被封装成channel,然后经过这个pipeline中各种handler的处理,首先因为网络传输是字节流的形式,所以要先进行解码,变成我们能处理的字符,然后根据这些字符进行业务处理,业务处理也是其中的一个handler)
3.断开连接
基于可以直接处理socket传输的字节流这个特性,我们可以实现自己的应用层协议,通过一个handler,解析传入的信息,然后根据预先定义好的协议进行处理。netty不仅仅可以用来开发http服务器,有很多优秀的开源框架都是基于netty,然后实现了自己的协议,比如dubbo。