Echo客户端
Echo,如同Linux的Echo命令,就是打印输出的内容,它的工作内容:
- 连接服务器
- 发送信息
- 等待和接收从服务器返回的同样的信息
- 关闭连接
使用ChannelHandler实现客户端业务逻辑
如同服务器一样,使用ChannelInboundHandler接口来处理数据。服务器继承了ChannelInboundHandlerAdapter,这里客户端我们使用SimpleChannelInboundHandler来处理所有的任务,需要覆写三个方法:
- channelActive() - 服务器的连接被建立后调用
- channelRead0() - 从服务器收到数据后调用
- exceptonCaught() - 捕获一个异常时调用
代码如下:
// 1.@Sharable标记这个类的实例可以在 channel 里共享
@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// 2.当被通知该channel是活动的时候就发送消息
ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks !", CharsetUtil.UTF_8));
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
// 3.记录接收到消息
System.out.println("Client received: " + msg.toString(CharsetUtil.UTF_8));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// 4.捕获错误,并关闭连接
cause.printStackTrace();
ctx.close();
}
}
注:
1、建立连接后,channelActive()方法被调用一次,我们使用Netty编码一个字符串,发送到服务器。
2、覆写channelRead0()方法,在接收到数据时调用。(注意,由服务器发送的消息可以以块的形式被接收。如:当服务器发送 5 个字节是不是保证所有的 5 个字节会立刻收到,即使是只有 5 个字节,channelRead0() 方法可被调用两次,第一次用一个ByteBuf(Netty的字节容器)装载3个字节和第二次一个 ByteBuf 装载 2 个字节。唯一要保证的是,该字节将按照它们发送的顺序分别被接收。 (注意,这是真实的,只有面向流的协议如TCP))
SimpleChannelInboundHandler vs. ChannelInboundHandler
何时用这两个要看具体业务的需要。在客户端,当 channelRead0() 完成,我们已经拿到的入站的信息。当方法返回时,SimpleChannelInboundHandler 会小心的释放对 ByteBuf(保存信息) 的引用。而在 EchoServerHandler,我们需要将入站的信息返回给发送者,由于 write() 是异步的,在 channelRead() 返回时,可能还没有完成。所以,我们使用 ChannelInboundHandlerAdapter,无需释放信息。最后在 channelReadComplete() 我们调用 ctxWriteAndFlush() 来释放信息。
客户端代码
客户端引导需要 host 、port 两个参数连接服务器。
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 {
// 1.客户端创建Bootstrap,服务器创建的是ServerBootstrap
Bootstrap b = new Bootstrap();
// 2.指定EventLoopGroup来处理客户端事件,因为用了NIO传输,所以用NioEventLoopGroup的实现
b.group(group)
// 3.使用的 channel 类型是一个用于 NIO 传输
.channel(NioSocketChannel.class)
// 4.设置服务器的 InetSocketAddress
.remoteAddress(new InetSocketAddress(host, port))
// 5.当建立一个连接和一个新的通道时,创建添加到 EchoClientHandler 实例到channel pipeline
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new EchoClientHandler());
}
});
// 6.连接到远程;等待连接完成
ChannelFuture cf = b.connect().sync();
// 7.阻塞直到 Channel 关闭
cf.channel().closeFuture().sync();
} finally {
// 8.调用 shutdownGracefully() 来关闭线程池和释放所有资源
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;
}
final String host = args[0];
final int port = Integer.parseInt(args[1]);
new EchoClient(host, port).start();
}
}
流程:
1、创建一个Bootstrap来初始化客户端。
2、一个NioEventLoopGroup实例被分配给处理该事件的处理,这包括创建新的连接和处理入站和出站数据。
3、创建一个InetSocketAddress以连接到服务器。
4、连接好服务器之时,将安装一个EchoClientHandler在pipeline,处理业务逻辑。
5、之后Bootstrap.connect()被调用连接到远程的的服务器。