客户端构建消息 =》 客户端连接 =》 客户端发送消息 =》 服务端解码并处理 =》服务端返回值写入session的channle =》 客户端收到消息并打印
这里有两个重要成员,User 和 Session .
之前在第 27节中已经提及过channel如何设计和存储,其中session起到了一个中间人的角色,连接用户和渠道。
基本的发送器定义了功能如下:
- 判断连接是否正常
- 判断用户是否登录
- 发送方法
具体的每个功能的发送器来实现基类的功能,重写相关的方法。
public abstract class BaseSender {
private User user;
private ClientSession session;
public boolean isConnected() {
if (null == session) {
log.info("session is null");
return false;
}
return session.isConnected();
}
public boolean isLogin() {
if (null == session) {
log.info("session is null");
return false;
}
return session.isLogin();
}
public void sendMsg(ProtoMsg.Message message) {
if (null == getSession() || !isConnected()) {
log.info("连接还没成功");
return;
}
Channel channel = getSession().getChannel();
ChannelFuture f = channel.writeAndFlush(message);
f.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future)
throws Exception {
// 回调
if (future.isSuccess()) {
sendSucced(message);
} else {
sendfailed(message);
}
}
});
}
登录消息发送器的功能比较简单,其实就是构建用户报文,然后调用基类的send方法。
public class LoginSender extends BaseSender {
public void sendLoginMsg() {
if (!isConnected()) {
log.info("还没有建立连接!");
return;
}
log.info("构造登录消息");
ProtoMsg.Message message = LoginMsgConverter.build(getUser(), getSession());
log.info("发送登录消息");
super.sendMsg(message);
}
}
首先启动服务端8081端口,然后客户端连接,并且发送登录报文
@Test
public void testLoginSender() throws IOException {
initBootstrap();
// 设置通道初始化
b.handler(
new ChannelInitializer<SocketChannel>() {
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast("decoder", new SimpleProtobufDecoder());
ch.pipeline().addLast("encoder", new SimpleProtobufEncoder());
}
}
);
log.info("测试用例:客户端开始连接 [疯狂创客圈IM]");
ChannelFuture f = b.connect();//异步发起连接
f.addListener(connectedListener2);
try {
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("测试用例执行完成");
f.channel().close();
}
发送逻辑代码如下
GenericFutureListener<ChannelFuture> connectedListener2 = (ChannelFuture f) ->
{
final EventLoop eventLoop = f.channel().eventLoop();
if (!f.isSuccess()) {
log.info("连接失败!在10s之后准备尝试重连!");
} else {
log.info("测试用例 :疯狂创客圈 IM 服务器 连接成功!");
channel = f.channel();
// 创建会话
ClientSession session = new ClientSession(channel);
session.setConnected(true);
// channel.closeFuture().addListener(closeListener);
startLogin(session);
}
};
private void startLogin(ClientSession session) {
//登录
User user = new User();
user.setUid("1");
user.setToken(UUID.randomUUID().toString());
user.setDevId(UUID.randomUUID().toString());
loginSender.setUser(user);
loginSender.setSession(session);
loginSender.sendLoginMsg();
}
客户端:
2022-04-01 22:02:27.128 INFO 39885 --- [ main] client.TestConnectAndLoginSender : 测试用例:客户端开始连接 [疯狂创客圈IM]
2022-04-01 22:02:27.368 INFO 39885 --- [ntLoopGroup-2-1] client.TestConnectAndLoginSender : 测试用例 :疯狂创客圈 IM 服务器 连接成功!
2022-04-01 22:02:27.385 INFO 39885 --- [ntLoopGroup-2-1] c.c.imClient.sender.LoginSender : 构造登录消息
2022-04-01 22:02:27.446 INFO 39885 --- [ntLoopGroup-2-1] c.c.imClient.sender.LoginSender : 发送登录消息
SimpleProtobufEncoder:encode0:encoder length=130
2022-04-01 22:02:27.529 INFO 39885 --- [ntLoopGroup-2-1] c.c.imClient.sender.BaseSender : 发送成功
SimpleProtobufDecoder:decode0:decoder length=60
服务端:
2022-04-01 22:02:27.592 INFO 39873 --- [ppool-2-mixed-1] c.crazymakercircle.im.common.bean.User LN:58 登录中: User{uid='1', devId='85417830-a3d0-4921-aef7-e778a078a26a', token='ed405b47-f1af-450e-b39e-91400c200a1e', nickName='nickName', platform=WINDOWS}
2022-04-01 22:02:27.621 INFO 39873 --- [ppool-2-mixed-1] c.c.imServer.session.ServerSession LN:69 ServerSession 绑定会话 /127.0.0.1:54217
2022-04-01 22:02:27.625 INFO 39873 --- [ppool-2-mixed-1] c.c.imServer.session.SessionMap LN:35 用户登录:id= 1 在线总数: 1
2022-04-01 22:02:27.658 INFO 39873 --- [ppool-2-mixed-1] c.c.i.handler.LoginRequestHandler LN:73 登录成功:User{uid='1', devId='85417830-a3d0-4921-aef7-e778a078a26a', token='ed405b47-f1af-450e-b39e-91400c200a1e', nickName='nickName', platform=WINDOWS}