netty channelinactive触发条件_使用Netty实现 IM 聊天贼简单,看不懂就锤爆哪吒的狗头~(结局)...

5. 断开重连

Netty 客户端需要实现断开重连机制,解决各种情况下的断开情况。例如说:

  • Netty 客户端启动时,Netty 服务端处于挂掉,导致无法连接上。
  • 在运行过程中,Netty 服务端挂掉,导致连接被断开。
  • 任一一端网络抖动,导致连接异常断开。

具体的代码实现比较简单,只需要在两个地方增加重连机制。

  • Netty 客户端启动时,无法连接 Netty 服务端时,发起重连。
  • Netty 客户端运行时,和 Netty 断开连接时,发起重连。

考虑到重连会存在失败的情况,我们采用定时重连的方式,避免占用过多资源。

5.1 具体代码

① 在 NettyClient 中,提供 #reconnect() 方法,实现定时重连的逻辑。代码如下:

// NettyClient.javapublic void reconnect() {    eventGroup.schedule(new Runnable() {        @Override        public void run() {            logger.info("[reconnect][开始重连]");            try {                start();            } catch (InterruptedException e) {                logger.error("[reconnect][重连失败]", e);            }        }    }, RECONNECT_SECONDS, TimeUnit.SECONDS);    logger.info("[reconnect][{} 秒后将发起重连]", RECONNECT_SECONDS);}

通过调用 EventLoop 提供的 #schedule(Runnable command, long delay, TimeUnit unit) 方法,实现定时逻辑。而在内部的具体逻辑,调用 NettyClient 的 #start() 方法,发起连接 Netty 服务端。

又因为 NettyClient 在 #start() 方法在连接 Netty 服务端失败时,又会调用 #reconnect()方法,从而再次发起定时重连。如此循环反复,知道 Netty 客户端连接上 Netty 服务端。如下图所示:

522c96b87c711a7cebe5530c989bad8f.png

NettyClient 重连

② 在 NettyClientHandler 中,实现 #channelInactive(ChannelHandlerContext ctx) 方法,在发现和 Netty 服务端断开时,调用 Netty Client 的 #reconnect() 方法,发起重连。代码如下:

// NettyClientHandler.java@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {    // 发起重连    nettyClient.reconnect();    // 继续触发事件    super.channelInactive(ctx);}

5.2 简单测试

① 启动 Netty Client,不要启动 Netty Server,控制台打印日志如下图:

bdccf157275bdab78b9aa570ec55e4ef.png

重连失败

可以看到 Netty Client 在连接失败时,不断发起定时重连。

② 启动 Netty Server,控制台打印如下图:

0ae107ec2fe9182921a97e4c1e8fe89e.png

重连成功

可以看到 Netty Client 成功重连上 Netty Server。

6. 心跳机制与空闲检测

在上文中,艿艿推荐胖友阅读《TCP Keepalive 机制刨根问底》文章,我们可以了解到 TCP 自带的空闲检测机制,默认是 2 小时。这样的检测机制,从系统资源层面上来说是可以接受的。

但是在业务层面,如果 2 小时才发现客户端与服务端的连接实际已经断开,会导致中间非常多的消息丢失,影响客户的使用体验。

因此,我们需要在业务层面,自己实现空闲检测,保证尽快发现客户端与服务端实际已经断开的情况。实现逻辑如下:

  • 服务端发现 180 秒未从客户端读取到消息,主动断开连接。
  • 客户端发现 180 秒未从服务端读取到消息,主动断开连接。

考虑到客户端和服务端之间并不是一直有消息的交互,所以我们需要增加心跳机制

  • 客户端每 60 秒向服务端发起一次心跳消息,保证服务端可以读取到消息。
  • 服务端在收到心跳消息时,回复客户端一条确认消息,保证客户端可以读取到消息。

友情提示:

为什么是 180 秒?可以加大或者减小,看自己希望多快检测到连接异常。过短的时间,会导致心跳过于频繁,占用过多资源。

为什么是 60 秒?三次机会,确认是否心跳超时。

虽然听起来有点复杂,但是实现起来并不复杂哈。

6.1 服务端的空闲检测

在 NettyServerHandlerInitializer 中,我们添加了一个 ReadTimeoutHandler 处理器,它在超过指定时间未从对端读取到数据,会抛出 ReadTimeoutException 异常。如下图所示:

530d7f7c1bb04cb8bbe8b2da066fdd93.png

ReadTimeoutHandler

通过这样的方式,实现服务端发现 180 秒未从客户端读取到消息,主动断开连接。

6.2 客户端的空闲检测

友情提示:和「6.1 服务端的空闲检测」一致。

在 NettyClientHandlerInitializer 中,我们添加了一个 ReadTimeoutHandler 处理器,它在超过指定时间未从对端读取到数据,会抛出 ReadTimeoutException 异常。如下图所示:

505164ef85d7539dec9c64a69398c0a5.png

ReadTimeoutHandler

通过这样的方式,实现客户端发现 180 秒未从服务端读取到消息,主动断开连接。

6.3 心跳机制

Netty 提供了 IdleStateHandler 处理器,提供空闲检测的功能,在 Channel 的读或者写空闲时间太长时,将会触发一个 IdleStateEvent 事件。

这样,我们只需要在 NettyClientHandler 处理器中,在接收到 IdleStateEvent 事件时,客户端向客户端发送一次心跳消息。如下图所示:

0e6c232a8920a042a3eb2af82c53f2cf.png

客户端心跳

  • 其中,HeartbeatRequest 是心跳请求。

同时,我们在服务端项目中,创建了一个 HeartbeatRequestHandler 消息处理器,在收到客户端的心跳请求时,回复客户端一条确认消息。代码如下:

@Componentpublic class HeartbeatRequestHandler implements MessageHandler {    private Logger logger = LoggerFactory.getLogger(getClass());    @Override    public void execute(Channel channel, HeartbeatRequest message) {        logger.info("[execute][收到连接({}) 的心跳请求]", channel.id());        // 响应心跳        HeartbeatResponse response = new HeartbeatResponse();        channel.writeAndFlush(new Invocation(HeartbeatResponse.TYPE, response));    }    @Override    public String getType() {        return HeartbeatRequest.TYPE;    }}
  • 其中,HeartbeatResponse 是心跳确认响应

6.4 简单测试

启动 Netty Server 服务端,再启动 Netty Client 客户端,耐心等待 60 秒后,可以看到心跳日志如下:

// ... 客户端2020-06-22 08:24:47.275  INFO 57005 --- [ntLoopGroup-2-1] c.i.s.l.n.c.handler.NettyClientHandler   : [userEventTriggered][发起一次心跳]2020-06-22 08:24:47.335  INFO 57005 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationEncoder        : [encode][连接(44223e18) 编码了一条消息(Invocation{type='HEARTBEAT_REQUEST', message='{}'})]2020-06-22 08:24:47.408  INFO 57005 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(44223e18) 解析到一条消息(Invocation{type='HEARTBEAT_RESPONSE', message='{}'})]2020-06-22 08:24:47.409  INFO 57005 --- [pool-1-thread-1] c.i.s.l.n.m.h.HeartbeatResponseHandler   : [execute][收到连接(44223e18) 的心跳响应]// ... 服务端2020-06-22 08:24:47.388  INFO 56998 --- [ntLoopGroup-3-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(34778465) 解析到一条消息(Invocation{type='HEARTBEAT_REQUEST', message='{}'})]2020-06-22 08:24:47.390  INFO 56998 --- [pool-1-thread-1] c.i.s.l.n.m.h.HeartbeatRequestHandler    : [execute][收到连接(34778465) 的心跳请求]2020-06-22 08:24:47.399  INFO 56998 --- [ntLoopGroup-3-1] c.i.s.l.n.codec.InvocationEncoder        : [encode][连接(34778465) 编码了一条消息(Invocation{type='HEARTBEAT_RESPONSE', message='{}'})]

7. 认证逻辑

友情提示:从本小节开始,我们就具体看看业务逻辑的处理示例。

认证的过程,如下图所示:

03ca84264790f2f6d06624037e2f75f0.png

认证流程

7.1 AuthRequest

创建 AuthRequest 类,定义用户认证请求。代码如下:

public class AuthRequest implements Message {    public static final String TYPE = "AUTH_REQUEST";    /**     * 认证 Token     */    private String accessToken;        // ... 省略 setter、getter、toString 方法}

这里我们使用 accessToken 认证令牌进行认证。

因为一般情况下,我们使用 HTTP 进行登录系统,然后使用登录后的身份标识(例如说 accessToken 认证令牌),将客户端和当前用户进行认证绑定。

7.2 AuthResponse

创建 AuthResponse 类,定义用户认证响应。代码如下:

public class AuthResponse implements Message {    public static final String TYPE = "AUTH_RESPONSE";    /**     * 响应状态码     */    private Integer code;    /**     * 响应提示     */    private String message;        // ... 省略 setter、getter、toString 方法}

7.3 AuthRequestHandler

服务端...

创建 AuthRequestHandler 类,为服务端处理客户端的认证请求。代码如下:

@Componentpublic class AuthRequestHandler implements MessageHandler {    @Autowired    private NettyChannelManager nettyChannelManager;    @Override    public void execute(Channel channel, AuthRequest authRequest) {        // <1> 如果未传递 accessToken        if (StringUtils.isEmpty(authRequest.getAccessToken())) {            AuthResponse authResponse = new AuthResponse().setCode(1).setMessage("认证 accessToken 未传入");            channel.writeAndFlush(new Invocation(AuthResponse.TYPE, authResponse));            return;        }        // <2> ... 此处应有一段        // <3> 将用户和 Channel 绑定        // 考虑到代码简化,我们先直接使用 accessToken 作为 User        nettyChannelManager.addUser(channel, authRequest.getAccessToken());        // <4> 响应认证成功        AuthResponse authResponse = new AuthResponse().setCode(0);        channel.writeAndFlush(new Invocation(AuthResponse.TYPE, authResponse));    }    @Override    public String getType() {        return AuthRequest.TYPE;    }}

代码比较简单,胖友看看 <1>、<2>、<3>、<4> 上的注释。

7.4 AuthResponseHandler

客户端...

创建 AuthResponseHandler 类,为客户端处理服务端的认证响应。代码如下:

@Componentpublic class AuthResponseHandler implements MessageHandler {    private Logger logger = LoggerFactory.getLogger(getClass());    @Override    public void execute(Channel channel, AuthResponse message) {        logger.info("[execute][认证结果:{}]", message);    }    @Override    public String getType() {        return AuthResponse.TYPE;    }}

打印个认证结果,方便调试。

7.5 TestController

客户端...

创建 TestController 类,提供 /test/mock 接口,模拟客户端向服务端发送请求。代码如下:

@RestController@RequestMapping("/test")public class TestController {    @Autowired    private NettyClient nettyClient;    @PostMapping("/mock")    public String mock(String type, String message) {        // 创建 Invocation 对象        Invocation invocation = new Invocation(type, message);        // 发送消息        nettyClient.send(invocation);        return "success";    }}

7.6 简单测试

启动 Netty Server 服务端,再启动 Netty Client 客户端,然后使用 Postman 模拟一次认证请求。如下图所示:

a0ebbb6c3db31d5fd86db6c9870c97bc.png

Postman 模拟认证请求

同时,可以看到认证成功的日志如下:

// 客户端...2020-06-22 08:41:12.364  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationEncoder        : [encode][连接(9e086597) 编码了一条消息(Invocation{type='AUTH_REQUEST', message='{"accessToken": "yunai"}'})]2020-06-22 08:41:12.390  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(9e086597) 解析到一条消息(Invocation{type='AUTH_RESPONSE', message='{"code":0}'})]2020-06-22 08:41:12.392  INFO 57583 --- [pool-1-thread-1] c.i.s.l.n.m.auth.AuthResponseHandler     : [execute][认证结果:AuthResponse{code=0, message='null'}]// 服务端...2020-06-22 08:41:12.374  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(791f122b) 解析到一条消息(Invocation{type='AUTH_REQUEST', message='{"accessToken": "yunai"}'})]2020-06-22 08:41:12.379  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationEncoder        : [encode][连接(791f122b) 编码了一条消息(Invocation{type='AUTH_RESPONSE', message='{"code":0}'})]

8. 单聊逻辑

私聊的过程,如下图所示:

706bcfd2c6be546e11062b815c232b05.png

私聊流程

服务端负责将客户端 A 发送的私聊消息,转发给客户端 B。

8.1 ChatSendToOneRequest

创建 ChatSendToOneRequest 类,发送给指定人的私聊消息的请求。代码如下:

public class ChatSendToOneRequest implements Message {    public static final String TYPE = "CHAT_SEND_TO_ONE_REQUEST";    /**     * 发送给的用户     */    private String toUser;    /**     * 消息编号     */    private String msgId;    /**     * 内容     */    private String content;        // ... 省略 setter、getter、toString 方法}

8.2 ChatSendResponse

创建 ChatSendResponse 类,聊天发送消息结果的响应。代码如下:

public class ChatSendResponse implements Message {    public static final String TYPE = "CHAT_SEND_RESPONSE";    /**     * 消息编号     */    private String msgId;    /**     * 响应状态码     */    private Integer code;    /**     * 响应提示     */    private String message;        // ... 省略 setter、getter、toString 方法}

8.3 ChatRedirectToUserRequest

创建 ChatRedirectToUserRequest 类, 转发消息给一个用户的请求。代码如下:

public class ChatRedirectToUserRequest implements Message {    public static final String TYPE = "CHAT_REDIRECT_TO_USER_REQUEST";    /**     * 消息编号     */    private String msgId;    /**     * 内容     */    private String content;        // ... 省略 setter、getter、toString 方法}

友情提示:写完之后,艿艿突然发现少了一个 fromUser 字段,表示来自谁的消息。

8.4 ChatSendToOneHandler

服务端...

创建 ChatSendToOneHandler 类,为服务端处理客户端的私聊请求。代码如下:

@Componentpublic class ChatSendToOneHandler implements MessageHandler {    @Autowired    private NettyChannelManager nettyChannelManager;    @Override    public void execute(Channel channel, ChatSendToOneRequest message) {        // <1> 这里,假装直接成功        ChatSendResponse sendResponse = new ChatSendResponse().setMsgId(message.getMsgId()).setCode(0);        channel.writeAndFlush(new Invocation(ChatSendResponse.TYPE, sendResponse));        // <2> 创建转发的消息,发送给指定用户        ChatRedirectToUserRequest sendToUserRequest = new ChatRedirectToUserRequest().setMsgId(message.getMsgId())                .setContent(message.getContent());        nettyChannelManager.send(message.getToUser(), new Invocation(ChatRedirectToUserRequest.TYPE, sendToUserRequest));    }    @Override    public String getType() {        return ChatSendToOneRequest.TYPE;    }}

代码比较简单,胖友看看 <1>、<2> 上的注释。

8.5 ChatSendResponseHandler

客户端...

创建 ChatSendResponseHandler 类,为客户端处理服务端的聊天响应。代码如下:

@Componentpublic class ChatSendResponseHandler implements MessageHandler {    private Logger logger = LoggerFactory.getLogger(getClass());    @Override    public void execute(Channel channel, ChatSendResponse message) {        logger.info("[execute][发送结果:{}]", message);    }    @Override    public String getType() {        return ChatSendResponse.TYPE;    }}

打印个聊天发送结果,方便调试。

8.6 ChatRedirectToUserRequestHandler

客户端

创建 ChatRedirectToUserRequestHandler 类,为客户端处理服务端的转发消息的请求。代码如下:

@Componentpublic class ChatRedirectToUserRequestHandler implements MessageHandler {    private Logger logger = LoggerFactory.getLogger(getClass());    @Override    public void execute(Channel channel, ChatRedirectToUserRequest message) {        logger.info("[execute][收到消息:{}]", message);    }    @Override    public String getType() {        return ChatRedirectToUserRequest.TYPE;    }}

打印个聊天接收消息,方便调试。

8.7 简单测试

① 启动 Netty Server 服务端。

② 启动 Netty Client 客户端 A。然后使用 Postman 模拟一次认证请求(用户为 yunai)。如下图所示:

bdf8565135e5563b1e99dc4f562e3d5a.png

Postman 模拟认证请求

③ 启动 Netty Client 客户端 B。注意,需要设置 --server.port 端口为 8081,避免冲突。如下图所示:

f090f837152d8bb3a4dd0903dd70674a.png

IDEA 设置

然后使用 Postman 模拟一次认证请求(用户为 tutou)。如下图所示:

894c323acc5f954c0eae2dfd407bcfca.png

Postman 模拟认证请求

④ 最后使用 Postman 模拟一次 yunai 芋艿给 tutou 土豆发送一次私聊消息。如下图所示:

6264a54fe7efaa45caa520d3b2e79891.png

Postman 模拟私聊请求

同时,可以看到客户端 A 向客户端 B 发送私聊消息的日志如下:

// 客户端 A...(芋艿)2020-06-22 08:48:09.505  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(9e086597) 编码了一条消息(Invocation{type='CHAT_SEND_TO_ONE_REQUEST', message='{toUser: "tudou", msgId: "1", content: "你猜"}'})]2020-06-22 08:48:09.510  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(9e086597) 解析到一条消息(Invocation{type='CHAT_SEND_RESPONSE', message='{"code":0,"msgId":"1"}'})]2020-06-22 08:48:09.511  INFO 57583 --- [ool-1-thread-69] c.i.s.l.n.m.c.ChatSendResponseHandler    : [execute][发送结果:ChatSendResponse{msgId='1', code=0, message='null'}]2020-06-22 08:48:35.148  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(9e086597) 编码了一条消息(Invocation{type='CHAT_SEND_TO_ONE_REQUEST', message='{toUser: "tutou", msgId: "1", content: "你猜"}'})]2020-06-22 08:48:35.150  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(9e086597) 解析到一条消息(Invocation{type='CHAT_SEND_RESPONSE', message='{"code":0,"msgId":"1"}'})]2020-06-22 08:48:35.150  INFO 57583 --- [ool-1-thread-70] c.i.s.l.n.m.c.ChatSendResponseHandler    : [execute][发送结果:ChatSendResponse{msgId='1', code=0, message='null'}]// 服务端 ...2020-06-22 08:48:35.149  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(791f122b) 解析到一条消息(Invocation{type='CHAT_SEND_TO_ONE_REQUEST', message='{toUser: "tutou", msgId: "1", content: "你猜"}'})]2020-06-22 08:48:35.149  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(791f122b) 编码了一条消息(Invocation{type='CHAT_SEND_RESPONSE', message='{"code":0,"msgId":"1"}'})]2020-06-22 08:48:35.149  INFO 56998 --- [ntLoopGroup-3-3] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(79cb3a1e) 编码了一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"你猜","msgId":"1"}'})]// 客户端 B...(秃头)2020-06-22 08:48:18.277  INFO 59613 --- [ntLoopGroup-2-1] c.i.s.l.n.c.handler.NettyClientHandler   : [userEventTriggered][发起一次心跳]2020-06-22 08:48:18.278  INFO 59613 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationEncoder        : [encode][连接(24fbc3e8) 编码了一条消息(Invocation{type='HEARTBEAT_REQUEST', message='{}'})]2020-06-22 08:48:18.280  INFO 59613 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(24fbc3e8) 解析到一条消息(Invocation{type='HEARTBEAT_RESPONSE', message='{}'})]2020-06-22 08:48:18.281  INFO 59613 --- [pool-1-thread-4] c.i.s.l.n.m.h.HeartbeatResponseHandler   : [execute][收到连接(24fbc3e8) 的心跳响应]2020-06-22 08:48:35.150  INFO 59613 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(24fbc3e8) 解析到一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"你猜","msgId":"1"}'})]2020-06-22 08:48:35.151  INFO 59613 --- [pool-1-thread-5] l.n.m.c.ChatRedirectToUserRequestHandler : [execute][收到消息:ChatRedirectToUserRequest{msgId='1', content='你猜'}]

9. 群聊逻辑

群聊的过程,如下图所示:

8a0bdfaf05f33b45b39b57e0c99de6f5.png

群聊流程

服务端负责将客户端 A 发送的群聊消息,转发给客户端 A、B、C。

友情提示:考虑到逻辑简洁,艿艿提供的本小节的示例,并不是一个一个群,而是所有人在一个大的群聊中哈~

9.1 ChatSendToAllRequest

创建 ChatSendToOneRequest 类,发送给所有人的群聊消息的请求。代码如下:

public class ChatSendToAllRequest implements Message {    public static final String TYPE = "CHAT_SEND_TO_ALL_REQUEST";    /**     * 消息编号     */    private String msgId;    /**     * 内容     */    private String content;        // ... 省略 setter、getter、toString 方法}

友情提示:如果是正经的群聊,会有一个 groupId 字段,表示群编号。

9.2 ChatSendResponse

和「8.2 ChatSendResponse」小节一致。

9.3 ChatRedirectToUserRequest

和「8.3 ChatRedirectToUserRequest」小节一致。

9.4 ChatSendToAllHandler

服务端...

创建 ChatSendToAllHandler 类,为服务端处理客户端的群聊请求。代码如下:

@Componentpublic class ChatSendToAllHandler implements MessageHandler {    @Autowired    private NettyChannelManager nettyChannelManager;    @Override    public void execute(Channel channel, ChatSendToAllRequest message) {        // <1> 这里,假装直接成功        ChatSendResponse sendResponse = new ChatSendResponse().setMsgId(message.getMsgId()).setCode(0);        channel.writeAndFlush(new Invocation(ChatSendResponse.TYPE, sendResponse));        // <2> 创建转发的消息,并广播发送        ChatRedirectToUserRequest sendToUserRequest = new ChatRedirectToUserRequest().setMsgId(message.getMsgId())                .setContent(message.getContent());        nettyChannelManager.sendAll(new Invocation(ChatRedirectToUserRequest.TYPE, sendToUserRequest));    }    @Override    public String getType() {        return ChatSendToAllRequest.TYPE;    }}

代码比较简单,胖友看看 <1>、<2> 上的注释。

9.5 ChatSendResponseHandler

和「8.5 ChatSendResponseHandler」小节一致。

9.6 ChatRedirectToUserRequestHandler

和「8.6 ChatRedirectToUserRequestHandler」小节一致。

9.7 简单测试

① 启动 Netty Server 服务端。

② 启动 Netty Client 客户端 A。然后使用 Postman 模拟一次认证请求(用户为 yunai)。如下图所示:

6f8dd527c77e954b01e95ed924c316ac.png

Postman 模拟认证请求

③ 启动 Netty Client 客户端 B。注意,需要设置 --server.port 端口为 8081,避免冲突。

57a12f89b20e772c2495ff81bdd97787.png

IDEA 设置

④ 启动 Netty Client 客户端 C。注意,需要设置 --server.port 端口为 8082,避免冲突。

8ec43b7e935d3d4611c8476b76068b2b.png

IDEA 设置

⑤ 最后使用 Postman 模拟一次发送群聊消息。如下图所示:

5357b3439e4b3b40c8e1167ccd22b4d4.png

Postman 模拟群聊请求

同时,可以看到客户端 A 群发给所有客户端的日志如下:

// 客户端 A...2020-06-22 08:55:44.898  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(9e086597) 编码了一条消息(Invocation{type='CHAT_SEND_TO_ALL_REQUEST', message='{msgId: "2", content: "广播消息"}'})]2020-06-22 08:55:44.901  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(9e086597) 解析到一条消息(Invocation{type='CHAT_SEND_RESPONSE', message='{"code":0,"msgId":"2"}'})]2020-06-22 08:55:44.901  INFO 57583 --- [ol-1-thread-148] c.i.s.l.n.m.c.ChatSendResponseHandler    : [execute][发送结果:ChatSendResponse{msgId='2', code=0, message='null'}]2020-06-22 08:55:44.901  INFO 57583 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(9e086597) 解析到一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"广播消息","msgId":"2"}'})]2020-06-22 08:55:44.903  INFO 57583 --- [ol-1-thread-149] l.n.m.c.ChatRedirectToUserRequestHandler : [execute][收到消息:ChatRedirectToUserRequest{msgId='2', content='广播消息'}]// 服务端...2020-06-22 08:55:44.898  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(791f122b) 解析到一条消息(Invocation{type='CHAT_SEND_TO_ALL_REQUEST', message='{msgId: "2", content: "广播消息"}'})]2020-06-22 08:55:44.901  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(791f122b) 编码了一条消息(Invocation{type='CHAT_SEND_RESPONSE', message='{"code":0,"msgId":"2"}'})]2020-06-22 08:55:44.901  INFO 56998 --- [ntLoopGroup-3-2] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(791f122b) 编码了一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"广播消息","msgId":"2"}'})]2020-06-22 08:55:44.901  INFO 56998 --- [ntLoopGroup-3-3] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(79cb3a1e) 编码了一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"广播消息","msgId":"2"}'})]2020-06-22 08:55:44.901  INFO 56998 --- [ntLoopGroup-3-4] c.i.s.l.n.codec.InvocationEncoder        : [decode][连接(9dc03826) 编码了一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"广播消息","msgId":"2"}'})]// 客户端 B...2020-06-22 08:55:44.902  INFO 59613 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(24fbc3e8) 解析到一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"广播消息","msgId":"2"}'})]2020-06-22 08:55:44.902  INFO 59613 --- [ool-1-thread-83] l.n.m.c.ChatRedirectToUserRequestHandler : [execute][收到消息:ChatRedirectToUserRequest{msgId='2', content='广播消息'}]// 客户端 C...2020-06-22 08:55:44.901  INFO 61597 --- [ntLoopGroup-2-1] c.i.s.l.n.codec.InvocationDecoder        : [decode][连接(9128c71c) 解析到一条消息(Invocation{type='CHAT_REDIRECT_TO_USER_REQUEST', message='{"content":"广播消息","msgId":"2"}'})]2020-06-22 08:55:44.903  INFO 61597 --- [ool-1-thread-16] l.n.m.c.ChatRedirectToUserRequestHandler : [execute][收到消息:ChatRedirectToUserRequest{msgId='2', content='广播消息'}]

本文在提供完整代码示例转发+关注 私信我【源码】直接免费分享给你

原创不易,给点个 Star 嘿,一起冲鸭!

  • 6
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值