客户端数量多,且需要传递的数据量级较大。可以周期性的发送数据的时候,使用该机制。要求对数据的即时性不高的时候,才可使用。
优点是可以使用数据缓存。不是每条数据进行一次数据交互。可以定时回收资源,对资源利用率高。
对服务端来说,主要是读数据,使用ReadTimeoutHandler类来控制,ReadTimeoutHandler定义一个定时断线处理器,当多长时间内,没有任何的可读取数据,自动断开连接。对客户端来说,则是写,使用的是WriteTimeoutHandler类。
代码上,在客户端多了一个ChannelFuture的判断,通过channelFuture.channel().isActive()方法来判断是否已经断线了,如果断线,则再次进行服务端的连接。
服务端代码:
public class MyServer {
// 监听线程组,监听客户端请求
private EventLoopGroup acceptorGroup = null;
// 处理客户端相关操作线程组,负责处理与客户端的数据通讯
private EventLoopGroup clientGroup = null;
// 服务启动相关配置信息
private ServerBootstrap bootstrap = null;
//端口
private int port;
public MyServer(int port) {
this.port = port;
init();
}
/**
* 初始化
*/
private void init() {
acceptorGroup = new NioEventLoopGroup();
clientGroup = new NioEventLoopGroup();
bootstrap = new ServerBootstrap();
// 绑定线程组
bootstrap.group(acceptorGroup, clientGroup);
// 设定通讯模式为NIO
bootstrap.channel(NioServerSocketChannel.class);
// 设定缓冲区大小
bootstrap.option(ChannelOption.SO_BACKLOG, 1024);
// SO_SNDBUF发送缓冲区,SO_RCVBUF接收缓冲区,SO_KEEPALIVE开启心跳监测(保证连接有效)
bootstrap.option(ChannelOption.SO_SNDBUF, 16*1024)
.option(ChannelOption.SO_RCVBUF, 16*1024)
.option(ChannelOption.SO_KEEPALIVE, true);
}
/**
* 执行监听
* @return
* @throws InterruptedException
*/
public ChannelFuture execAccept() throws InterruptedException {
bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 定义一个定时断线处理器,当多长时间内,没有任何的可读取数据,自动断开连接。
// 构造参数,就是间隔时长。 默认的单位是秒。
// 自定义间隔时长单位。 new ReadTimeoutHandler(long times, TimeUnit unit);
sc.pipeline().addLast(new ReadTimeoutHandler(3));
sc.pipeline().addLast(new MyServerHandler());
}
});
ChannelFuture future = bootstrap.bind(port).sync();
return future;
}
/**
* 关闭资源
*/
public void release(){
this.acceptorGroup.shutdownGracefully();
this.clientGroup.shutdownGracefully();
}
public static void main(String[] args) {
ChannelFuture future = null;
MyServer server = null;
try {
server = new MyServer(8888);
future = server.execAccept();
System.out.println("server start...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if(future!=null) {
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(server!=null) {
server.release();
}
}
}
}
public class MyServerHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf - netty提供的类,对NIO中的ByteBuffer进行了包装,使用时不再需要去flip
ByteBuf buf = (ByteBuf)msg;
// 开辟有效长度的字节数组大小
byte[] bytes = new byte[buf.readableBytes()];
// 将缓存中的数据读取到字节数组中。
buf.readBytes(bytes);
String message = new String(bytes,"UTF-8");
System.out.println("[client message]: " + message);
String backMsg = "The server receives the message";
ctx.writeAndFlush(Unpooled.copiedBuffer(backMsg.getBytes("UTF-8")));
}
/**
* 客户端断线,会抛出异常
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("Client has been disconnected...");
ctx.close();
}
}
客户端代码:
public class MyClient {
// 处理请求和处理服务端响应的线程组
private EventLoopGroup group = null;
// 服务启动相关配置信息
private Bootstrap bootstrap = null;
// 主机
private String host;
// 端口
private int port;
private ChannelFuture future = null;
public MyClient(String host,int port) {
this.host = host;
this.port = port;
init();
}
/**
* 初始化
*/
private void init() {
group = new NioEventLoopGroup();
bootstrap = new Bootstrap();
// 绑定线程组
bootstrap.group(group);
// 设定通讯模式为NIO
bootstrap.channel(NioSocketChannel.class);
}
/**
* 设置handler
*/
public void setHandler() {
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 写操作自定断线。 在指定时间内,没有写操作,自动断线。
sc.pipeline().addLast(new WriteTimeoutHandler(3));
sc.pipeline().addLast(new MyClientHandler());
}
});
}
/**
* 获取future、断线重连
* @return
* @throws InterruptedException
*/
public ChannelFuture getFuture() throws InterruptedException {
if(future==null) {
future = bootstrap.connect(host, port).sync();
}
else if(!future.channel().isActive()) {
future = bootstrap.connect(host, port).sync();
}
return future;
}
/**
* 关闭资源
*/
public void release(){
this.group.shutdownGracefully();
}
public static void main(String[] args) {
ChannelFuture future = null;
MyClient client = null;
try {
client = new MyClient("127.0.0.1",8888);
client.setHandler();
future = client.getFuture();
//每2秒发送一次消息,共3次
for(int i=0;i<3;i++) {
future.channel().writeAndFlush(Unpooled.copiedBuffer("Client sends message".getBytes("UTF-8")));
Thread.sleep(2000);
}
System.out.println("--> After 10 seconds, the client sends the message again");
//10秒后,再次发送消息
Thread.sleep(10000);
future = client.getFuture();
future.channel().writeAndFlush(Unpooled.copiedBuffer("Client sends new message...".getBytes("UTF-8")));
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
if(future!=null) {
try {
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
if(client!=null) {
client.release();
}
}
}
}
public class MyClientHandler extends ChannelHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// ByteBuf - netty提供的类,对NIO中的ByteBuffer进行了包装,使用时不再需要去flip
ByteBuf buf = (ByteBuf)msg;
// 开辟有效长度的字节数组大小
byte[] bytes = new byte[buf.readableBytes()];
// 将缓存中的数据读取到字节数组中。
buf.readBytes(bytes);
String message = new String(bytes,"UTF-8");
System.out.println("[server message]: " + message);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("client exceptionCaught method run...");
ctx.close();
}
}