客户端 Client 登录和响应处理

疯狂创客圈 Java 分布式聊天室【 亿级流量】实战系列之 17【 博客园 总入口


源码IDEA工程获取链接Java 聊天室 实战 源码

写在前面

​ 大家好,我是作者尼恩。目前和几个小伙伴一起,组织了一个高并发的实战社群【疯狂创客圈】。正在开始高并发、亿级流程的 IM 聊天程序 学习和实战

​ 前面,已经完成一个高性能的 Java 聊天程序的四件大事:

  1. 完成了协议选型,选择了性能更佳的 Protobuf协议。具体的文章为: Netty+Protobuf 整合一:实战案例,带源码

  2. 介绍了 通讯消息数据包的几条设计准则。具体的文章为: Netty +Protobuf 整合二:protobuf 消息通讯协议设计的几个准则

  3. 解决了一个非常基础的问题,这就是通讯的 粘包和半包问题。具体的文章为:Netty 粘包/半包 全解 | 史上最全解读

  4. 前一篇文件,已经完成了 系统三大组成模块的组成介绍。 具体的文章为:Netty聊天程序(实战一):从0开始实战100w级流量应用

    今天介绍非常重要的一个内容:

    客户端的通讯、登录请求和登录响应设计

    下面,开启今天的 惊险和刺激实战之旅

客户端的会话管理

​ 什么是会话?

​ 为了方便客户端的开发,管理与服务器的连接,这里引入一个非常重要的中间角色——Session (会话)。有点儿像Web开发中的Tomcat的服务器 Session,但是又有很大的不同。

​ 客户端的会话概念图,如下图所示:

​ 客户端会话有两个很重的成员,一个是user,代表了拥有会话的用户。一个是channel,代表了连接的通道。两个成员的作用是:

  • 通过user,可以获得当前的用户信息

  • 通过channel,可以向服务器发送消息

    所以,会话左拥右抱,左手用户资料,右手服务器的连接。在本例的开发中,会经常用到。

客户端的逻辑构成

从逻辑上来说,客户端有三个子的功能模块。

模块一:Handler

入站处理器。

在Netty 中非常重要,负责处理入站消息。比方,服务器发送过来登录响应,服务器发送过来的聊天消息。

模块二:MsgBuilder

消息组装器。

将 Java 内部的 消息 Bean 对象,转成发送出去的 Protobuf 消息。

模块三:Sender

消息发送器。

Handler 负责收的工作。Sender 则是负责将消息发送出去。

三大子模块的类关系图:

介绍完成了主要的组成部分后,开始服务器的连接和Session 的创建。

连接服务器与Session 的创建

​ 通过bootstrap 帮助类,设置完成线程组、通道类型,向管道流水线加入处理器Handler后,就可以开始连接服务器的工作。

​ 本小节需要重点介绍的,是连接成功之后,创建 Session,并且将 Session和 channel 相互绑定。

​ 代码如下:

package com.crazymakercircle.chat.client;
//...
@Data
@Service("EchoClient")
public class ChatClient
{
static final Logger LOGGER =
LoggerFactory.getLogger(ChatClient.class);
//..
private Channel channel;
private ClientSender sender; public void doConnect(Bootstrap bootstrap, EventLoopGroup eventLoopGroup)
{
ChannelFuture f = null;
try
{
if (bootstrap != null)
{
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
bootstrap.remoteAddress(host, port); // 设置通道初始化
bootstrap.handler(
new ChannelInitializer<SocketChannel>()
{
public void initChannel(SocketChannel ch) throws Exception
{
ch.pipeline().addLast(new ProtobufDecoder());
ch.pipeline().addLast(new ProtobufEncoder());
ch.pipeline().addLast(chatClientHandler); }
}
);
LOGGER.info(new Date() + "客户端开始登录[疯狂创客圈IM]"); f = bootstrap.connect().addListener((ChannelFuture futureListener) ->
{
final EventLoop eventLoop = futureListener.channel().eventLoop();
if (!futureListener.isSuccess())
{
LOGGER.info("与服务端断开连接!在10s之后准备尝试重连!");
eventLoop.schedule(() -> doConnect(new Bootstrap(), eventLoop), 10, TimeUnit.SECONDS); initFalg = false;
}
else
{
initFalg = true;
}
if (initFalg)
{
LOGGER.info("EchoClient客户端连接成功!"); LOGGER.info(new Date() + ": 连接成功,启动控制台线程……");
channel = futureListener.channel(); // 创建会话
ClientSession session = new ClientSession(channel);
channel.attr(ClientSession.SESSION).set(session);
session.setUser(ChatClient.this.getUser());
startConsoleThread();
} }); // 阻塞
f.channel().closeFuture().sync();
}
} catch (Exception e)
{
LOGGER.info("客户端连接失败!" + e.getMessage());
} } //... }

Session和 channel 相互绑定

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("登录成功");
}
}
} }

​ LoginResponceHandler 对消息类型进行判断,如果是请求响应消息,并且登录成功。 则取出绑定的session,通过session,进一步完成登录成功后的业务处理。

​ 比如设置成功的状态,完成一些成功的善后处理操作等等。

​ 其他的客户端响应处理流程,大致也是类似的。

写在最后

​ 至此为止,可以看到,客户端登录的完整流程。

​ 下一篇:服务器的请求处理和通讯的全流程闭环介绍。


疯狂创客圈 Java 死磕系列

  • Java (Netty) 聊天程序【 亿级流量】实战 开源项目实战

Netty聊天室(2):从0开始实战100w级流量应用的更多相关文章

  1. Netty聊天器(实战一):从0开始实战100w级流量应用

    Java 聊天程序(百万级流量实战一):系统介绍 疯狂创客圈 Java 分布式聊天室[ 亿级流量]实战系列之14 [博客园 总入口 ] 源码IDEA工程获取链接:Java 聊天室 实战 源码 写在前面 ...

  2. Netty聊天室-源码

    目录 Netty聊天室 源码工程 写在前面 [百万级流量 聊天室实战]: [分布式 聊天室] [Spring +Netty]: [Netty 原理] 死磕 系列 [提升篇]: [内力大增篇]: 疯狂创 ...

  3. java架构之路-(netty专题)netty的基本使用和netty聊天室

    上次回顾: 上次博客,我们主要说了我们的IO模型,BIO同步阻塞,NIO同步非阻塞,AIO基于NIO二次封装的异步非阻塞,最重要的就是我们的NIO,脑海中应该有NIO的模型图. Netty概念: Ne ...

  4. 【开源】SpringBootNetty聊天室V1.2.0升级版本介绍

    前言 SpringBoot!微服务微架构的基础,Netty通信框架的元老级别框架,即之前的SpringBoot与Netty的实现聊天室的功能后已经过了不到一周的时间啦,今天我们更新了项目版本从V1.0 ...

  5. netty 聊天室

    https://blog.csdn.net/qq_37372007/article/details/82937584 使用netty实现一个多人聊天室--failed: Error during We ...

  6. Netty网络聊天室之会话管理

    写过web的同学们应该对Session这个东西很熟悉.浏览器第一次与服务器建立连接的时候,服务器就会自动为之分配一个Session.Session可以用来判断用户是否经过登录验证,也可以保存用户的各种 ...

  7. Netty学习笔记(六) 简单的聊天室功能之WebSocket客户端开发实例

    在之前的Netty相关学习笔记中,学习了如何去实现聊天室的服务段,这里我们来实现聊天室的客户端,聊天室的客户端使用的是Html5和WebSocket实现,下面我们继续学习. 创建客户端 接着第五个笔记 ...

  8. Netty学习笔记(四) 简单的聊天室功能之服务端开发

    前面三个章节,我们使用了Netty实现了DISCARD丢弃服务和回复以及自定义编码解码,这篇博客,我们要用Netty实现简单的聊天室功能. Ps: 突然想起来大学里面有个课程实训,给予UDP还是TCP ...

  9. netty实现消息中心(二)基于netty搭建一个聊天室

    前言 上篇博文(netty实现消息中心(一)思路整理 )大概说了下netty websocket消息中心的设计思路,这篇文章主要说说简化版的netty聊天室代码实现,支持群聊和点对点聊天. 此demo ...

随机推荐

  1. java 中文转Unicode 以及 Unicode转中文

    package com.sun; public class Snippet {    public static void main(String[] args) {        String cn ...

  2. 使用LBS(地理位置)

    一.使用之前的简单封装的OkHttpUtil http://www.cnblogs.com/itfenqing/p/6758909.html 二.关键代码: public class MainActi ...

  3. 杂(三)-The type java.lang.Object cannot be resolved It is indirectly referenced ...

    The type java.lang.Object cannot be resolved. It is indirectly referenced from required .class files ...

  4. Java Learning Path(五)资源篇

    Java Learning Path(五)资源篇 1. http://java.sun.com/ (英文) Sun的Java网站,是一个应该经常去看的地方.不用多说. 2.http://www-900 ...

  5. 深入理解dp px density

    1 http://blog.csdn.net/lcaihy1314/article/details/8446401 2 待续

  6. window.location网页URL信息

    window.location属性 描述 hash 设置或获取 href 属性中在井号“#”后面的分段. host 设置或获取 location 或 URL 的 hostname 和 port 号码. ...

  7. winform对话框拖拽显示文件路径的问题

    allow drop=true; dragEnter dragDrop vs管理员账户拖拽会失败

  8. Struts2学习之拦截器栈

    © 版权声明:本文为博主原创文章,转载请注明出处 拦截器栈: - 从结构上看:拦截器栈相当于多个拦截器的组合 - 从功能上看:拦截器栈也是拦截器 默认拦截器栈: - 在struts-core.jar中 ...

  9. Android的View 事件传递

    欢迎转载,请附出处: http://blog.csdn.net/as02446418/article/details/47422891 1.基础知识 (1) 全部 Touch 事件都被封装成了 Mot ...

  10. eureka集群高可用配置,亲测成功配置(转)

    转自大神的文章:https://blog.csdn.net/tianyaleixiaowu/article/details/78184793 网上讲这个东西的很多,抄来抄去的,大部分类似,多数没讲明白 ...