欧美1区2区3区激情无套,两个女人互添下身视频在线观看,久久av无码精品人妻系列,久久精品噜噜噜成人,末发育娇小性色xxxx

C++項(xiàng)目推薦-基于muduo庫(kù)的單聊群聊項(xiàng)目-可寫(xiě)簡(jiǎn)歷

1 項(xiàng)目簡(jiǎn)介

今天分享基于muduo庫(kù)實(shí)現(xiàn)的單聊群聊項(xiàng)目,該項(xiàng)目支持QT客戶端一對(duì)一聊天,服務(wù)端基于muduo+MySQL+Redis.

視頻講解:C++校招項(xiàng)目-基于muduo庫(kù)的分布式單聊群聊項(xiàng)目-可寫(xiě)簡(jiǎn)歷

源項(xiàng)目地址:https://github.com/haojoy/WeChat.git

2 Linux C++后端編譯和運(yùn)行

部署項(xiàng)目,我只講解基于我改過(guò)的版本,原始版本大家參考原有的部署方式。

部署前提:

  • 安裝好MySQL
  • 安裝好Redis

該項(xiàng)目需要MySQL和redis基礎(chǔ)

首先安裝依賴庫(kù):

  • json庫(kù)相關(guān):sudo apt-get install nlohmann-json3-dev
  • redis開(kāi)發(fā)包: sudo apt-get install -y libhiredis-dev
#首先解壓老廖提供的代碼

# 進(jìn)入項(xiàng)目
cd WeChat
mkdir build
cd build
#重新cmake 編譯debug方式
cmake -DCMAKE_BUILD_TYPE=Debug ..
make -j4

編譯成功后build目錄的bin目錄產(chǎn)生ChatServer和ChatClient執(zhí)行文件。

  • ChatServer:是后端服務(wù)程序,可以接入qt客戶端,也可以接入ChatClient
  • ChatClient:是Linux環(huán)境的命令行客戶端,具體功能看源碼實(shí)現(xiàn)。

啟動(dòng)ChatServer之前,我們要根據(jù)自己的MySQL賬號(hào)信息修改mysql相關(guān)的配置。

WeChat/application/chatserver/include/server/db/connection.h

const string  kMySqlIp = "127.0.0.1";
const string kMySqlUserName = "root";
const string kMySqlPassword = "123456";
const int kMySqlPort = 3306;
const string kMySqlDbName = "wechat";

主要是修改用戶kMySqlUserName和密碼kMySqlPassword。

重新make下。

運(yùn)行后端程序,記得以sudo方式運(yùn)行,因?yàn)槔锩嬗行┠夸浀膭?chuàng)建需要sudo權(quán)限。

sudo ./bin/ChatServer

默認(rèn)的監(jiān)聽(tīng)端口:

8088:fileserver相關(guān)

8080:chatserver相關(guān)

啟動(dòng)Linux命令行客戶端

./bin/ChatClient 127.0.0.1 8080

我們可以選擇創(chuàng)建用戶

========================
1. login
2. register
3. quit
========================
choice:2
username:darren
userpassword:123
name register success, userid is 1, do not forget it!

3 QT客戶端編譯和運(yùn)行

編譯環(huán)境:QT5.15.2 MinGW 64-bit

3.1 修改chatserver和fileserver地址

運(yùn)行代碼前修改服務(wù)器地址chatserver和fileserver的ip和端口。

3.1.1 修改chatserver地址

修改位置: Net\packdef.h

#define _DEF_TCP_PORT (8080)

#define _DEF_SERVER_IP ("192.168.1.27")

3.1.2 修改fileserver地址

修改位置: Common\fileTransferProtocol.h

#define _DEF_FILE_SERVER_IP ("192.168.1.27")

#define _DEF_FILE_SERVER_PROT (8088)

3.2 啟動(dòng)QT和注冊(cè)賬號(hào)

這個(gè)QT客戶端是有修改過(guò):void Kernel::slot_ChangeUserIcon()才正常設(shè)置頭像。

使用QT5.15.2 MinGW 64-bit啟動(dòng)QT,如果需要聊天,則需要啟動(dòng)兩個(gè)qt客戶端。

這里開(kāi)了兩個(gè)QT客戶端進(jìn)行聊天。

注意:目前QT客戶端還有部分功能并不完整,大家可以自行添加功能,或者修改Linux命令行的ChatClient進(jìn)行測(cè)試。

4 Linux后端框架快速分析

這個(gè)項(xiàng)目基于muduo架構(gòu),如果你不熟悉muduo則需要先學(xué)習(xí)muduo網(wǎng)絡(luò)模型,這個(gè)網(wǎng)上資料很多的。

4.1 數(shù)據(jù)庫(kù)的創(chuàng)建

application/chatserver/src/db/connection.cpp,這里直接使用代碼創(chuàng)建數(shù)據(jù)庫(kù)和對(duì)應(yīng)的表單

bool Connection::createDBTables() {
	if(createDBCnt_++ != 0){
		return true;
	}

	if (mysql_real_connect(_conn, kMySqlIp.c_str(), kMySqlUserName.c_str(), kMySqlPassword.c_str(), nullptr, kMySqlPort, nullptr, 0) == nullptr) {
        LOG_ERROR << "MySQL connection error: " << mysql_error(_conn);
        return false;
    }

	string queryStr = "CREATE DATABASE IF NOT EXISTS `" + kMySqlDbName + "`";
	if (mysql_query(_conn, queryStr.c_str()) != 0) {
		LOG_ERROR << "MySQL createDatabase error: " << mysql_error(_conn);
		return false;
	}

	queryStr = "USE `" + kMySqlDbName + "`";
	if (mysql_query(_conn, queryStr.c_str()) != 0) {
		LOG_ERROR << "MySQL useDatabase error: " << mysql_error(_conn);
		return false;
	}

	string sql_tuser = "CREATE TABLE IF NOT EXISTS `t_user` (\
		`userid` int NOT NULL AUTO_INCREMENT PRIMARY KEY, \
		`avatar_id` VARCHAR(36) DEFAULT NULL, \
		`username` VARCHAR(64) DEFAULT NULL, \
		`password` VARCHAR(64) DEFAULT NULL, \
		`tel` VARCHAR(15) DEFAULT NULL, \
		`state` enum('online','offline') CHARACTER SET latin1 DEFAULT 'offline' \
	)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
	if (mysql_query(_conn, sql_tuser.c_str()) != 0) {
		LOG_ERROR << "MySQL createTable  t_user error: " << mysql_error(_conn);
		return false;
	}

	string sql_tfile = "CREATE TABLE IF NOT EXISTS `t_file` (\
		file_id VARCHAR(36) NOT NULL PRIMARY KEY, \
		file_name VARCHAR(255) NOT NULL, \
		file_path TEXT NOT NULL, \
		file_size BIGINT NOT NULL, \
		file_md5 CHAR(32) NOT NULL, \
		file_state VARCHAR(50) NOT NULL DEFAULT 'PENDING' \
	)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
	if (mysql_query(_conn, sql_tfile.c_str()) != 0) {
		LOG_ERROR << "MySQL createTable t_file error: " << mysql_error(_conn);
		return false;
	}

	string sql_friendship = "CREATE TABLE IF NOT EXISTS `t_friendship` (\
		`userid` int NOT NULL, \
		`friend_id` int NOT NULL, \
		KEY `userid` (`userid`,`friend_id`) \
	)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
	if (mysql_query(_conn, sql_friendship.c_str()) != 0) {
		LOG_ERROR << "MySQL createTable t_friendship error: " << mysql_error(_conn);
		return false;
	}

	string sql_offlinemsg = "CREATE TABLE IF NOT EXISTS `t_offlinemsg` (\
		`id` INT AUTO_INCREMENT PRIMARY KEY, \
		`sendTo` INT NOT NULL, \
		`sendFrom` INT NOT NULL, \
		`messageContent` TEXT NOT NULL, \
		`messageType` ENUM('text', 'friend_apply', 'vedio', 'audio', 'file') NOT NULL DEFAULT 'text', \
		`createTime` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP \
	)ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
	if (mysql_query(_conn, sql_offlinemsg.c_str()) != 0) {
		LOG_ERROR << "MySQL createTable t_offlinemsg error: " << mysql_error(_conn);
		return false;
	}

	string sql_allgrp = "CREATE TABLE IF NOT EXISTS `t_allgrp` (\
		`id` int(11) NOT NULL AUTO_INCREMENT, \
		`groupname` varchar(50) CHARACTER SET latin1 NOT NULL, \
		`groupdesc` varchar(200) CHARACTER SET latin1 DEFAULT '', \
		PRIMARY KEY (`id`), \
		UNIQUE KEY `groupname` (`groupname`) \
	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
	if (mysql_query(_conn, sql_allgrp.c_str()) != 0) {
		LOG_ERROR << "MySQL createTable t_allgrp error: " << mysql_error(_conn);
		return false;
	}

	string sql_grpuser = "CREATE TABLE IF NOT EXISTS `t_grpuser` (\
		`groupid` int(11) NOT NULL, \
		`userid` int(11) NOT NULL, \
		`grouprole` enum('creator','normal') CHARACTER SET latin1 DEFAULT NULL, \
		KEY `groupid` (`groupid`,`userid`) \
	) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;";
	if (mysql_query(_conn, sql_grpuser.c_str()) != 0) {
		LOG_ERROR << "MySQL createTable t_grpuser error: " << mysql_error(_conn);
		return false;
	}
	return true;
}


4.2 main函數(shù)位置

main函數(shù)入口位置:WeChat/application/chatserver/src/main.cpp, line 17

(gdb) b main
Breakpoint 1 at 0x1584a6: file /home/lqf/linux/reactor/WeChat/application/chatserver/src/main.cpp, line 17.
(gdb) 

4.3 架構(gòu)核心

消息協(xié)議設(shè)計(jì)

協(xié)議采用json做序列化,設(shè)計(jì)的json字符串里有個(gè)msgid字段,用來(lái)區(qū)分不同的消息。消息類型如下所示:

enum Message
{
    LOGIN_MSG = 1, // 登錄消息
    LOGIN_MSG_ACK, // 登錄響應(yīng)消息
    LOGINOUT_MSG, // 注銷消息
    REG_MSG, // 注冊(cè)消息
    REG_MSG_ACK, // 注冊(cè)響應(yīng)消息
    ONE_CHAT_MSG, // 聊天消息
    ADD_FRIEND_REQ, // 添加好友消息
    ADD_FRIEND_RSP,

    CREATE_GROUP_MSG, // 創(chuàng)建群組
    ADD_GROUP_MSG, // 加入群組
    GROUP_CHAT_MSG, // 群聊天

    GET_FRIEND_INFO_REQ, // 獲取待添加好友的信息
    GET_FRIEND_INFO_RSP, // 查找信息結(jié)果
    REFRESH_FRIEND_LIST,

    SET_AVATAR_RQ,
    SET_AVATAR_RS,
    SET_AVATAR_COMPLETE_NOTIFY,
};

然后通過(guò)_msgHandlerMap根據(jù)不同的msgid調(diào)用對(duì)應(yīng)的函數(shù)進(jìn)行處理。

// 注冊(cè)消息以及對(duì)應(yīng)的Handler回調(diào)操作
ChatService::ChatService()
{
    // 用戶基本業(yè)務(wù)管理相關(guān)事件處理回調(diào)注冊(cè)
    _msgHandlerMap.insert({LOGIN_MSG, std::bind(&ChatService::login, this, _1, _2, _3)});
    _msgHandlerMap.insert({LOGINOUT_MSG, std::bind(&ChatService::loginout, this, _1, _2, _3)});
    _msgHandlerMap.insert({REG_MSG, std::bind(&ChatService::userRegister, this, _1, _2, _3)});
    _msgHandlerMap.insert({ONE_CHAT_MSG, std::bind(&ChatService::oneChat, this, _1, _2, _3)});
    _msgHandlerMap.insert({ADD_FRIEND_REQ, std::bind(&ChatService::addFriendReq, this, _1, _2, _3)});
    _msgHandlerMap.insert({ADD_FRIEND_RSP, std::bind(&ChatService::addFriendRsp, this, _1, _2, _3)});

    // 群組業(yè)務(wù)管理相關(guān)事件處理回調(diào)注冊(cè)
    _msgHandlerMap.insert({CREATE_GROUP_MSG, std::bind(&ChatService::createGroup, this, _1, _2, _3)});
    _msgHandlerMap.insert({ADD_GROUP_MSG, std::bind(&ChatService::addGroup, this, _1, _2, _3)});
    _msgHandlerMap.insert({GROUP_CHAT_MSG, std::bind(&ChatService::groupChat, this, _1, _2, _3)});

    _msgHandlerMap.insert({GET_FRIEND_INFO_REQ, std::bind(&ChatService::getFriendInfoReq, this, _1, _2, _3)});
    _msgHandlerMap.insert({SET_AVATAR_RQ, std::bind(&ChatService::dealAvatarUpdateRq, this, _1, _2, _3)});
    _msgHandlerMap.insert({SET_AVATAR_COMPLETE_NOTIFY, std::bind(&ChatService::dealAvatarUploadComplete, this, _1, _2, _3)});

    // 連接redis服務(wù)器
    if (_redis.connect())
    {
        // 設(shè)置上報(bào)消息的回調(diào)
        _redis.init_notify_handler(std::bind(&ChatService::handleRedisSubscribeMessage, this, _1, _2));
    }
}

處理邏輯

// 上報(bào)讀寫(xiě)事件相關(guān)信息的回調(diào)函數(shù)
void ChatServer::onMessage(const TcpConnectionPtr &conn,
                           Buffer *buffer,
                           Timestamp time)
{
    string buf = buffer->retrieveAllAsString();

    // 測(cè)試,添加json打印代碼
    cout << buf << endl;

    // 數(shù)據(jù)的反序列化
    json js;
    .......
    js = json::parse(buf);
    ........
    // 達(dá)到的目的:完全解耦網(wǎng)絡(luò)模塊的代碼和業(yè)務(wù)模塊的代碼
    // 通過(guò)js["msgid"] 獲取=》業(yè)務(wù)handler=》conn  js  time
    auto msgHandler = ChatService::instance()->getHandler(js["msgid"].get<int>());
    // 回調(diào)消息綁定好的事件處理器,來(lái)執(zhí)行相應(yīng)的業(yè)務(wù)處理
    msgHandler(conn, js, time);
}

登錄邏輯

登錄正常后,以u(píng)ser id作為key, TcpConnectionPtr作為value插入到_userConnMap,后續(xù)發(fā)送消息就是根據(jù)這個(gè)user id找到對(duì)應(yīng)的客戶端連接。

// 處理登錄業(yè)務(wù)  id  pwd   pwd
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
    .......
    // 登錄成功,記錄用戶連接信息
    {
        lock_guard<mutex> lock(_connMutex);
        _userConnMap.insert({id, conn});
    }

}

登錄成功后,可以:

  • 查詢用戶信息:_userModel.queryuserinfo
  • 查詢離線消息:_offlineMsgModel.query(id)
  • 查詢好友列表: _friendModel.query(id)
  • 查詢?nèi)毫斜恚篲groupModel.queryGroups(id)

然后根據(jù)查詢結(jié)果,做json序列化后發(fā)送給客戶端。

4.4 分布式框架

這個(gè)項(xiàng)目采用了分布式架構(gòu)的方式,以支持更多的客戶端加入,不同的服務(wù)直接使用redis進(jìn)行消息轉(zhuǎn)發(fā)。

其實(shí)邏輯并不復(fù)雜,以一對(duì)一聊天代碼為例:

登錄相關(guān)處理

  • 不管客戶端登錄哪個(gè)ChatServer,登錄成功后都從redis消息隊(duì)列訂閱自己的channel,channel根據(jù)user id
void ChatService::login(const TcpConnectionPtr &conn, json &js, Timestamp time)
{
// id用戶登錄成功后,向redis訂閱channel(id)
_redis.subscribe(id);
}

一對(duì)一聊天相關(guān)處理

一對(duì)一聊天發(fā)送消息相關(guān)處理(ChatService::oneChat函數(shù)):

  • 先查詢對(duì)方是否在同一個(gè)ChatServer,如果是同一個(gè)ChatServer,則可以在_userConnMap查詢到對(duì)方
int toid = js["receiverid"].get<int>();
    {
        lock_guard<mutex> lock(_connMutex);
        auto it = _userConnMap.find(toid);
        if (it != _userConnMap.end())
        {
            // toid在線,轉(zhuǎn)發(fā)消息   服務(wù)器主動(dòng)推送消息給toid用戶
            it->second->send(js.dump());        //如果在同一個(gè)服務(wù)器,則直接發(fā)送
            return;
        }
    }

直接調(diào)用對(duì)方的tcpconnection發(fā)送信息即可。

  • 如果查詢不到對(duì)方,則將消息發(fā)送給消息隊(duì)列,以對(duì)方user id作為channel,這樣對(duì)方就能收到消息的推送
// 查詢toid是否在線 
    User user = _userModel.query(toid);
    if (user.getState() == "online")
    {
        _redis.publish(toid, js.dump());
        return;
    }

如何獲取訂閱數(shù)據(jù)

訂閱數(shù)據(jù)的獲取有單獨(dú)的線程,class Redis 這個(gè)類在封裝的時(shí)候提供了回調(diào)接口_notify_message_handler,

有獨(dú)立的線程不斷調(diào)用observer_channel_message:

// 在獨(dú)立線程中接收訂閱通道中的消息
void Redis::observer_channel_message()
{
    redisReply *reply = nullptr;
    while (REDIS_OK == redisGetReply(this->_subcribe_context, (void **)&reply))
    {
        // 訂閱收到的消息是一個(gè)帶三元素的數(shù)組
        if (reply != nullptr && reply->element[2] != nullptr && reply->element[2]->str != nullptr)
        {
            // 給業(yè)務(wù)層上報(bào)通道上發(fā)生的消息
            _notify_message_handler(atoi(reply->element[1]->str) , reply->element[2]->str);
        }

        freeReplyObject(reply);
    }

    cerr << ">>>>>>>>>>>>> observer_channel_message quit <<<<<<<<<<<<<" << endl;
}

具體是調(diào)用ChatService::handleRedisSubscribeMessage處理訂閱的數(shù)據(jù)

// 從redis消息隊(duì)列中獲取訂閱的消息
void ChatService::handleRedisSubscribeMessage(int userid, string msg)
{
    lock_guard<mutex> lock(_connMutex);
    auto it = _userConnMap.find(userid);
    if (it != _userConnMap.end())
    {
        it->second->send(msg);
        return;
    }

    // 存儲(chǔ)該用戶的離線消息
    _offlineMsgModel.insert(userid, msg);
}

5 擴(kuò)展

需要思考的問(wèn)題:

  • 當(dāng)前的協(xié)議設(shè)計(jì)是否有粘包半包的問(wèn)題
  • 是否可以使用kafka消息隊(duì)列替換redis消息隊(duì)列。
  • 目前的明文密碼方式是否合適。
  • 在線狀態(tài)寫(xiě)到數(shù)據(jù)庫(kù)里是否合適?
#項(xiàng)目##實(shí)習(xí)##后端開(kāi)發(fā)##C++##簡(jiǎn)歷中的項(xiàng)目經(jīng)歷要怎么寫(xiě)#
全部評(píng)論

相關(guān)推薦

某公司一顆釘子:可以看看下面這幾個(gè)項(xiàng)目 云存儲(chǔ)項(xiàng)目:https://www.bilibili.com/video/BV1XPfTY8EGD/ 多線程任務(wù)隊(duì)列系統(tǒng):https://www.bilibili.com/video/BV1XS9dYsE9d/ RPC項(xiàng)目:https://www.bilibili.com/video/BV15ff4YsEPy/ web多人聊天:https://www.bilibili.com/video/BV1iYtrezEkA/ 仿寫(xiě)redis之Qedis:https://www.bilibili.com/video/BV1a4zzYKEAt/
點(diǎn)贊 評(píng)論 收藏
分享
?今天給大家分享的是一位粉絲的提問(wèn),北大軟微研二,有大模型項(xiàng)目經(jīng)歷但技術(shù)淺,C++&nbsp;后端與國(guó)企哪個(gè)好上岸?接下來(lái)把粉絲的具體提問(wèn)和我的回復(fù)分享給大家,希望也能給一些類似情況的小伙伴一些啟發(fā)和幫助。同學(xué)提問(wèn):本科,哈爾濱工業(yè)大學(xué)(深圳)計(jì)算機(jī)科學(xué)與技術(shù)專業(yè),本科學(xué)習(xí)不是很刻苦(計(jì)算機(jī)組成原理自己實(shí)現(xiàn)過(guò)流水線的CPU),除了一些課程實(shí)驗(yàn)和畢業(yè)設(shè)計(jì),沒(méi)有什么項(xiàng)目經(jīng)驗(yàn),全靠自己的數(shù)學(xué)天賦和能力(高數(shù)年級(jí)排名5%),研究生考上了北京大學(xué)軟件與微電子學(xué)院軟件工程專業(yè)(數(shù)學(xué)一144),目前研二下期,研一除了上課沒(méi)有什么項(xiàng)目經(jīng)驗(yàn)。研二在導(dǎo)師組里做橫向,是一個(gè)和大模型相關(guān)的Django后端pythom項(xiàng)目。自身...
點(diǎn)贊 評(píng)論 收藏
分享
某公司一顆釘子:想把tcp聊天室的項(xiàng)目寫(xiě)到簡(jiǎn)歷里面的話需要做一定的擴(kuò)展,可以參考一下這個(gè)項(xiàng)目:https://www.bilibili.com/video/BV1iYtrezEkA/ 也可以看看下面這幾個(gè)項(xiàng)目 云存儲(chǔ):https://www.bilibili.com/video/BV1XPfTY8EGD/ 多線程任務(wù)隊(duì)列系統(tǒng):https://www.bilibili.com/video/BV1XS9dYsE9d/ RPC項(xiàng)目:https://www.bilibili.com/video/BV15ff4YsEPy/
點(diǎn)贊 評(píng)論 收藏
分享
評(píng)論
點(diǎn)贊
8
分享

創(chuàng)作者周榜

更多
正在熱議
更多
# 面試問(wèn)題記錄 #
63663次瀏覽 923人參與
# 工作中,你有沒(méi)有遇到非常愛(ài)罵人的領(lǐng)導(dǎo)? #
17927次瀏覽 131人參與
# 京東TGT #
48905次瀏覽 179人參與
# 我的2024小目標(biāo) #
58996次瀏覽 396人參與
# 工作一周年分享 #
19511次瀏覽 111人參與
# 互聯(lián)網(wǎng)行業(yè)現(xiàn)在還值得去嗎 #
6711次瀏覽 42人參與
# 硬件人的簡(jiǎn)歷怎么寫(xiě) #
255257次瀏覽 2891人參與
# 面試吐槽bot #
15368次瀏覽 95人參與
# 面試經(jīng)驗(yàn)談 #
41451次瀏覽 562人參與
# 拼多多工作體驗(yàn) #
17023次瀏覽 152人參與
# 上班到公司第一件事做什么? #
39022次瀏覽 367人參與
# 實(shí)習(xí)生應(yīng)該準(zhǔn)時(shí)下班嗎 #
202364次瀏覽 1320人參與
# 你覺(jué)得技術(shù)面多長(zhǎng)時(shí)間合理? #
86509次瀏覽 647人參與
# 國(guó)企和大廠硬件兄弟怎么選? #
120425次瀏覽 1656人參與
# 入職第五天,你被拉進(jìn)了幾個(gè)工作群 #
18281次瀏覽 80人參與
# 工作時(shí)那些社死瞬間 #
23927次瀏覽 175人參與
# 假如我穿越到了媽媽的18歲 #
6664次瀏覽 43人參與
# 你遇到過(guò)哪些神仙同事 #
72879次瀏覽 649人參與
# 請(qǐng)用你的專業(yè)向媽媽表白 #
12110次瀏覽 88人參與
# 技術(shù)轉(zhuǎn)行的心路歷程 #
47843次瀏覽 665人參與
# 你想吐槽公司的哪些規(guī)定 #
15162次瀏覽 58人參與
??途W(wǎng)
牛客企業(yè)服務(wù)