Session和 channel 相互绑定,再截取出来,分析一下。
ClientSession session = new ClientSession(channel); channel.attr(ClientSession.SESSION).set(session); session.setUser(ChatClient.this.getUser()); 为什么要Session和 channel 相互绑定呢?
发的时候, 需要从Session 写入 Channel ,这相当于正向的绑定。
收的时候,是从Channel 过来的,需要找到 Session ,这相当于反向的绑定。
Netty 中的 channel ,实现了AttributeMap接口 ,相当于一个 Map容器。 反向的绑定,利用了channel 的这个特点。
看一下AttributeMap接口 如何使用的?
AttributeMap接口的使用 AttributeMap 是一个接口,并且只有一个attr()方法,接收一个AttributeKey类型的key,返回一个Attribute类型的value。按照Javadoc,AttributeMap实现必须是线程安全的。
AttributeMap内部结构看起来像下面这样:
不要被吓着了,其实很简单。
AttributeMap 的使用,主要是设置和取值。
设值 Key-> Value
AttributeMap 的设值的方法,举例如下:
channel.attr(ClientSession.SESSION).set(session);这个是链式调用,attr() 方法中的是 Key, set()方法中的是Value。 这样就完成了 Key-> Value 的设置。
取值
AttributeMap 的取值的方法,举例如下:
ClientSession session = ctx.channel().attr(ClientSession.SESSION).get();这个是链式调用,attr() 方法中的是 Key, get()方法返回 的是Value。 这样就完成了 取值。
关键是,这个key比较特殊。
一般的Map,Key 的类型多半为字符串。但是这里的Key不行,有特殊的约定。
Key的类型必须是 AttributeKey 类型,而且这是一个泛型类,它的优势是,不需要对值进行强制的类型转换。
Key的例子如下:
public static final AttributeKey<ClientSession> SESSION = AttributeKey.valueOf("session"); 客户端登录请求登录的请求,大致如下:
ClientSender的 代码如下:
package com.crazymakercircle.chat.client; @Service("ClientSender") public class ClientSender { static final Logger LOGGER = LoggerFactory.getLogger(ClientSender.class); private User user; private ClientSession session; public void sendLoginMsg() { LOGGER.info("开始登陆"); ProtoMsg.Message message = LoginMsgBuilder.buildLoginMsg(user); session.writeAndFlush(message); } //... public boolean isLogin() { return session.isLogin(); } }Sender 首先通过 LoginMsgBuilder,构造一个protobuf 消息。然后调用session发送消息。
session 会通过绑定的channel ,将消息发送出去。
session的代码,如下:
public synchronized void writeAndFlush(Object pkg) { channel.writeAndFlush(pkg); }其他的客户端请求流程,大致也是类似的。
一个客户端的请求大致的流程有三步,分别从Sender 到session到channel。
处理登录成功的响应 这是从服务器过来的入站消息。 如果登录成功,服务器会发送一个登录成功的响应过来。 这个响应,会从channel 传递到Handler。
处理器 LoginResponceHandler 的代码如下:
package com.crazymakercircle.chat.clientHandler; //... public class LoginResponceHandler extends ChannelInboundHandlerAdapter { static final Logger LOGGER = LoggerFactory.getLogger(LoginResponceHandler.class); /** * 业务逻辑处理 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { LOGGER.info("msg:{}", msg.toString()); if (msg != null && msg instanceof ProtoMsg.Message) { ProtoMsg.Message pkg = (ProtoMsg.Message) msg; ProtoMsg.LoginResponse info = pkg.getLoginResponse(); ProtoInstant.ResultCodeEnum result = ProtoInstant.ResultCodeEnum.values()[info.getCode()]; if (result.equals(ProtoInstant.ResultCodeEnum.SUCCESS)) { ClientSession session = ctx.channel().attr(ClientSession.SESSION).get(); session.setLogin(true); LOGGER.info("登录成功"); } } } }