集群聊天服务器项目介绍
我这个项目首先是一个网络集群聊天服务器的一个项目,他分为了四个模块,第一个是网络模块,网络模块我采用的是开源的muduo网络库,他解耦了网络模块和业务模块的代码,能够让开发者专注于业务的开发。 第二个是服务层,服务层这里用了一些C++11的技术,例如map,绑定器,主要做了一个消息id以及消息发生之后的回调操作的绑定,相当于做了个回调机制,当网络IO输出一个消息请求,我从这个消息解析出json,得到消息id,通过回调处理消息 数据存储层,用了mysql,对项目中的一些重要的数据进行落地存储,比如用户账号密码,好友列表,离线消息。 单机服务器的并发能力是有限的,所以考虑快速提升项目并发能力,项目要支持多机的扩展,要部署多台网络服务器,需要支持nginx负载均衡,项目主要是基于tcp私有协议进行通信,所以nginx是基于tcp负载均衡做的长链接,通信上,客户端不仅要给服务端发消息,服务器需要主动给客户端推消息,必须是长链接,短连接服务器没有办法给客户端直接推消息。 不同服务器上的用户,需要进行跨服务器通信,在这里引入了redis,作为一个消息队列的功能,利用他的发布-订阅功能,实现跨服务器消息通信。
解耦操作
指针函数:指针函数本质上其实就是一个函数,只不过他的返回值是一个指针,这里需要注意一下就是使用指针函数的时候要避免返回局部变量指针的情况,要用静态。 函数指针:函数指针本质上是一个指针,这个指针的地址指向了一个函数,是一个指向函数的指针。函数的定义都存放在代码段,函数指针就是指向代码段中函数入口地址的指针。函数指针的一个非常典型的应用就是回调函数 回调函数作用:回调函数将函数指针作为一个参数,传递给另一个函数,回调函数可以把调用者与被调用者分开,所以调用者不关心谁是被调用者,允许用户把需要调用的方法的指针作为参数传递给一个函数,以便该函数在处理相似事件的时候可以灵活的使用不同的方法。在回调中,主程序把回调函数像参数一样传入库函数,这样一来,只要我们改变传进库函数的参数,就可以实现不同的功能,当库函数很复杂或者不可见的时候利用回调函数就显得十分优秀。 正常的解耦,c++有两种方式,利用回调方式,或者利用接口编程。我们的项目采用的是回调机制。我们的设计思路是对业务进行编号,网络模块在接收到数据之后,解析出业务对象的编号,然后根据业务编号,去map容器里面找到改业务编号对应的处理函数,然后获取到处理函数的句柄,执行即可。
如何保证数据的安全传输
客户端消息如何按序显示
为什么需要按序显示:例如有两个客户端,甲和乙,甲给乙发消息,消息先后顺序发出去,但是可能由于网络问题,消息不一定以这个先后顺序到达服务端,也不一定会以既定顺序从服务端到达乙客户端,于是就有按序显示消息这个需求。 如何按序显示:给每一个消息都添加一个序列号,比如seq=0/1/2/3/4……,按照先后顺序给消息seq赋值,按照0,1,2的顺序,这样不管服务端接受到的seq顺序是什么,乙客户端维护了每一个好友的消息seq,当这个seq=0时,乙只能接受到序列号为0的消息,如果接收到不是0的消息,例如1或者2,会将这些消息先缓存到客户端本地,等收到seq与乙客户端维护的序列号相同时接收消息,然后seq+1,先查看本地里有没有seq为1的消息,有的话显示,然后就重复以上。因为有很多客户端和群组,每个客户端需要维护其他所有客户端和群组的seq。 消息有了序列号之后,还可以实现例如撤回功能,没有标记的话消息无法撤回。 **如果信息丢失:**TCP/IP里面字段有一个TTL,这是最大的跳数,消息在网络节点上没经过一个路由器就会增加一下TTL跳数,TTL通常是64,如果一个消息从甲客户端到乙客户端跑的这个路由器节点数超过64,这个路由器会将这个网络消息丢弃掉,如果乙客户端收不到这个消息,会给服务端发送消息请求甲客户端重新发送,如果还无法收到就判定这个消息无法成功接收了,然后更新seq为下一条收到消息的seq就好了。
如果网络拥塞严重,聊天服务端如何感知客户端在线还是掉线了
利用心跳机制检测客户端在线还是掉线:利用UDP绑定端口号做一个心跳业务处理,服务端会给每一个客户端分配一个心跳计数,例如:userid:zhangsan;heart:0, userid:lisi;heart:0。在服务端启动心跳计时器,超时1s,就把所有账号的心跳计数加一,客户端每隔1s,用udp发送userid和消息类型为心跳处理,服务端收到就会减1;如果服务端账号的心跳计数超过5,就判定客户端已经掉线,拆除这个客户端所有的连接以及资源。 除了应用层,TCP协议里,传输层有自带的keepalive功能,保活功能,默认关闭,创建监听套接字时用setsockopt SO_KEEPALIVE开启,这个功能有三个可以修改的参数,每隔一段时间,传输层会发送空的报文段,如果在线,会有回应。经过一系列探测如果还没回应就拆除链接。但是因为这个功能是在传输层实现的,传输层的上面才是应用层,这个只能拆除tcp的链接,但是在业务层不仅仅要关闭socketfd,还需要释放一系列资源,比如客户端还存在在服务端map表里等一系列资源,不能只关闭传输层只关闭socketfd,而且万一应用层已经死锁,传输层还是可以继续交互的,但是应用层已经无法通信,会产生僵尸连接。所以就综上,不能仅仅用tcp自带的keepalive功能。
|