netty心跳机制和断线重连(四)
心跳是为了保证客户端和服务端的通信可用。因为各种原因客户端和服务端不能及时响应和接收信息。比如网络断开,停电 或者是客户端/服务端 高负载。
所以每隔一段时间 客户端发送心跳包到客户端 服务端做出心跳的响应;
1.如果客户端在指定时间没有向服务端发送心跳包。则表示客户端的通信出现了问题。
2.如果客户端发送心跳包到服务端没有收到响应 则表示服务端的通信出现了问题。
netty提供IdleStateHandle 在监听距离上一次写的时间和距离上一次读的时间 如果超时则调用
源码:
public class IdleStateHandler extends ChannelDuplexHandler
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// This method will be invoked only if this handler was added
// before channelActive() event is fired. If a user adds this handler
// after the channelActive() event, initialize() will be called by beforeAdd().
initialize(ctx);
super.channelActive(ctx);
}
}
private void initialize(ChannelHandlerContext ctx) {
// Avoid the case where destroy() is called before scheduling timeouts.
// See: https://github.com/netty/netty/issues/143
switch (state) {
case 1:
case 2:
return;
} state = 1;
initOutputChanged(ctx); lastReadTime = lastWriteTime = ticksInNanos();
if (readerIdleTimeNanos > 0) {
readerIdleTimeout = schedule(ctx, new ReaderIdleTimeoutTask(ctx),//监听read的task
readerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (writerIdleTimeNanos > 0) {
writerIdleTimeout = schedule(ctx, new WriterIdleTimeoutTask(ctx),//监听写的task
writerIdleTimeNanos, TimeUnit.NANOSECONDS);
}
if (allIdleTimeNanos > 0) {
allIdleTimeout = schedule(ctx, new AllIdleTimeoutTask(ctx),//监听读写的task
allIdleTimeNanos, TimeUnit.NANOSECONDS);
}
}
private final class ReaderIdleTimeoutTask extends AbstractIdleTask { ReaderIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
} @Override
protected void run(ChannelHandlerContext ctx) {
long nextDelay = readerIdleTimeNanos;
if (!reading) {
nextDelay -= ticksInNanos() - lastReadTime;
} if (nextDelay <= 0) {
// Reader is idle - set a new timeout and notify the callback.
readerIdleTimeout = schedule(ctx, this, readerIdleTimeNanos, TimeUnit.NANOSECONDS); boolean first = firstReaderIdleEvent;
firstReaderIdleEvent = false; try {
IdleStateEvent event = newIdleStateEvent(IdleState.READER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Read occurred before the timeout - set a new timeout with shorter delay.
readerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
} private final class WriterIdleTimeoutTask extends AbstractIdleTask { WriterIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
} @Override
protected void run(ChannelHandlerContext ctx) { long lastWriteTime = IdleStateHandler.this.lastWriteTime;
long nextDelay = writerIdleTimeNanos - (ticksInNanos() - lastWriteTime);
if (nextDelay <= 0) {
// Writer is idle - set a new timeout and notify the callback.
writerIdleTimeout = schedule(ctx, this, writerIdleTimeNanos, TimeUnit.NANOSECONDS); boolean first = firstWriterIdleEvent;
firstWriterIdleEvent = false; try {
if (hasOutputChanged(ctx, first)) {
return;
} IdleStateEvent event = newIdleStateEvent(IdleState.WRITER_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Write occurred before the timeout - set a new timeout with shorter delay.
writerIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
} private final class AllIdleTimeoutTask extends AbstractIdleTask { AllIdleTimeoutTask(ChannelHandlerContext ctx) {
super(ctx);
} @Override
protected void run(ChannelHandlerContext ctx) { long nextDelay = allIdleTimeNanos;
if (!reading) {
nextDelay -= ticksInNanos() - Math.max(lastReadTime, lastWriteTime);
}
if (nextDelay <= 0) {
// Both reader and writer are idle - set a new timeout and
// notify the callback.
allIdleTimeout = schedule(ctx, this, allIdleTimeNanos, TimeUnit.NANOSECONDS); boolean first = firstAllIdleEvent;
firstAllIdleEvent = false; try {
if (hasOutputChanged(ctx, first)) {
return;
} IdleStateEvent event = newIdleStateEvent(IdleState.ALL_IDLE, first);
channelIdle(ctx, event);
} catch (Throwable t) {
ctx.fireExceptionCaught(t);
}
} else {
// Either read or write occurred before the timeout - set a new
// timeout with shorter delay.
allIdleTimeout = schedule(ctx, this, nextDelay, TimeUnit.NANOSECONDS);
}
}
}
三个内部类是IdleSateHandle的内部类 可以看到内部是通过另起一个线程进行监听上一次对应事件的触发 如果超时则调用对应的事件
基于三的代码进行修改
首先是MessageHead消息头增加消息类型
public class MessageHead {
private int headData=0X76;//协议开始标志
private int length;//包的长度
private String token;
private Date createDate;
private String type;//消息类型 ping表示心跳包
public int getHeadData() {
return headData;
}
public void setHeadData(int headData) {
this.headData = headData;
}
public int getLength() {
return length;
}
public void setLength(int length) {
this.length = length;
} public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public Date getCreateDate() {
return createDate;
}
public void setCreateDate(Date createDate) {
this.createDate = createDate;
} public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
@Override
public String toString() {
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
// TODO Auto-generated method stub
return "headData:"+headData+",length:"+length+",token:"+token+",createDate:"+ simpleDateFormat.format(createDate);
}
}
MessageDecode
package com.liqiang.SimpeEcode; import java.text.SimpleDateFormat;
import java.util.List;
import com.liqiang.nettyTest2.nettyMain; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.handler.codec.MessageToByteEncoder;
import io.netty.handler.codec.MessageToMessageDecoder; public class MessageDecode extends ByteToMessageDecoder{ private final int BASE_LENGTH=4+4+50+50+50;//协议头 类型 int+length 4个字节+消息类型加令牌和 令牌生成时间50个字节
private int headData=0X76;//协议开始标志 @Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
// 刻度长度必须大于基本长度
if(buffer.readableBytes()>=BASE_LENGTH) {
/**
* 粘包 发送频繁 可能多次发送黏在一起 需要考虑 不过一个客户端发送太频繁也可以推断是否是攻击
*/
//防止soket流攻击。客户端传过来的数据太大不合理
if(buffer.readableBytes()>2048) {
//buffer.skipBytes(buffer.readableBytes()); }
}
int beginIndex;//记录包开始位置
while(true) {
// 获取包头开始的index
beginIndex = buffer.readerIndex();
//如果读到开始标记位置 结束读取避免拆包和粘包
if(buffer.readInt()==headData) {
break;
} //初始化读的index为0
buffer.resetReaderIndex();
// 当略过,一个字节之后,
//如果当前buffer数据小于基础数据 返回等待下一次读取
if (buffer.readableBytes() < BASE_LENGTH) {
return;
}
}
// 消息的长度
int length = buffer.readInt();
// 判断请求数据包数据是否到齐 -150是消息头的长度。
if ((buffer.readableBytes()-) < length) {
//没有到齐 返回读的指针 等待下一次数据到期再读
buffer.readerIndex(beginIndex);
return;
}
//读取消息类型
byte[] typeByte=new byte[50];
buffer.readBytes(typeByte);
//读取令牌
byte[] tokenByte=new byte[50];
buffer.readBytes(tokenByte); //读取令牌生成时间
byte[]createDateByte=new byte[50];
buffer.readBytes(createDateByte);
//读取content
byte[] data = new byte[length];
buffer.readBytes(data);
MessageHead head=new MessageHead();
head.setHeadData(headData);
head.setToken(new String(tokenByte).trim());
SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
head.setCreateDate( simpleDateFormat.parse(new String(createDateByte).trim()));
head.setLength(length);
head.setType(new String(typeByte).trim());
Message message=new Message(head, data);
//认证不通过
if(!message.authorization(message.buidToken())) {
ctx.close(); return;
}
out.add(message);
buffer.discardReadBytes();//回收已读字节
} }
MessageEncoder
package com.liqiang.SimpeEcode; import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder; public class MessageEncoder extends MessageToByteEncoder<Message> { @Override
protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
// TODO Auto-generated method stub
// 写入开头的标志
out.writeInt(msg.getHead().getHeadData());
// 写入包的的长度
out.writeInt(msg.getContent().length);
byte[] typeByte = new byte[50];
/**
* type定长50个字节
* 第一个参数 原数组
* 第二个参数 原数组位置
* 第三个参数 目标数组
* 第四个参数 目标数组位置
* 第五个参数 copy多少个长度
*/
byte[] indexByte=msg.getHead().getType().getBytes();
try {
System.arraycopy(indexByte, 0, typeByte, 0,indexByte.length>typeByte.length?typeByte.length:indexByte.length);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
//写入消息类型
out.writeBytes(typeByte);
byte[] tokenByte = new byte[50];
/**
* token定长50个字节
* 第一个参数 原数组
* 第二个参数 原数组位置
* 第三个参数 目标数组
* 第四个参数 目标数组位置
* 第五个参数 copy多少个长度
*/
indexByte=msg.getHead().getToken().getBytes();
try {
System.arraycopy(indexByte, 0, tokenByte, 0,indexByte.length>tokenByte.length?tokenByte.length:indexByte.length);
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
} //写入令牌
out.writeBytes(tokenByte);
byte[] createTimeByte = new byte[50];
SimpleDateFormat format0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = format0.format(msg.getHead().getCreateDate());
indexByte=time.getBytes();
System.arraycopy(indexByte, 0, createTimeByte, 0,indexByte.length>createTimeByte.length?createTimeByte.length:indexByte.length);
//写入令牌生成时间
out.writeBytes(createTimeByte); // 写入消息主体
out.writeBytes(msg.getContent()); } }
红色部分为改动部分
ClientChannelInitializer
package com.liqiang.nettyTest2; import java.util.concurrent.TimeUnit; import com.liqiang.SimpeEcode.MessageDecode;
import com.liqiang.SimpeEcode.MessageEncoder; import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler; public class ClientChannelInitializer extends ChannelInitializer<SocketChannel> { private Client client;
public ClientChannelInitializer(Client client) {
// TODO Auto-generated constructor stub
this.client=client;
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// TODO Auto-generated method stub
socketChannel.pipeline()
//表示5秒向服务器发送一次心跳包 10秒没接收到服务器端信息表示服务器端通信异常 则会触发clientHandle userEventTriggered事件
.addLast("ping",new IdleStateHandler(10, 5, 0, TimeUnit.SECONDS))
.addLast("decoder",new MessageEncoder())
.addLast("encoder",new MessageDecode())
.addLast(new ClientHandle(client));//注册处理器 }
}
ClientHandle修改
package com.liqiang.nettyTest2; import java.util.Date; import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead; import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.cors.CorsHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent; public class ClientHandle extends ChannelInboundHandlerAdapter { Client client;
public ClientHandle(Client client) {
// TODO Auto-generated constructor stub
this.client=client;
}
/**
* 读写超时事事件
* IdleStateHandle配置的 如果5秒没有触发writer事件 则会触发 userEventTrigerd方法 我们则写一次心跳
* 如果10秒没有触发read事件则表示服务器通信异常 因为我们每次发送一次心跳包 服务器都会做出对应的心跳反应
* @throws Exception
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleStateEvent idleStateEvent=((IdleStateEvent) evt);
/**
* 如果没有收到服务端的写 则表示服务器超时 判断是否断开连接
*/
if(idleStateEvent.state()==IdleState.READER_IDLE) {
System.out.println("服务器无响应");
if(!ctx.channel().isOpen()) {
System.out.println("正在重连");
client.connection();
System.out.println("重连成功");
}
}else if(idleStateEvent.state()==IdleState.WRITER_IDLE) {
//如果没有触发写事件则向服务器发送一次心跳包
System.out.println("正在向服务端发送心跳包");
MessageHead head=new MessageHead();
byte[]content="".getBytes();
head.setCreateDate(new Date());
head.setType("ping");
head.setLength(content.length);
Message pingMessage=new Message(head,content);
head.setToken(pingMessage.buidToken());
ctx.writeAndFlush(pingMessage);
}
}else {
super.userEventTriggered(ctx, evt);
}
}
//建立连接时回调
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
//System.out.println("与服务器建立连接成功");
client.setServerChannel(ctx);
client.setConnection(true);
//ctx.fireChannelActive();//如果注册多个handle 下一个handel的事件需要触发需要调用这个方法 }
//读取服务器发送信息时回调
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Message message=(Message) msg;
if(message.getHead().getType().equals("ping")) {
//表示是心跳包 不做任何业务处理
}else {
// TODO Auto-generated method stub
System.out.println(msg.toString());
} } //发生异常时回调
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// TODO Auto-generated method stub
System.out.println("发生异常 与服务器断开连接");
ctx.close();//关闭连接
}
}
ServerChannelInitializer
package com.liqiang.nettyTest2; import java.util.concurrent.TimeUnit; import com.liqiang.SimpeEcode.MessageDecode;
import com.liqiang.SimpeEcode.MessageEncoder; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.LineBasedFrameDecoder;
import io.netty.handler.codec.AsciiHeadersEncoder.NewlineType;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler; public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> { private Server server;
public ServerChannelInitializer(Server server) {
this.server=server;
}
@Override
protected void initChannel(SocketChannel channel) throws Exception {
// TODO Auto-generated method stub
channel.pipeline()
//7秒没收到客户端信息 则表示客户端因为网络等原因异常关闭
.addLast("ping",new IdleStateHandler(7, 0, 0,TimeUnit.SECONDS))
.addLast("decoder",new MessageDecode())
.addLast("encoder",new MessageEncoder())
.addLast(new ServerHandle(server));
} }
ServerHandle
package com.liqiang.nettyTest2; import java.util.Date; import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler; public class ServerHandle extends ChannelInboundHandlerAdapter { private Server server; public ServerHandle(Server server) {
// TODO Auto-generated constructor stub
this.server = server;
}
/**
* 读写超时事事件
*/
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if(evt instanceof IdleStateEvent) {
IdleStateEvent event=(IdleStateEvent)evt;
//如果读超时
if(event.state()==IdleState.READER_IDLE) {
System.out.println("有客户端超时了");
ctx.channel().close();//关闭连接
}
}else {
super.userEventTriggered(ctx, evt);
} } // 建立连接时回调
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
System.out.println("有客户端建立连接了");
server.addClient(ctx);
// ctx.fireChannelActive();//pipeline可以注册多个handle 这里可以理解为是否通知下一个Handle继续处理
} // 接收到客户端发送消息时回调
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Message message=(Message)msg;
if(message.getHead().getType().equals("ping")) {
//表示心跳包 服务端响应心跳包 而不做相关业务处理
MessageHead head=new MessageHead();
byte[] content="".getBytes();
head.setCreateDate(new Date());
head.setType("ping");
head.setLength(content.length);
Message pingMessage=new Message(head,content);
head.setToken(pingMessage.buidToken());
ctx.writeAndFlush(pingMessage);
}else {
System.out.println("server接收到客户端发送信息:" + msg.toString());
}
// TODO Auto-generated method stub // ctx.fireChannelRead(msg);pipeline可以注册多个handle 这里可以理解为是否通知下一个Handle继续处理
} // 通信过程中发生异常回调
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// TODO Auto-generated method stub
// super.exceptionCaught(ctx, cause);
ctx.close();// 发生异常关闭通信通道
System.out.println("发生异常与客户端失去连接"); cause.printStackTrace();
// ctx.fireExceptionCaught(cause);pipeline可以注册多个handle 这里可以理解为是否通知下一个Handle继续处理
}
}
client
package com.liqiang.nettyTest2; import com.liqiang.SimpeEcode.Message; import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.EventExecutorGroup; public class Client implements Runnable{
private String ip;// ip
private int port;// 端口
private boolean isConnection = false;
private ChannelHandlerContext serverChannel; public Client(String ip, int port) {
this.ip = ip;
this.port = port;
} // 与服务器建立连接
public void connection() {
new Thread(this).start(); }
@Override
public void run() {
// TODO Auto-generated method stub
EventLoopGroup group = new NioEventLoopGroup();// 服务器监听服务器发送信息
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group).channel(NioSocketChannel.class).option(ChannelOption.TCP_NODELAY, true)
.handler(new ClientChannelInitializer(this));// 基于NIO编程模型通信
try {
ChannelFuture channelFuture = bootstrap.connect(ip, port).sync(); channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
System.out.println("连接服务器失败");
}finally {
//尝试重连
System.out.println("正在重连");
run();
}
} public void close() {
serverChannel.close();
}
public boolean isConnection() {
return isConnection;
} public void setConnection(boolean isConnection) {
this.isConnection = isConnection;
} public void sendMsg(Message msg) {
while(isConnection) {
serverChannel.writeAndFlush(msg);
} } public ChannelHandlerContext getServerChannel() {
return serverChannel;
} public void setServerChannel(ChannelHandlerContext serverChannel) {
this.serverChannel = serverChannel;
} }
Server
package com.liqiang.nettyTest2; import java.net.InetSocketAddress;
import java.util.List;
import java.util.Vector; import com.liqiang.SimpeEcode.Message; import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder; public class Server implements Runnable {
private int port;// 监听端口
private Vector<ChannelHandlerContext> clients;// 保存在线客户端信息 public Server(int port) {
clients = new Vector<ChannelHandlerContext>();
this.port = port;
} // 广播
public void sendAll(Message msg) {
clients.forEach(c -> {
c.writeAndFlush(msg);
});
} public void addClient(ChannelHandlerContext client) {
clients.add(client);
} @Override
public void run() {
/**
* NioEventLoopGroup 内部维护一个线程池 如果构造函数没有指定线程池数量 则默认为系统core*2
*/
EventLoopGroup acceptor = new NioEventLoopGroup();// acceptor负责监客户端连接请求
EventLoopGroup worker = new NioEventLoopGroup();// worker负责io读写(监听注册channel的 read/writer事件) ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(acceptor, worker).channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port)).childHandler(new ServerChannelInitializer(this))
.option(ChannelOption.SO_BACKLOG, 128).childOption(ChannelOption.SO_KEEPALIVE, true);
try {
ChannelFuture channelFuture = bootstrap.bind(port).sync(); System.out.println("服务器已启动");
// 将阻塞 直到服务器端关闭或者手动调用
channelFuture.channel().closeFuture().sync();
// 释放资源 } catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
acceptor.shutdownGracefully();
worker.shutdownGracefully();
} } public void startServer() {
new Thread(this).start();
} }
测试
package com.liqiang.nettyTest2; import java.util.Date; import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead; public class nettyClientMain {
public static void main(String[] args) {
new Thread(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
Client client1 = new Client("127.0.0.1", 8081);
client1.connection();
String content = "哈哈哈哈!";
byte[] bts = content.getBytes();
MessageHead head = new MessageHead();
// 令牌生成时间
head.setCreateDate(new Date());
head.setType("message");
head.setLength(bts.length);
Message message = new Message(head, bts);
message.getHead().setToken(message.buidToken());
client1.sendMsg(message); }
}).start(); }
}
package com.liqiang.nettyTest2; import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date; import javax.management.StringValueExp;
import javax.swing.text.StringContent; import com.liqiang.SimpeEcode.Message;
import com.liqiang.SimpeEcode.MessageHead; public class nettyMain {
public static void main(String[] args) {
new Thread(new Runnable() { @Override
public void run() {
// TODO Auto-generated method stub
Server server = new Server(8081);
server.startServer(); }
}).start(); } }
1.先开启服务端
2.再开启客户端
3.关闭服务端
然后我们再重新启动服务端 打印
正在重连
正在重连
正在重连
正在重连
正在重连
正在重连
正在重连
正在重连
正在向服务端发送心跳包
正在向服务端发送心跳包
正在向服务端发送心跳包
正在向服务端发送心跳包
netty心跳机制和断线重连(四)的更多相关文章
- Netty — 心跳检测和断线重连
一.前言 由于在通信层的网络连接的不可靠性,比如:网络闪断,网络抖动等,经常会出现连接断开.这样对于使用长连接的应用而言,当突然高流量冲击势必会造成进行网络连接,从而产生网络堵塞,应用响应速度下降,延 ...
- Netty学习篇④-心跳机制及断线重连
心跳检测 前言 客户端和服务端的连接属于socket连接,也属于长连接,往往会存在客户端在连接了服务端之后就没有任何操作了,但还是占用了一个连接:当越来越多类似的客户端出现就会浪费很多连接,netty ...
- Netty 如何实现心跳机制与断线重连?
作者:sprinkle_liz www.jianshu.com/p/1a28e48edd92 心跳机制 何为心跳 所谓心跳, 即在 TCP 长连接中, 客户端和服务器之间定期发送的一种特殊的数据包, ...
- NETTY 心跳机制
最近工作比较忙,但闲暇之余还是看了阿里的冯家春(fengjiachun)的github上的开源代码Jupiter,写的RPC框架让我感叹人外有人,废话不多说,下面的代码全部截取自Jupiter,写了一 ...
- Netty心跳机制
一.概念介绍网络中的接收和发送数据都是使用操作系统中的SOCKET进行实现.但是如果此套接字已经断开,那发送数据和接收数据的时候就一定会有问题.可是如何判断这个套接字是否还可以使用呢?这个就需要在系统 ...
- netty心跳机制测试
netty中有比较完善的心跳机制,(在基础server版本基础上[netty基础--基本收发])添加少量代码即可实现对心跳的监测和处理. 1 server端channel中加入心跳处理机制 // Id ...
- 连接管理 与 Netty 心跳机制
一.前言 踏踏实实,动手去做,talk is cheap, show me the code.先介绍下基础知识,然后做个心跳机制的Demo. 二.连接 长连接:在整个通讯过程,客户端和服务端只用一个S ...
- webSocket使用心跳包实现断线重连
首先new一个webscoket的连接 let noticeSocketLink = new WebSocket(‘webSocket的地址’) 这里是连接成功之后的操作 linkNoticeWebs ...
- netty心跳机制解决
直接看别个的源码:https://blog.csdn.net/xt8469/article/details/84827443>>https://blog.csdn.net/xt8469/a ...
随机推荐
- 用sp_executesql执行动态SQL语句及获得返回值
过去我执行拼凑出来的动态SQL语句,都直接使用EXEC @sql 的方式.有好几次,都看到有资料说,应该尽量使用 sp_executesql. 究其原因,是因为仅仅参数不同的情况下,sp_execut ...
- HDU3183 RMQ/贪心
A Magic Lamp Problem Description Kiki likes traveling. One day she finds a magic lamp, unfortunately ...
- luogu1265 公路修建
题目描述 某国有n个城市,它们互相之间没有公路相通,因此交通十分不便.为解决这一“行路难”的问题,政府决定修建公路.修建公路的任务由各城市共同完成. 修建工程分若干轮完成.在每一轮中,每个城市选择一个 ...
- C++对象内存布局 (二)
在上一篇文章中讨论了C++单一一般继承的对象内存布局http://www.cnblogs.com/uangyy/p/4621561.html 接下来继续讨论第二种情况: 2.单一的虚拟继承:有成员变量 ...
- Android 6.0 中TimePicker显示为滚动样式的方法
在Android6.0中,TimePicker控件的默认样式为转盘的样式,就像这个样子: 如果想要显示为之前的滚动样式的话也很简单,只要在布局文件中设置TimePicker的timePickerMod ...
- bzoj 2152 聪聪可可(点分治模板)
2152: 聪聪可可 Time Limit: 3 Sec Memory Limit: 259 MBSubmit: 3194 Solved: 1647[Submit][Status][Discuss ...
- BZOJ 4563 错排+高精度
思路: 把障碍移到对角线 就发现 这是个错位排列问题 用错排公式即可解 s[i]=(s[i-1]+s[i-2])*i //By SiriusRen #include <cstdio> #i ...
- bootstrap的栅格系统和响应式工具
关于bootstrap的响应式布局,昨天看了杨老师的视频教学https://www.bilibili.com/video/av18357039豁然开朗,在这里记录一下 一:meta标签的引用 < ...
- 设置Hadoop的 dataNode的单个Map的内存配置
1.进入hadoop的配置目录 ,找到 环境变量的 $HADOOP_HOME cd $HADOOP_HOME 2.修改dataNode 节点的 单个map的能使用的内存配置 找到配置的文件: /opt ...
- 连接Oracle数据库帮助类
连接Oracle数据库帮助类,就是把连接Oracle数据库的方法封装起来,只需要在其它页面调用就可,不需要重复写. import java.sql.Connection; import java.sq ...