netty实战 第二章
编写Echo服务器
一个netty服务器至少需要两个部分:
- 至少一个ChannelHander,实现了服务器对接收到客户端数据的处理,即业务逻辑。
- 引导,配置服务器的启动代码,比如配置监听端口等。
ChannelHander和业务逻辑
- ChannerHander是一个父接口,负责接收并响应事件通知。数据处理逻辑都包含在这些核心接口中。
- ChannelInboundHandler定义了响应入站事件的方法。其中ChannelInboundHandlerAdapter提供了ChannelInboundHandler的默认实现。
只需重写ChannelInboundHandlerAdapter几个方法就可实现服务端接收数据的业务逻辑
- channelRead() 每次传入消息时,都会调用该方法。
- channelReadComplete() 当消息读取到末尾时,调用该方法
- exceptionCaught() 发生异常时,会调用该方法
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf in = (ByteBuf) msg;
System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));
ctx.write(in);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
channelRead表示接收消息,可以看到msg转换成了ByteBuf,然后打印.每次打印完后,channelReadComplete也会调用,如果传一个超长的字符串过来,超过1024个字母长度,channelRead会调用多次,而channelReadComplete只调用一次。
启动类代码
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public static void main(String[] args) throws Exception {
if (args.length != 1) {
System.err.println("Usage: " + EchoServer.class.getSimpleName() +" <port>");
}
int port = Integer.parseInt(args[0]);
new EchoServer(port).start();
}
public void start() throws Exception {
final EchoServerHandler serverHandler = new EchoServerHandler();
EventLoopGroup group = new NioEventLoopGroup(); // 使用nio传输,指定NioEventLoopGroup来接受和处理新链接
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
.channel(NioServerSocketChannel.class) // 将channel指定为NioServerSocketChannel
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>(){
@Override
public void initChannel(SocketChannel ch)throws Exception {
// 当一个新的连接产生时,一个新的子 Channel 将会被创建,而 ChannelInitializer 将会把EchoServerHandler添加到该 Channel的ChannelPipeline中
ch.pipeline().addLast(serverHandler);
// 对 sync()方法的调用将导致当前Thread阻塞,一直到绑定操作完成为止
ChannelFuture f = b.bind().sync();
// 阻塞等待直到服务器的Channel关闭
f.channel().closeFuture().sync();
}finally {
// 关闭EventLoopGroup,并释放所有的资源
group.shutdownGracefully().sync();
}
});
实现Echo服务器的过程
- EchoServerHandler 实现了业务逻辑;
- main()方法引导了服务器;
- 导过程中所需要的步骤如下:
- 创建一个 ServerBootstrap 的实例以引导和绑定服务器;
- 创建并分配一个 NioEventLoopGroup 实例以进行事件的处理,如接受新连接以及读/写数据;
- 指定服务器绑定的本地的 InetSocketAddress;
- 使用一个 EchoServerHandler 的实例初始化每一个新的 Channel;
- 调用 ServerBootstrap.bind()方法以绑定服务器
编写Echo客户端
与服务的一样,客户端还需实现自己的ChannelInboundHandler。通过重写SimpleChannelInboundHandler几个方法实现客户端的业务逻辑
- channelActive() 与服务端建立连接后调用
- channelRead0() 接收到服务器信息后调用
- exceptionCaught() 发生异常时调用
@Sharable
public class EchoClientHandler extendsSimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) {
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!",CharsetUtil.UTF_8));
}
@Override
public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause) {
cause.printStackTrace();
ctx.close();
}
public class EchoClient {
private final String host;
private final int port;
public EchoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(new InetSocketAddress(host, port))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
ChannelFuture f = b.connect().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
if (args.length != 2) {
System.err.println("Usage: " + EchoClient.class.getSimpleName() +" <host> <port>");
return;
}
String host = args[0];
int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
实现Echo客户端的过程
- 为进行事件处理分配了一个 NioEventLoopGroup 实例,其中事件处理包括创建新的连接以及处理入站和出站数据;
- 为服务器连接创建了一个 InetSocketAddress 实例;
- 当连接被建立时,一个 EchoClientHandler 实例会被安装到(该 Channel 的)ChannelPipeline 中;
- 在一切都设置完成后,调用 Bootstrap.connect()方法连接到远程节点;

浙公网安备 33010602011771号