Netty是一个NIO客户端服务端框架,支持快速、简单地开发网络应用程序,可以迅速地构建起TCP和UDP服务程序。
以下摘自《Netty权威指南 第2版》
- NIO的类库和API繁杂,使用麻烦,需要熟练掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等;
- 需要具备其它的额外技能做铺垫,例如熟悉Java多线程编程。这是因为NIO编程设计到Reactor模式,你必须对多线程和网络编程非常熟悉,才能编写出高质量的NIO程序;
- 可靠性能力补齐,工作量和难度都非常大。例如客户端面临重连、网络闪断、半包读写、失败缓存、网络拥塞和异常码流的处理等问题,NIO编程的特点是功能开发相对容易,但是可靠性能力补齐的工作量和难度都非常大;
- JDK NIO的bug,例如臭名昭著的epoll bug,它会导致Selector空轮询,最终导致CPU 100%。官方声称在JDK1.6版本的update8修复该问题,但是直到JDK 1.7版本该问题仍旧存在,只不过该bug发生概率降低了一些而已,它并没有得到根本性的解决。
由于上述原因,在大多数场景下,不建议直接使用JDK的NIO类库,除非你精通NIO编程或有特殊的需求。在绝大多数的业务场景中,我们可以使用NIO框架Netty来进行NIO编程,它既可以作为客户端也可以作为服务端,同时支持UDP和异步文件传输,功能非常强大。
以下内容介绍了如何使用Netty提供的API构建一个基本的服务端程序。
private void bind(int port) {
// 初始化两个线程租
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
// ServerChannel的引导程序,为了使ServerChannel更加易用
ServerBootstrap bootstrap = new ServerBootstrap();
/* bossGroup与workerGroup是两个线程组,bossGroup用于接收客户端的请求,
workerGroup用于处理接收到的SocketChannel的网络读写 */
bootstrap.group(bossGroup, childGroup)
.channel(NioServerSocketChannel.class)// 设置需要启动的Channel实例
/* 设置NioServerSocketChannel的TCP参数,
backlog的解析请看这里 https://www.cnblogs.com/qiumingcheng/p/9492962.html */
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());// 设置处理接收到的请求的处理类
ChannelFuture future = bootstrap.bind(port).sync();// 为ServerBootstrap绑定监听端口
future.channel().closeFuture().sync();// 关闭NioServerSocketChannel
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放线程组
bossGroup.shutdownGracefully();
childGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
SimpleTimeServer server = new SimpleTimeServer();
server.bind(8088);
}
ServerBootstrap的childHandler函数要求传入一个实现了io.netty.channel.ChannelHandler的实例,childHandler是提供给childGroup中的线程的时间回调,从名字也可以看出这一点,我们传入的是一个ChildChannelHandler实例,这是我们自己实现的一个类,它并不是一个直接处理SocketChannel的类,而是继承了ChannelInitializer,用来组织更多的实际的处理类。
ChildChannelHandler代码:
/**
* ServerBootstrap中的childGroup中接收到的ChildChannel的处理类,
* 用于处理接收到的请求
*/
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) {
/* 这里的SocketChannel是Netty提供的,pipeline是一个ChildHandler的list,
即我们可以使用多个Handler对接收到的SocketChannel进行处理 */
socketChannel.pipeline()
.addLast(new ActualHandler1(), new ActualHandler2());
}
}
上面说到了,ChildChannelHandler其实只是一个用来组织实际处理类的聚合管理类,实际的处理类是代码中的ActualHandler1和ActualHandler2,我们看下一个ActualHandler应该怎样实现。
private class ActualHandler extends ChannelHandlerAdapter {
/**
* 有新建连接创建时,会首先回调此函数
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("有新建连接创建");
}
/**
* 读取接收到的ServerChannel中的数据
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf reqBuf = (ByteBuf) msg;
byte[] bytes = new byte[reqBuf.readableBytes()];
reqBuf.readBytes(bytes);
String reqMsg = new String(bytes, StandardCharsets.UTF_8);
System.out.println("接收到请求消息:" + reqMsg);
ByteBuf respBuf = Unpooled.copiedBuffer("你好,我已经收到你的消息了".getBytes(StandardCharsets.UTF_8));
ctx.write(respBuf);
}
/**
* read完成后调用的函数,进行一些数据读取完毕后的操作
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("读取完毕");
ctx.flush();
}
/**
* 出现异常时的回调函数
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
System.out.println("出异常了!");
cause.printStackTrace();
ctx.close();
}
}
解析:
ActualHandler就是真正用来处理接收到的SocketChannel的处理类,在这里我们可以进行消息的读写解码等等一些操作。
我们这里的ActualHandler继承了ChannelHandlerAdapter,ChannelHandlerAdapter继承了ChannelHandler,里面有很多各种各样的回调函数,可以根据自己的需求灵活选取。
前面介绍了创建服务端程序的方法,客户端其实和服务端大同小异。
/**
* 连接指定的远端服务,写法与Server端的bind类似
*
* @param host 远端服务ip
* @param port 远端服务端口
*/
public void connect(String host, int port) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ActualHandler());
}
});
// 发起异步连接操作
ChannelFuture future = bootstrap.connect(host, port).sync();
// 等待客户端链路关闭
future.channel().closeFuture().sync();
} catch (Throwable t) {
t.printStackTrace();
group.shutdownGracefully();
}
}
public static void main(String[] args) {
new SimpleTimeClient().connect("localhost", 8088);
}
服务端设置的是childHandler,客户端需要设置handler,原理与服务端一致。
这里我们直接使用了匿名内部类来初始化ChannelInitializer实例。
原理与服务端一致。
private class ActualHandler extends ChannelHandlerAdapter {
/**
* 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
byte[] reqMsg = "你好~".getBytes(StandardCharsets.UTF_8);
ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
ctx.writeAndFlush(buf);
}
/**
* 服务端回复消息时会回调此函数
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String respMsg = new String(bytes, StandardCharsets.UTF_8);
System.out.println("收到服务端的返回消息:" + respMsg);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
在网络编程中,粘包问题是往往是无法回避的问题,通常我们处理粘包有以下几种方式:
以前我们需要手动实现拆包的代码,繁杂而枯燥,而Netty为我们提供了几种现成的拆包处理类,使用非常简便。
LineBasedFrameDecoder使用换行符作为结束符进行拆包
使用方法:
在接收消息的一方的SocketChannel.pipeline中加入LineBasedFrameDecoder,构造器中参数为每行消息最大长度,如果达到最大长度仍未接收到换行符,则会抛出异常。
socketChannel.pipeline().addLast(
new LineBasedFrameDecoder(1024)
, new StringDecoder()
, new LineBasedTimeServerHandler());
在LineBasedFrameDecoder后面我们还加入了StringDecoder,这个解码器很简单,就是将字节码转为字符串,这样我们收到的消息就不是ByteBuf,而直接是String了。
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String respMsg = (String) msg;// 接收到的直接是String
System.out.println("当前的时间:" + respMsg);
ctx.close();
}
在消息发送的地方加入换行符作为结尾
byte[] msg = ("我是消息" + System.getProperty("line.separator"))
.getBytes(StandardCharsets.UTF_8);
完成这两步,基于换行符拆包就搞定了,是不是炒鸡简单,节省了我们大量的拆包工作。
DelimiterBasedFrameDecoder允许我们使用指定字符作为结束符进行拆包,其实换行符就是一种特殊的指定字符,所以DelimiterBasedFrameDecoder的用法与LineBasedFrameDecoder的用法完全一致,只需要发送方将换行符变更为其它指定字符即可。
byte[] msg = ("我是消息" + "$")// 以$作为结束符
.getBytes(StandardCharsets.UTF_8);
FixedLengthFrameDecoder可以让我们使用定长字节数来拆分消息。
消息接收方:
socketChannel.pipeline()
.addLast(
new FixedLengthFrameDecoder(39)// frameLength是一个完整消息的byte数据长度
, new StringDecoder()
, new FixedLengthTimeServerHandler());
消息发送方只要按照约定的字节数长度来发送消息即可。
下面的程序是基于Netty编写的简单的时间服务,有服务端和客户端程序,因为将最基础的基于字节数据的方式和三种解码器的方式都整合在了一起,所以代码比较长,建议将代码获取到IDE中进行阅读,代码中有必要的注释。
此程序存放于Gitee,仓库地址:https://gitee.com/imdongrui/study-repo.git
仓库中的ioserver和ioclient工程
package com.dongrui.study.ioserver.nettyserver;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Date;
/**
* 基于Netty实现的简单时间服务
*/
public class SimpleTimeServer {
private int counter = 0;
private String[] modes = {"simple", "lineBased", "delimiterBased", "fixedLength"};
private String mode = modes[3];
/**
* 初始化ServerBootstrap,并绑定指定的端口
*
* @param port 需要监听的端口
*/
private void bind(int port) {
// 初始化两个线程租
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
// ServerChannel的引导程序,为了使ServerChannel更加易用
ServerBootstrap bootstrap = new ServerBootstrap();
/* bossGroup与workerGroup是两个线程组,bossGroup用于接收客户端的请求,
workerGroup用于处理接收到的SocketChannel的网络读写 */
bootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)// 设置需要启动的Channel实例
/* 设置NioServerSocketChannel的TCP参数,
backlog的解析请看这里 https://www.cnblogs.com/qiumingcheng/p/9492962.html */
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChildChannelHandler());// 设置处理接收到的请求的处理类
ChannelFuture future = bootstrap.bind(port).sync();// 为ServerBootstrap绑定监听端口
future.channel().closeFuture().sync();// 关闭NioServerSocketChannel
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放线程组
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
/**
* ServerBootstrap中的childGroup中接收到的ChildChannel的处理类,
* 用于处理接收到的请求
*/
private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) {
/* 这里的SocketChannel是Netty提供的,pipeline是一个ChildHandler的list,
即我们可以使用多个Handler对接收到的SocketChannel进行处理 */
switch (mode) {
case "simple":
socketChannel.pipeline()
.addLast(new SimpleTimeServerHandler());
break;
case "lineBased":
socketChannel.pipeline()
// 使用LineBasedFrameDecoder可以以换行符为结束标识对消息进行拆分,构造器中参数为每行消息最大长度,如果达到最大长度仍未接收到换行符,则会抛出异常
.addLast(new LineBasedFrameDecoder(1024))
// StringDecoder可以将消息转码为String
.addLast(new StringDecoder())
.addLast(new LineBasedTimeServerHandler());
break;
case "delimiterBased":
socketChannel.pipeline()
// 使用DelimiterBasedFrameDecoder可以以指定标识为结束标识对消息进行拆分
.addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))))
// StringDecoder可以将消息转码为String
.addLast(new StringDecoder())
.addLast(new DelimiterBasedTimeServerHandler());
break;
case "fixedLength":
socketChannel.pipeline()
// frameLength是一个完整消息的byte数据长度
.addLast(new FixedLengthFrameDecoder(39))
.addLast(new StringDecoder())
.addLast(new FixedLengthTimeServerHandler());
break;
}
}
}
/**
* 实际处理时间服务的Handler,不对消息进行行处理,即未解决粘包问题
*/
private class SimpleTimeServerHandler extends ChannelHandlerAdapter {
/**
* 有新建连接创建时,会首先回调此函数
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
System.out.println("有新建连接创建");
}
/**
* 读取接收到的ServerChannel中的数据
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 不使用LineBasedFrameDecoder和StringDecoder处理粘包时的写法,接收到的msg是ByteBuf
ByteBuf reqBuf = (ByteBuf) msg;
byte[] bytes = new byte[reqBuf.readableBytes()];
reqBuf.readBytes(bytes);
String reqMsg = new String(bytes, StandardCharsets.UTF_8);
System.out.println("counter: " + ++counter);
System.out.println("接收到请求消息:" + reqMsg);
ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "").getBytes(StandardCharsets.UTF_8));
ctx.write(respBuf);
}
/**
* read完成后调用的函数,进行一些数据读取完毕后的操作
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("读取完毕");
ctx.flush();
}
/**
* 出现异常时的回调函数
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
/**
* 实际处理时间服务的Handler,对消息进行行处理以解决粘包问题
*/
private class LineBasedTimeServerHandler extends ChannelHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("有新建连接");
}
/**
* 读取接收到的ServerChannel中的数据
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 使用LineBasedFrameDecoder和StringDecoder处理粘包时的写法,接收到的msg是String
String reqMsg = (String) msg;
System.out.println("counter: " + ++counter);
System.out.println("接收到请求消息:" + reqMsg);
// 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8));
ctx.write(respBuf);
}
/**
* read完成后调用的函数,进行一些数据读取完毕后的操作
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("读取完毕");
ctx.flush();
}
/**
* 出现异常时的回调函数
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
private class DelimiterBasedTimeServerHandler extends ChannelHandlerAdapter {
/**
* 读取接收到的ServerChannel中的数据
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String reqMsg = (String) msg;
System.out.println("counter: " + ++counter);
System.out.println("接收到请求消息:" + reqMsg);
ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "$").getBytes(StandardCharsets.UTF_8));
ctx.write(respBuf);
}
/**
* read完成后调用的函数,进行一些数据读取完毕后的操作
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("读取完毕");
ctx.flush();
}
/**
* 出现异常时的回调函数
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
private class FixedLengthTimeServerHandler extends ChannelHandlerAdapter {
/**
* 读取接收到的ServerChannel中的数据
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String reqMsg = (String) msg;
System.out.println("counter: " + ++counter);
System.out.println("接收到请求消息:" + reqMsg);
ByteBuf respBuf = Unpooled.copiedBuffer((new Date().getTime() + "").getBytes(StandardCharsets.UTF_8));
ctx.write(respBuf);
}
/**
* read完成后调用的函数,进行一些数据读取完毕后的操作
*
* @param ctx
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
System.out.println("读取完毕");
ctx.flush();
}
/**
* 出现异常时的回调函数
*
* @param ctx
* @param cause
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
public static void main(String[] args) {
SimpleTimeServer server = new SimpleTimeServer();
server.bind(8088);
}
}
package com.dongrui.study.ioclient.nettyclient;
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.FixedLengthFrameDecoder;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import java.nio.charset.StandardCharsets;
public class SimpleTimeClient {
private String[] modes = {"simple", "lineBased", "delimiterBased", "fixedLength"};
private String mode = modes[3];
/**
* 连接指定的远端服务,写法与Server端的bind类似
*
* @param host 远端服务ip
* @param port 远端服务端口
*/
public void connect(String host, int port) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) {
switch (mode) {
case "simple":
ch.pipeline()
.addLast(new SimpleTimeClientHandler());
break;
case "lineBased":
ch.pipeline()
// 使用LineBasedFrameDecoder可以以换行符为结束标识对消息进行拆分,构造器中参数为每行消息最大长度,如果达到最大长度仍未接收到换行符,则会抛出异常
.addLast(new LineBasedFrameDecoder(1024))
// StringDecoder可以将消息转码为String
.addLast(new StringDecoder())
.addLast(new LineBasedTimeClientHandler());
break;
case "delimiterBased":
ch.pipeline()
// 使用DelimiterBasedFrameDecoder可以以指定标识为结束标识对消息进行拆分
.addLast(new DelimiterBasedFrameDecoder(1024, Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8))))
// StringDecoder可以将消息转码为String
.addLast(new StringDecoder())
.addLast(new DelimiterBasedTimeClientHandler());
break;
case "fixedLength":
ch.pipeline()
.addLast(new FixedLengthFrameDecoder(13))
.addLast(new StringDecoder())
.addLast(new FixedLengthTimeClientHandler());
break;
}
}
});
// 发起异步连接操作
ChannelFuture future = bootstrap.connect(host, port).sync();
// 等待客户端链路关闭
future.channel().closeFuture().sync();
} catch (Throwable t) {
t.printStackTrace();
group.shutdownGracefully();
}
}
private class SimpleTimeClientHandler extends ChannelHandlerAdapter {
/**
* 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 100; i++) {
byte[] reqMsg = ("快告诉我时间,不然干掉你!" + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
ctx.writeAndFlush(buf);
}
}
/**
* 服务端回复消息时会回调此函数
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf buf = (ByteBuf) msg;
byte[] bytes = new byte[buf.readableBytes()];
buf.readBytes(bytes);
String respMsg = new String(bytes, StandardCharsets.UTF_8);
System.out.println("当前的时间:" + respMsg);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
private class LineBasedTimeClientHandler extends ChannelHandlerAdapter {
/**
* 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 100; i++) {
// 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
byte[] reqMsg = ("快告诉我时间,不然干掉你!" + System.getProperty("line.separator")).getBytes(StandardCharsets.UTF_8);
ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
ctx.writeAndFlush(buf);
}
}
/**
* 服务端回复消息时会回调此函数
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String respMsg = (String) msg;
System.out.println("当前的时间:" + respMsg);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
private class DelimiterBasedTimeClientHandler extends ChannelHandlerAdapter {
/**
* 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 100; i++) {
// 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
byte[] reqMsg = ("快告诉我时间,不然干掉你!" + "$").getBytes(StandardCharsets.UTF_8);
ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
ctx.writeAndFlush(buf);
}
}
/**
* 服务端回复消息时会回调此函数
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String respMsg = (String) msg;
System.out.println("当前的时间:" + respMsg);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
private class FixedLengthTimeClientHandler extends ChannelHandlerAdapter {
/**
* 当连接成功时,会回调此函数,我们可以在此函数中向服务端发送消息
*
* @param ctx
*/
@Override
public void channelActive(ChannelHandlerContext ctx) {
for (int i = 0; i < 100; i++) {
// 当服务端使用了LineBasedFrameDecoder,那么每条消息结尾需要加上line.separator,否则服务端消息读取无法完成
byte[] reqMsg = "快告诉我时间,不然干掉你!".getBytes(StandardCharsets.UTF_8);
ByteBuf buf = Unpooled.copiedBuffer(reqMsg);
ctx.writeAndFlush(buf);
}
}
/**
* 服务端回复消息时会回调此函数
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
String respMsg = (String) msg;
System.out.println("当前的时间:" + respMsg);
ctx.close();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
public static void main(String[] args) {
new SimpleTimeClient().connect("localhost", 8088);
}
}