1.为什么要实现
我们在使用netty的时候,可能tcp连接已近断开了,但是应用层没有检测到,或者一个连接很久没有进行操作了,但是却把服务器的资源占用了,所以以上这些情况,我们就需要进行超时检测。
2.服务端实现
服务端实现主要就是靠一个读写状态事件检测处理器,当服务端检测到一个连接,多少秒或者没有读写操作了就可以去断开这个连接,好让其他更多的连接建立。(一般检测写事件就行了)
@Slf4j
public class ChatServer {
public static void main(String[] args) {
NioEventLoopGroup boss = new NioEventLoopGroup();
NioEventLoopGroup worker = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
LoginRequestMessageHandler loginRequestMessageHandler = new LoginRequestMessageHandler();
ChatRequestMessageHandler chatRequestMessageHandler = new ChatRequestMessageHandler();
//群聊相关消息的处理
GroupCreateRequestMessageHandler groupCreateRequestMessageHandler = new GroupCreateRequestMessageHandler();
GroupChatRequestMessageHandler groupChatRequestMessageHandler = new GroupChatRequestMessageHandler();
GroupJoinRequestMessageHandler groupJoinRequestMessageHandler = new GroupJoinRequestMessageHandler();
GroupMembersRequestMessageHandler groupMembersRequestMessageHandler = new GroupMembersRequestMessageHandler();
GroupQuitRequestMessageHandler groupQuitRequestMessageHandler = new GroupQuitRequestMessageHandler();
QuitChandler quitChandler = new QuitChandler();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.channel(NioServerSocketChannel.class);
serverBootstrap.group(boss, worker);
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//检测读空闲事件
ch.pipeline().addLast(new IdleStateHandler(5,0,0));
ch.pipeline().addLast(new ChannelDuplexHandler(){
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
if (idleStateEvent.state()==IdleState.READER_IDLE){
log.debug("读事件超时了");
ctx.channel().close();
}
}
});
ch.pipeline().addLast(new ProcotolFrameDecoder());
ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
ch.pipeline().addLast(loginRequestMessageHandler);
ch.pipeline().addLast(chatRequestMessageHandler);
ch.pipeline().addLast(groupCreateRequestMessageHandler);
ch.pipeline().addLast(groupChatRequestMessageHandler);
ch.pipeline().addLast(quitChandler);
}
});
Channel channel = serverBootstrap.bind(8080).sync().channel();
channel.closeFuture().sync();
} catch (InterruptedException e) {
log.error("server error", e);
} finally {
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
}
3.客户端处理
但是有时我们有时候又确实在一端时间里面可能没有操作,比如上厕所,或则有电话。
那么在客户端同样也可以加入一个读写事件状态检测处理器,当检测我门客户端一段时间(这个时间必须小于服务端检测的时间)没有写入的时候就可以主动往服务器去发送一个心跳消息,向服务端表明我还在。
@Slf4j
public class ChatClient {
public static void main(String[] args) {
NioEventLoopGroup group = new NioEventLoopGroup();
LoggingHandler LOGGING_HANDLER = new LoggingHandler(LogLevel.DEBUG);
MessageCodecSharable MESSAGE_CODEC = new MessageCodecSharable();
CountDownLatch LOGIN_WAIT=new CountDownLatch(1);
try {
final boolean[] flag = {false};
Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
bootstrap.group(group);
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new ProcotolFrameDecoder());
// ch.pipeline().addLast(LOGGING_HANDLER);
ch.pipeline().addLast(MESSAGE_CODEC);
//检测写空闲事件,检测时间要比服务器的时间短
ch.pipeline().addLast(new IdleStateHandler(0,3,0));
ch.pipeline().addLast(new ChannelDuplexHandler(){
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
if (idleStateEvent.state()== IdleState.WRITER_IDLE){
ctx.writeAndFlush(new PingMessage());
}
}
});
ch.pipeline().addLast("clientHandler",new ChannelInboundHandlerAdapter(){
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ExecutorService executorService = Executors.newSingleThreadExecutor();
try {
executorService.execute(()->{
Scanner scanner = new Scanner(System.in);
System.out.println("请输入用户名");
String username = scanner.nextLine();
System.out.println("请输入密码");
String password = scanner.nextLine();
LoginRequestMessage message = new LoginRequestMessage(username, password);
ctx.writeAndFlush(message);
System.out.println("等待");
try {
LOGIN_WAIT.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//登录失败
if (!flag[0]){
System.out.println("登录失败");
ctx.channel().close();
return;
}
//登录成功
System.out.println("登录成功");
while (true){
System.out.println("========");
System.out.println("send [to] [content]");
String s = scanner.nextLine();
String[] s1 = s.split(" ");
switch (s1[0]){
case "send":
ctx.writeAndFlush(new ChatRequestMessage(username, s1[1], s1[2]));
break;
case "gsend":
ctx.writeAndFlush(new GroupChatRequestMessage(username, s1[1], s1[2]));
break;
case "gcreate":
Set<String> set = new HashSet<>(Arrays.asList(s1[2].split(",")));
set.add(username); // 加入自己
ctx.writeAndFlush(new GroupCreateRequestMessage(s1[1], set));
break;
case "gmembers":
ctx.writeAndFlush(new GroupMembersRequestMessage(s1[1]));
break;
case "gjoin":
ctx.writeAndFlush(new GroupJoinRequestMessage(username, s1[1]));
break;
case "gquit":
ctx.writeAndFlush(new GroupQuitRequestMessage(username, s1[1]));
break;
case "quit":
ctx.channel().close();
return;
default:
System.out.println("我还没有这个功能");
break;
}
}
});
} catch (Exception e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof LoginResponseMessage){
LoginResponseMessage message= (LoginResponseMessage) msg;
if (message.isSuccess()){
flag[0] =true;
}
LOGIN_WAIT.countDown();
}
log.debug("{}",msg);
}
});
}
});
Channel channel = bootstrap.connect("localhost", 8080).sync().channel();
channel.closeFuture().sync();
} catch (Exception e) {
log.error("client error", e);
} finally {
group.shutdownGracefully();
}
}
}