任务:
创建开发环境
编写一个Echo服务器和客户端
编译测试应用
框架:
同时连到一个服务器的多个客户端。理论上,可支持的客户端数量是受可支配系统资源限制的(以及在用的JDK版本带来的限制)。Echo客户端和服务器之间的交互非常简单; 客户端建立一个连接后,它送一条或者多条消息到服务器,然后服务器再将该消息送回客户端。虽然这个交互看起来不那么实用,但却是一个典型的客户端/服务器系统请求-响应交互过程的示例。
服务器端:
所有的Netty服务器都需要:
至少一个ChannelHandler—这个组件实现了服务器如何处理从客户端收到的数据—它的业务逻辑。
Bootstrapping—这是配置服务器的启动代码。至少要做的是,它把服务器绑到一个可以监听连接请求的端口上。
ChannelInboundHandler:定义了作用于输入事件的方法。这个简单的应用只需要这个接口的一些方法,所以用子类ChannelInboundHandlerAdapter就足够了,这个子类提供了ChannelInboundHandler的缺省实现。
channelRead()—每次收到消息时被调用
channelReadComplete()—用来通知handler上一个ChannelRead()是被这批消息中的最后一个消息调用
exceptionCaught()—在读操作异常被抛出时被调用
ChannelInboundHandlerAdapter有简明直接的API,每个方法都可以被重写(overridden),钩挂到(hook into)event生命周期的适当时间点。因为你需要处理所有收到的数据,所以你得重写channelRead()。在这个服务器里,你只要简单地把收到的消息送回远端。
重写exceptionCaught()让你可以对任何Throwable的子类型做出反应—在这里,你打印了这个异常然后关闭了这个连接。一个更加复杂的应用也许会试着从异常中恢复,但是在这个例子中,仅仅把连接关闭就向远端发出了错误已经发生的信号。
如果异常没有被捕获会发生什么?
每个Channel有一个对应的ChannelPipeline,这个ChannelPipeline有一串ChannelHandler实例。默认情况下,一个handler会传递某个handler方法的调用到下一个handler。所以,如果在这个传递链中exceptionCaught()没有实现,异常会一直走到ChannelPipeline的终点,然后被载入日志。因为这个原因,你的应用应该提供至少一个实现了exceptionCaught()的handler。
除了ChannelInboundHandlerAdapter,还有许多其他ChannelHandler子类和实现可以学习。ChannelHandlers被不同类型的events调用
应用程序通过实现或者扩展ChannelHandlers来钩挂到event的生命周期,并且提供定制的应用逻辑在结构上,ChannelHandlers解耦你的业务逻辑和网络代码。这会简化开发过程,因为代码会随着需求的变化而变化。
Bootstrapping服务器:
绑定到一个端口,服务器在这个端口上监听并且接收新的连接请求
配置Channels,来通知EchoServerHandler实例收到的消息
传输:传输层用来提供端对端或者主机对主机通信的服务。互联网通信是建立在TCP传输的基础上的。NIO传输(NIO transport)指的是一个类似于TCP传输的方式,不过它服务器端的性能增强由Java NIO的实现带来的。
EchoServerHandler实现业务逻辑
main()方法启动(bootstrap)服务器
以下是Bootstrapping中的必需步骤:
创建一个ServerBootstrap实例来启动和绑定服务器
创建并且分配一个NioEventLoopgroup实例来处理event,比如接受新的连接和读/写数据
指定本地InetSocketAddress到服务器绑定的端口
用一个EchoServerHandler实例来初始化每个新的Channel
调用ServerBootstrap.bind()来绑定服务器。
Echo客户端
连接到服务器
送出一条或者多条消息
对每条消息,等待并且从服务器收回一条同样的消息
关闭连接
和服务器一样,客户端会有一个ChannelInboundHandler来处理数据。扩展SimpleChannelInbondHandler类来处理所有的任务。
channelActive()—和服务器的连接建立起来后被调用
channelRead0()—从服务器收到一条消息时被调用
exceptionCaught()—处理过程中异常发生时被调用
SimpleChannelInboundHandler对比ChannelInboundHandler
你也许想知道为什么我们在客户端用了SimpleChannelInboundHandler,而不是EchoServerHandler中用的ChannelInboundHandlerAdapter。这和两个因素有关:业务逻辑如何处理消息,以及Netty如何管理资源。
在客户端,当channelRead0()完成时,你收到输入消息并且已经处理完成。当这个方法返回时,SimpleChannelInboundHandler会负责释放用于存放消息的ByteBuf的内存引用。
在EchoServerHandler中,你仍然要把输入的消息送回给发送端,而write()操作是异步的,直到channelRead()返回可能都没有完成(如代码清单2.1所示)。所以,EchoServerHandler扩展了ChannelInboundHandlerAdapter,不会在channelRead()返回的时候释放消息的内存引用。在writeAndFlush()被调用时,这个消息的内存引用会在EchoServerHandler的channelReadComplete()方法中被释放
bootstrapping一个客户端和bootstrapping一个服务器类似,不同于服务器绑定到一个监听端口,客户端用主机和端口参数来连接到一个远程地址,这里就是指Echo服务器.跟之前一样,使用的是NIO传输方式。注意,你在客户端和服务器中可以用不同的传输方式;比如,服务器端用NIO,在客户端用OIO。在第四章我们会分析一些因素和情景,让你为一个特定的使用案例选择一个特定传输方式。创建一个Bootstrap实例来初始化客户端
分配一个NioEventLoopgroup实例来处理事件,包括创建新的连接和处理输入输出数据
创建一个InetSocketAddress用于连接到服务器
当连接建立,一个EchoClientHandler会被装入pipeline
所有东西都创建完毕后,调用Bootstrap.connet()连接到远端。
完成客户端开发后,你可以接着编译和测试这个系统。
代码:
package server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
@Sharable
//可以被channel共享
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg) throws java.lang.Exception {
ByteBuf in = (ByteBuf) msg;
System.out.println("接收到:"+in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)throws java.lang.Exception {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);//将收到的消息写入发送,不刷新输出消息
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) throws java.lang.Exception {
cause.printStackTrace();
ctx.close();
}
}
package server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) {
if(args.length != 1){
System.err.println("使用:"+EchoServer.class.getSimpleName()+"<Port>");
}
int port = Integer.parseInt(args[0]);
try {
new EchoServer(port).start();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void start() throws InterruptedException {
final EchoServerHandler serverHandler = new EchoServerHandler();
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap bootstrap = new ServerBootstrap();
//配置
bootstrap.group(group)
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
protected void initChannel(SocketChannel ch)throws java.lang.Exception {
ch.pipeline().addLast(serverHandler);//sharable
}
});
ChannelFuture f = bootstrap.bind().sync();//异步绑定
f.channel().closeFuture().sync();//阻塞当前线程知道关闭操作
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
group.shutdownGracefully().sync();//释放资源
}
}
}