基于muduo库的C++集群聊天服务器

代码链接https://github.com/ynighter/chatserver.git


目录

1.前言

 2.mysql表格设计

 3.json

4.相关类函数的实现

Public

MySQL

User

Group

FriendModel

ChatServer

Redis

5.主函数实现

6.nginx负载均衡

7.基于发布-订阅的redis

8.效果测试


1.前言

        本项目在muduo网络库的基础上,实现基于nginx负载均衡的聊天服务器,可实现用户注册,登录,群聊,一对一聊天,注销登录等功能。其主要的流程如下图所示。

         图中可见,不同的客户端由nginx使用负载均衡算法分布到相应的服务器,为克服不同服务器之通信的问题,使用redis的发布订阅的模式接收消息然后发送给对应的服务器,避免了服务器之间两两相互连接。

        主要的业务逻辑如下图所示:

 

        网络模块底层采用的muduo库,其采用的是 one loop per thread + nonblocking IO 的网络事件模型,其基于epoll的事件处理机制能够高效地处理网络事件,提供了高效的事件处理和内存管理方式。有一个较高muduo通过事件驱动的方式实现了异步I/O,能够支持上万的并发连接。用户只需要关注连接到来和socket消息到来时的业务处理。使用muduo库作为项目的核心网络模块,提供高并发以及高可用网络IO服务,解耦网络和业务模块的代码,提高了系统的可维护性和可扩展性。

        本项目在vscode上开发进行,使用ssh远程连接linux,还需要安装redis,nginx,mysql,cmake环境还需要json头文件,boost与muduo网络开发库。下图为vscode开发界面,使用ssh远程连接。其余环境文件可自行安装。

 2.mysql表格设计

        本项目的用户信息都使用mysql来存储,其中包括用户信息,好友信息,群组消息,离线消息, 对于不同的表结构的设计,分别如下所示:

       1.user表格用来存储对应的注册人员信息,包括密码,姓名,id,状态,当注册新成员时,将新成员的信息写入该表格,同时返回一个用户id用户登录。

       2.friend表格用来存储两个用户,这两个用户的好友信息只会被存储一次。

       3.offlinemessage表格用来存储在线用户给离线用户发送的信息,当离线用户登录之后,便会将离线消息发送给该用户。

       4.groupuser表格用来存储某一群组的id与该群组之中的成员id,同时每一群组成员的身份信息也被列举。

       5.最后一个表格为allgroups,用来保存已经创建的所有群组,包含群组的id,名称,与群聊描述。

 3.json

        json为一种轻量级的数据交换格式(也叫数据序列化方式),采用完全独立于编程语言的文本格式来存储和表示数据。的层次结构简洁和清晰,易于阅读和编写、机器解析和生成,且能有效地提升网络传输效率。

        在网络中,常用的数据传输序列化格式有 XML、Json、ProtoBuf。在公司级别的项目中,大量的在使用 ProtoBuf 作为数据序列化的方式,以其数据压缩编码传输,占用带宽小,同样的数据信息,是 Json 的1/10,XML 的1/20,但使用起来比 Json 稍复杂一些。

        本项目的网络中,因为简便的特性,需要使用json数据序列化方式来进行网络传输,对于json的使用需要包含以下内容:

#include "json.hpp"
using json = nlohmann::json;

        json本质为一种键值对,在数据序列化时将数据对象直接处理成json字符串进行发送,在反序列化时根据相应的key值就可以获取到对应的信息,同时对于c++,可以直接进行容器序列化,使用非常简便。

        json数据序列化:

#include "json.hpp"
#include <iostream>

int main()
{
    using json = nlohmann::json;

    json js;
    // 序列化数组
    js["array"] = {
  
  {1,2,3},{4,5,6}};
    std::cout << js << std::endl;
    // 序列化键值对
    js["key"] = "value";
    std::cout << js << std::endl;
    // 序列化对象
    js["key1"] = {"value1_1", "value1_2"};
    js["key2"] = {"value2_1", "value2_2"};
    js["msg"]["key3_1"] = "value3_1";
    js["msg"]["key3_2"] = "value3_2";
    std::cout << js << std::endl;
    
    return 0;
}

/*
{"array":[[1,2,3],[4,5,6]]}

{"array":[[1,2,3],[4,5,6]],"key":"value"}

{"array":[[1,2,3],[4,5,6]],"key":"value","key1":["value1_1","value1_2"],"key2":["value2_1","value2_2"],"msg":{"key3_1":"value3_1","key3_2":"value3_2"}}
*/

        json容器序列化:

#include "json.hpp"
#include <iostream>
#include <vector>
#include <map>

int main()
{
    using json = nlohmann::json; 
 
    // 容器序列化
    std::vector<int> arr_vctor({1,2,3,4,5});
    js["arr_vctor"] = arr_vctor;
    std::cout << js << std::endl;
    std::vector<std::vector<int>> arr2_vctor({
  
  {1,2,3},{4,5,6}});
    js["arr2_vctor"] = arr2_vctor;
    std::cout << js << std::endl;
    std::map<int, std::string> mp;
    mp[1] = "mp1"; mp[2] = "mp2";
    js["map"] = mp;
    std::cout << js << std::endl;

    return 0;
}

/*
{"arr_vctor":[1,2,3,4,5],"array":[[1,2,3],[4,5,6]],"key":"value","key1":["value1_1","value1_2"],"key2":["value2_1","value2_2"],"msg":{"key3_1":"value3_1","key3_2":"value3_2"}}

{"arr2_vctor":[[1,2,3],[4,5,6]],"arr_vctor":[1,2,3,4,5],"array":[[1,2,3],[4,5,6]],"key":"value","key1":["value1_1","value1_2"],"key2":["value2_1","value2_2"],"msg":{"key3_1":"value3_1","key3_2":"value3_2"}}

{"arr2_vctor":[[1,2,3],[4,5,6]],"arr_vctor":[1,2,3,4,5],"array":[[1,2,3],[4,5,6]],"key":"value","key1":["value1_1","value1_2"],"key2":["value2_1","value2_2"],"map":[[1,"mp1"],[2,"mp2"]],"msg":{"key3_1":"value3_1","key3_2":"value3_2"}}
*/

        json反序列化:

#include "json.hpp"
#include <iostream>
#include <string>
#include <vector>
#include <map>
using json = nlohmann::json;

std::string func1()
{ 
    json js;
    // 序列化数组
    js["array"] = {
  
  {1,2,3},{4,5,6}};
    std::cout << js << std::endl;
    // 序列化键值对
    js["key"] = "value";
    std::cout << js << std::endl;
    // 序列化对象
    js["key1"] = {"value1_1", "value1_2"};
    js["key2"] = {"value2_1", "value2_2"};
    js["msg"]["key3_1"] = "value3_1";
    js["msg"]["key3_2"] = "value3_2";
    std::cout << js << std::endl;
 
    std::string ret = js.dump();  // 将json对象序列化为字符串
    return ret;
}

std::string func2()
{ 
    json js; 
    // 容器序列化
    std::vector<int> arr_vctor({1,2,3,4,5});
    js["arr_vctor"] = arr_vctor;
    std::cout << js << std::endl;
    std::vector<std::vector<int>> arr2_vctor({
  
  {1,2,3},{4,5,6}});
    js["arr2_vctor"] = arr2_vctor;
    std::cout << js << std::endl;
    std::map<int, std::string> mp;
    mp[1] = "mp1"; mp[2] = "mp2";
    js["map"] = mp;
    std::cout << js << std::endl;

    std::string ret = js.dump();  // 将json对象序列化为字符串
    return ret;
}

int main()
{
    json js;
    std::string revBuf1 = func1();
    // 模拟从网络接收到json字符串,通过json::parse函数把json字符串转换为json对象
    json jsBuf1 = json::parse(revBuf1); 
    auto ret1 = jsBuf1["array"];   
    std::cout << ret1 << std::endl;
    auto ret2 = jsBuf1["msg"];    
    std::cout << ret2 << std::endl;
 
    std::cout << "................." << std::endl;

    std::string revBuf2 = func2();
    json jsBuf2 = json::parse(revBuf2); 
    auto ret3 = jsBuf2["arr2_vctor"];  // auto ==> vector<vector<int>>
    std::cout << ret3 << std::endl;
    auto ret4 = jsBuf2["map"];         // auto ==> map<int, string>
    std::cout << ret4 << std::endl; 

    return 0;
}

4.相关类函数的实现

        本项目的类函数编写主要为服务器和类函数两部分内容,其余部分内容为其他接口对应,本文只对不同类的函数功能做简要分析,具体功能实现请阅读文章开头附上的代码链接。

Public

        此类主要是为了区分不同种类的消息类型,进行通信时,消息种类可分为群聊消息,注册消息等,设计此类可根据不同的消息类型实现不同的回调函数。

#ifndef PUBLIC_H
#define PUBLIC_H

/* server和client的公共文件 */
enum EnMsgType
{
    LOGIN_MSG = 1,     // 登录消息  {"msgid":1,"id":*,"password":"***"}
    LOGIN_MSG_ACK,     // 登录响应消息
 
    LOGINOUT_MSG,      // 注销消息

    SIGNUP_MSG,        // 注册消息  {"msgid":3,"id":*,"name":"***","password":*}
    SIGNUP_MSG_ACK,    // 注册响应消息

    ONE_CHAT_MSG,      // 聊天消息  {"msgid":5,"id":*,"from":"***","to":*,"msg":"***"}

    ADD_FRIEND_MSG,    // 添加好友消息

    CREATE_GROUP_MSG,          // 创建群组   CREATE_GROUP_MSG_ACK,  // 创建群组响应消息 
    ADD_GROUP_MSG,             // 加入群组
    GROUP_CHAT_MSG,            // 群聊天
};

#endif

MySQL

        此类为了将发送的消息与mysql之间进行交互,实现数据库之中的查询,更新,连接等操作。

#ifndef MYSQL_H
#define MYSQL_H

#include <mysql/mysql.h>
#include <string>
#include <ctime>
using namespace std;

/* 实现MySQL数据库的操作 */
class MySQL
{
public:
	// 初始化数据库连接
	MySQL(string ip = "192.168.134.129", string username = "root", string password = "wzzclgsj111", string dbname = "chat");
	// 释放数据库连接资源
	~MySQL();

	// 连接数据库
	bool connect();

	// 更新操作 insert、delete、update
	bool update(string sql);
	
	// 查询操作 select
	MYSQL_RES* query(string sql); 
	void PrintQueryResult(MYSQL_RES* results); 

	MYSQL* getConnection() { return _conn; }
private:
	MYSQL* _conn;        // 与MySQL Server的一条连接 

	string _ip;
	string _username;
	string _password;
	string _dbname;
};

#endif

User

        此类主要是为注册的用户设计操作,初始化用户的状态,id名称与密码,同时设计一个usermodel类来进行mysql中数据表的相关操作。

#ifndef USER_H
#define USER_H

#include <string>
using namespace std;

// User表的ORM类(Object Relation Map对象关系映射)
class User
{
public:
    User(int id = -1, string name = "", string pwd = "", string state = "offline")
        : _id(id), _name(name), _pwd(pwd), _state(state)  
    { }

    void setID(int id) { _id = id; }
    void setName(string name) { _name = name; }
  
基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅 基于muduo网络集群聊天服务器和客户端源码,使用nginx tcp负载均衡,mysql数据,redis发布-订阅数据,redis发布-订阅
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值