项目地址:https://gitee.com/cai-jinxiang/chat-server
网络模块:采用muduo库完成,解耦了网络与业务模块
服务层:使用c++11技术,设计了消息id及回调函数的绑定,服务器和客户端
数据存储层:使用mysql存储消息,用户信息,离线消息,群聊消息等
负载均衡模块:Nginx的基于TCP的负载均衡模块,长连接
redis的发布订阅功能,作为消息队列,服务器中不同用户的通信。
使用的技术
一种轻量级的数据交换格式,只包含一个json.hpp
使用Json格式作为客户端和服务端之间的消息传递格式
一个项目通常的目录
bin:生成的可执行文件
lib:生成的库文件
include:头文件
src:源文件
build:编译产生的中间文件
example:示例文件
thridparty:第三方库的源码文件,比如json.hpp
CMakeLists.txt
autobuild.sh :一键编译,执行的cmake文件
在做项目时,在项目根目录创建 build文件,把cmake文件放进去,并在cmake设置文件中,设置可执行文件的路径为根目录下的bin
使用redis的发布订阅功能,在多服务器中进行通信
通过hredis库进行c++的redis编码
// 在独立线程中接收订阅通道中的消息
void Redis::observer_channel_message()
{
redisReply *reply = nullptr;
while (REDIS_OK == redisGetReply(this->_subcribe_context, (void **)&reply))
{
// 订阅收到的消息是一个带三元素的数组
if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr)
{
// 给业务层上报通道上发生的消息
_notify_message_handler(atoi(reply->element[1]->str) , reply->element[2]->str);
}
freeReplyObject(reply);
}
cerr << ">>>>>>>>>>>>> observer_channel_message quit <<<<<<<<<<<<<" << endl;
}
// 向redis指定的通道subscribe订阅消息
bool Redis::subscribe(int channel)
{
// SUBSCRIBE命令本身会造成线程阻塞等待通道里面发生消息,这里只做订阅通道,不接收通道消息
// 通道消息的接收专门在observer_channel_message函数中的独立线程中进行
// 只负责发送命令,不阻塞接收redis server响应消息,否则和notifyMsg线程抢占响应资源
if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "SUBSCRIBE %d", channel))
{
cerr << "subscribe command failed!" << endl;
return false;
}
// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)
int done = 0;
while (!done)
{
if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done))
{
cerr << "subscribe command failed!" << endl;
return false;
}
}
// redisGetReply
return true;
}
// 向redis指定的通道channel发布消息
bool Redis::publish(int channel, string message)
{
redisReply *reply = (redisReply *)redisCommand(_publish_context, "PUBLISH %d %s", channel, message.c_str());
if (nullptr == reply)
{
cerr << "publish command failed!" << endl;
return false;
}
freeReplyObject(reply);
return true;
}
/ 向redis指定的通道unsubscribe取消订阅消息
bool Redis::unsubscribe(int channel)
{
if (REDIS_ERR == redisAppendCommand(this->_subcribe_context, "UNSUBSCRIBE %d", channel))
{
cerr << "unsubscribe command failed!" << endl;
return false;
}
// redisBufferWrite可以循环发送缓冲区,直到缓冲区数据发送完毕(done被置为1)
int done = 0;
while (!done)
{
if (REDIS_ERR == redisBufferWrite(this->_subcribe_context, &done))
{
cerr << "unsubscribe command failed!" << endl;
return false;
}
}
return true;
}
目前来说只有client主动下线
添加心跳机制,使用UDP协议绑定一个端口,负责心跳,让在线用户每隔1s发送一个心跳包,当超过ns没有心跳包时,可以认为用户下线。
tcp有个保活机制,
问题:在应用层中,只是通过send(fd, buf, sizeof(buf), 0)的返回值,来判断是否发送成功,但是,send的成功只是将buf的内容拷贝到内核的TCP缓冲区,TCP虽然有超时重传机制,但是如果一直接收不到对方的ACK超过一定次数后,就不会再传了。
解决方案: