了解了了ChannelPipeline
、ChannelHandler
和 EventLoop
之后,如何将这些零件组织起来呢?
答案就是今天说的引导(Bootstrap)
1.Bootstrap
引导类的层次结构:
可以看到不管是服务器引导还是客户端引导都继承了Channel
接口,很显然,服务器致力于使用一个父Channel
来接受来自客户端的连接,并创建子Channel
以用于它们之间的通信;而客户端将最可能只需要一个单独的、没有父 Channel
的Channel
来用于所有的网络交互。
AbstractBootstrap 类的完整声明是:
public class ServerBootstrap extends AbstractBootstrap<ServerBootstrap, ServerChannel>
Bootstrap 类的完整声明是:
public class Bootstrap extends AbstractBootstrap<Bootstrap, Channel>
Bootstrap
类被用于客户端或者使用了无连接协议的应用程序中,许多方法都继承自AbstractBootstrap
类。
我们来看看它的一个例子:
//设置EventLoopGroup用于提供处理channel的事件的EventLoop
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
//设置用于 Channel 事件和数据的ChannelInboundHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data");
}
});
//连接到远程主机
ChannelFuture future = bootstrap.connect(new InetSocketAddress("www.baidu.com",80));
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
System.out.println("Connected!");
}else{
System.out.println("error");
future.cause().printStackTrace();
}
}
});
2.ServerBootstrap
在ServerBootstrap
的bind()
方法被调用时创建了一个ServerChannel
,当连接接收时,ServerChannel
将会创建一个新的子Channel
。
NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
//设置用于处理接受的子channel的IO和数据的handller
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx,
ByteBuf byteBuf) throws Exception {
System.out.println(ctx.channel().toString());
System.out.println("Server Receive:"+byteBuf.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush((Unpooled.copiedBuffer("Thanks ~", CharsetUtil.UTF_8)));
}
} );
final ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("Server bound");
System.out.println(future.channel().toString());
} else {
System.err.println("Bound attempt failed");
channelFuture.cause().printStackTrace();
}
}
} );
上面的程序可以运行,但是还要有个客户端才能验证出一些结论。
public class BootstrapDemo {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("Received data");
System.out.println(ctx.channel());
System.out.println(msg.toString(CharsetUtil.UTF_8));
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost",8080));
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if(future.isSuccess()) {
System.out.println("Connected!");
System.out.println(future.channel());
ChannelFuture future1 = future.channel().writeAndFlush( Unpooled.copiedBuffer("four you", CharsetUtil.UTF_8));
if(future1.isSuccess()) {
System.out.println("写过去了!");
}else{
System.out.println("写失败了!");
future1.cause().printStackTrace();
}
}else{
System.out.println("error");
future.cause().printStackTrace();
}
}
});
}
}
服务端先运行,客户端后运行,然后客户端结果是:
Connected!
[id: 0x228d8d17, L:/127.0.0.1:63645 - R:localhost/127.0.0.1:8080]
写过去了!
Received data
[id: 0x228d8d17, L:/127.0.0.1:63645 - R:localhost/127.0.0.1:8080]
Thanks ~
服务端:
Server bound
[id: 0x64e9aab0, L:/0:0:0:0:0:0:0:0:8080]
[id: 0xccf20a12, L:/127.0.0.1:8080 - R:/127.0.0.1:63645]
Server Receive:four you
- 客户端两次打印出的channel是同一个引用,正好验证了上面那句客户端(BootStrap)将最可能只需要一个单独的、没有父
Channel
的Channel
来用于所有的网络交互。 - 调用服务端bind()方法会创建一个Channel,每有一个新的连接时,创建一个子channel。
3.代理服务器的实现
注意:这个例子先别细看,先把它运行起来,注意启顺序就是代码的顺序。
Server bound
Receive:four you from client!~after proxy!
Bind Ok
从客户端读到:four you from client!
从服务端读到:Thanks ~ from server
Connected!
写过去了!
received:Thanks ~ from server~after proxy!
要是你的运行结果和上面一样,那就ok,先跳过代码看下面的解释吧!
Code1
public class ServerBootstrapDemo {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
protected void channelRead0(ChannelHandlerContext ctx,
ByteBuf byteBuf) throws Exception {
// System.out.println(ctx.channel().toString());
System.out.println("Receive:" + byteBuf.toString(CharsetUtil.UTF_8));
ctx.writeAndFlush((Unpooled.copiedBuffer("Thanks ~ from server", CharsetUtil.UTF_8)));
}
});
final ChannelFuture future = bootstrap.bind(new InetSocketAddress(8080));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture)
throws Exception {
if (channelFuture.isSuccess()) {
System.out.println("Server bound");
// System.out.println(future.channel().toString());
} else {
System.err.println("Bound attempt failed");
channelFuture.cause().printStackTrace();
}
}
});
}
}
Code2
public class ProxyServerBootStrapDemo {
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.childHandler(new SimpleChannelInboundHandler<ByteBuf>() {
ChannelFuture connectFutrue;
@Override
public void channelActive(final ChannelHandlerContext bigctx) throws Exception {
//创建一个 Bootstrap类的实例以连接到远程主机
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class)
//为入站 I/O 设置ChannelInboundHandler
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
@Override
//从目标服务器那读数据
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
System.out.println("从服务端读到:" + msg.toString(CharsetUtil.UTF_8));
String message = msg.toString(CharsetUtil.UTF_8) + "~after proxy!";
bigctx.writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
}
});
//使用与分配给已被接受的子Channel相同的EventLoop
bootstrap.group(bigctx.channel().eventLoop());
connectFutrue = bootstrap.connect(
new InetSocketAddress("localhost", 8080));
}
@Override
//从客户端那读到数据
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
if (connectFutrue.isDone()) {
System.out.println("从客户端读到:" + msg.toString(CharsetUtil.UTF_8));
String message = msg.toString(CharsetUtil.UTF_8) + "~after proxy!";
connectFutrue.channel().writeAndFlush(Unpooled.copiedBuffer(message, CharsetUtil.UTF_8));
}
}
});
ChannelFuture future = bootstrap.bind(new InetSocketAddress(8088));
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("Bind Ok");
} else {
System.out.println("Bind error");
future.cause().printStackTrace();
}
}
});
}
}
Code3
public class BootstrapDemo {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new SimpleChannelInboundHandler<ByteBuf>() {
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
//System.out.println(ctx.channel());
System.out.println("received:" + msg.toString(CharsetUtil.UTF_8));
}
});
ChannelFuture future = bootstrap.connect(new InetSocketAddress("localhost", 8088));
future.addListener(new ChannelFutureListener() {
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
System.out.println("Connected!");
//System.out.println(future.channel());
ChannelFuture future1 = future.channel().writeAndFlush(Unpooled.copiedBuffer("four you from client!", CharsetUtil.UTF_8));
if (future1.isSuccess()) {
System.out.println("写过去了!");
} else {
System.out.println("写失败了!");
future1.cause().printStackTrace();
}
} else {
System.out.println("error");
future.cause().printStackTrace();
}
}
});
}
}
看一下简化了的流程图
该程序相当于一个代理。ProxyServerBootStrapDemo类作为中间的代理,它接收客户端BootStrap的请求,转发给类ServerBootStrap。基于Netty的实现。主要是ProxyServerBootStrap中ServerChannel的子Channel的EventLoop 传递给Bootstrap的group()方法来共享该EventLoop。也就是说共享了同一个线程。没了线程间的通信,没有了上下文切换。
4.添加多个ChannelHandler
我们都在引导的过程中调用了handler()
或者childHandler()
方法来添加单个的ChannelHandler
,有些情况下我们需要添加多个handller
,通过在ChannelPipeline
中将它们链接在一起来部署尽可能多的ChannelHandler
。但是看了Bootstrap的源码就会发现,它只有一个ChannelHandler
的成员变量。
Netty 提供了一个特殊的ChannelInboundHandlerAdapter
子类:
public abstract classChannelInitializer<C extends Channel>extends ChannelInboundHandlerAdapter
例子:
public class ChannelInitializerDemo {
public static void main(String[] args) {
ServerBootstrap bootstrap = new ServerBootstrap();
EventLoopGroup group = new NioEventLoopGroup();
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
//注册一个 ChannelInitializerImpl 的实例来设置 ChannelPipeline
.childHandler(new ChannelInitializerImp());
bootstrap.bind(80);
}
}
class ChannelInitializerImp extends ChannelInitializer {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline().addLast(new ServerHandller());
}
}
这个方法提供了一种将多个ChannelHandler
添加到一个ChannelPipeline
中的简便方法。你只需要简单地向Bootstrap
或ServerBootstrap
的实例提供你的ChannelInitializer
实现即可,并且一旦Channel
被注册到了它的EventLoop
之后,就会调用你的initChannel()
,ChannelHandler
添加到一个ChannelPipeline
中。