目錄
服務(wù)端
?服務(wù)端視角下的流程圖
一、數(shù)據(jù)庫管理模塊
1.1 數(shù)據(jù)庫表的創(chuàng)建
1.2 .對于數(shù)據(jù)庫的操作
1.2.1首先得連接數(shù)據(jù)庫
1.2.2執(zhí)行數(shù)據(jù)庫語句
?1.2.3 返回數(shù)據(jù)庫中存放的所有用戶的信息
?1.2.4返回數(shù)據(jù)庫中存放的所有用戶的好友信息
?二、用戶管理模塊
2.1、UserInfo類:描述單個用戶的信息
?2.2UserMana類:組織用戶的信息
2.2.1初始化
?剩下的就是各類業(yè)務(wù)的接口
三、自定義消息格式
3.1了解Json對象
?3.2、Json序列化和反序列化
3.3、模擬注冊請求和發(fā)送請求
四、網(wǎng)絡(luò)通信模塊&業(yè)務(wù)模塊
4.1、InitChatSvr函數(shù)
4.2 StartChatSvr函數(shù)
客戶端
客戶端視角下的流程圖
1、注冊
2、登錄
3、發(fā)送消息
4、添加好友
服務(wù)端
?服務(wù)端視角下的流程圖
一、數(shù)據(jù)庫管理模塊
數(shù)據(jù)庫模塊是項目的最底層,主要就是負(fù)責(zé)與數(shù)據(jù)庫進行打交道,
具體功能有:連接數(shù)據(jù)庫、將用戶信息存入數(shù)據(jù)庫中、從數(shù)據(jù)庫中獲取信息、操作數(shù)據(jù)庫中的表
1.1 數(shù)據(jù)庫表的創(chuàng)建
這里創(chuàng)建了兩張表:user,friendinfo。分別用來存放用戶信息,用戶好友信息。
1.2 .對于數(shù)據(jù)庫的操作
1.2.1首先得連接數(shù)據(jù)庫
1.2.2執(zhí)行數(shù)據(jù)庫語句
?1.2.3 返回數(shù)據(jù)庫中存放的所有用戶的信息
用戶管理模塊剛初始化的時候就需要一個工作:從數(shù)據(jù)庫當(dāng)中獲取所有用戶的信息,還有每個用戶的好友信息
?1.2.4返回數(shù)據(jù)庫中存放的所有用戶的好友信息
?二、用戶管理模塊
數(shù)據(jù)庫模塊是整個項目的最低層,而用戶管理模塊是倒數(shù)第二層,是建立在數(shù)據(jù)庫模塊之上的
2.1、UserInfo類:描述單個用戶的信息
?2.2UserMana類:組織用戶的信息
2.2.1初始化
用戶管理模塊剛初始化的時候就需要一個工作:從數(shù)據(jù)庫當(dāng)中獲取所有用戶的信息,還有每個用戶的好友信息,并將這些信息放于user_map當(dāng)中去,方便后續(xù)的查詢
?剩下的就是各類業(yè)務(wù)的接口
三、自定義消息格式
3.1了解Json對象
只有當(dāng)服務(wù)端和客戶端發(fā)送的消息格式統(tǒng)一的時候,才能正常的進行通信,就像網(wǎng)絡(luò)當(dāng)中的各種協(xié)議一樣,只有雙方都遵守的時候,才能正常的進行數(shù)據(jù)交換。
我們這里采用的是Json數(shù)據(jù)格式
Json就是一個key? :value的東西,他可以包含多動類型
簡單介紹:
示例:
這就是一個Json對象,Json還支持嵌套,可以有無數(shù)種格式
我們來測試一下:
?能夠看到,json對象用起來是非常方便的。
?我們再來測試看一下Json的NB之處:
?我們來運行一下看能打印出什么:
?3.2、Json序列化和反序列化
為什么需要序列化呢?
在tcpsocket編程的時候,我們學(xué)習(xí)過,內(nèi)存不連續(xù)的結(jié)構(gòu)體是不能直接用send發(fā)送的,需要使用序列化,將內(nèi)存組合起來,放到一塊連續(xù)的內(nèi)存當(dāng)中去,然后再發(fā)送。反序列化就是反過來,具體可以去看我之前寫的博客:網(wǎng)絡(luò)基礎(chǔ)2--HTTP協(xié)議詳解_Flying clouds的博客-CSDN博客
序列化:
?反序列化:
3.3、模擬注冊請求和發(fā)送請求
注冊請求:
?注冊響應(yīng):
四、網(wǎng)絡(luò)通信模塊&業(yè)務(wù)模塊
1、該模塊負(fù)責(zé)TCP的socket編程,來負(fù)責(zé)網(wǎng)絡(luò)通信。用epoll來監(jiān)控多個文件描述符,從而實現(xiàn)具有高并發(fā)的基礎(chǔ)
2、該模塊還負(fù)責(zé)處理各種業(yè)務(wù)如:注冊、登錄、添加好友、聊天的具體實現(xiàn)
4.1、InitChatSvr函數(shù)
這個函數(shù)的作用是初始化資源,獲取到需要的所有資源,比如用戶管理模塊的實例化指針、發(fā)送隊列和接收隊列、提供TCP服務(wù)的實例化指針、epoll操作句柄。
bool InitChatSvr(int wtc = 4){
/* 1. 初始化用戶管理類的指針 */
um_ = UserMana::GetInstance();
if(um_ == NULL){
std::cout << "初始化用戶管理失敗了, 請檢查..." << std::endl;
return false;
}
/* 2. 初始化隊列資源 */
recv_que_ = new MsgPool<ChatMsg>();
if(recv_que_ == NULL){
std::cout << "init MsgPool failed" << std::endl;
return false;
}
send_que_ = new MsgPool<ChatMsg>();
if(send_que_ == NULL){
std::cout << "init MsgPool failed" << std::endl;
return false;
}
work_thread_count_ = wtc;
/* 3. 初始化tcp通信的內(nèi)容 */
sockfd_ = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sockfd_ < 0){
perror("socket");
return false;
}
int opt = 1;
// sockfd為需要端口復(fù)用的套接字
setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(port_);
addr.sin_addr.s_addr = inet_addr("0.0.0.0");
int ret = bind(sockfd_, (struct sockaddr*)&addr, sizeof(addr));
if(ret < 0){
perror("bind");
return false;
}
ret = listen(sockfd_, 10);
if(ret < 0){
perror("listen");
return false;
}
/* 4. 初始化epoll */
ep_fd_ = epoll_create(5);
if(ep_fd_ < 0){
perror("epoll_create");
return false;
}
return true;
}
4.2 StartChatSvr函數(shù)
這個函數(shù)相當(dāng)于是開始工作的函數(shù),調(diào)用這個函數(shù)之后,主線程就會 啟動接收線程、發(fā)送線程、工作線程。
啟動完別的線程之后,我們給主線程也找了一個活干:一直在accept,并將接收到的新連接套接字放到epoll當(dāng)中去,讓epoll幫我們?nèi)ケO(jiān)控這些個文件描述符的狀態(tài)
bool StartChatSvr(){
/* 1. 啟動接收線程 */
pthread_t tid;
int ret = pthread_create(&tid, NULL, RecvStart, (void*)this);
if(ret < 0){
std::cout << "create thread failed\n";
return false;
}
/* 2. 啟動發(fā)送線程 */
ret = pthread_create(&tid, NULL, SendStart, (void*)this);
if(ret < 0){
std::cout << "create thread failed\n";
return false;
}
/* 3. 啟動工作線程 */
for(int i = 0; i < work_thread_count_; i++){
ret = pthread_create(&tid, NULL, WorkerStart, (void*)this);
if(ret < 0){
std::cout << "create thread failed\n";
return false;
}
}
/* 4. 主線程accept */
/* 5. 將新連接套接字放到epoll當(dāng)中 */
while(1){
int new_sockfd = accept(sockfd_, NULL, NULL);
if(new_sockfd < 0){
continue;
}
/* 添加到epoll */
struct epoll_event ee;
ee.events = EPOLLIN;
ee.data.fd = new_sockfd;
epoll_ctl(ep_fd_, EPOLL_CTL_ADD, new_sockfd, &ee);
}
客戶端
客戶端視角下的流程圖
?
客戶端我們這里選取了MFC框架來進行,我們這里只是用到了MFC最基本的使用方法,想要熟練掌握MFC的同學(xué)可以去看b站上面的視頻,上面的講解是非常清晰的,我們這里只是簡單來用。
我們在客戶端主要是實現(xiàn)了四個基本的業(yè)務(wù):注冊,登錄,添加好友,給好友發(fā)送消息
我們先來看看四個窗口:
?
1、注冊
我們首先要插入一個Dialog,然后去對其進行操作,具體做法就是:插入一個Dialog,然后點擊這個Dialog,我們就能看到一個窗口,然后再對窗口添加button。。。啥的各種內(nèi)容。
在窗口當(dāng)中對 : 姓名、學(xué)校、電話、密碼添加變量,用來保存之后輸入的值。
我們右鍵去對其添加變量、控件什么的,雙擊就是會給我們自動生成代碼。
我們這個注冊窗口是需要點擊提交之后,成功的話就直接跳轉(zhuǎn)到登錄的界面,我們這里選擇的是雙擊提交,生成代碼,然后在這個函數(shù)里面實現(xiàn)我們想要的功能
void CRegisterdlg::OnBnClickedCommit()
{
// TODO: 在此添加控件通知處理程序代碼
/*1.獲取輸入框的內(nèi)容*/
UpdateData(true);
if (m_nickname_.IsEmpty()||m_school_.IsEmpty()||
m_tel_.IsEmpty()||m_passwd_.IsEmpty()) {
MessageBox(TEXT("請輸入完整的注冊內(nèi)容!"));
return;
}
std::string nickname = CT2A(m_nickname_.GetString());
std::string school = CT2A(m_school_.GetString());
std::string telnum = CT2A(m_tel_.GetString());
std::string passwd = CT2A(m_passwd_.GetString());
/*2.組織ChatMSg數(shù)據(jù)*/
ChatMsg cm;
cm.msg_type_ = Register;
cm.json_msg_["nickname"] = nickname.c_str();
cm.json_msg_["school"] = school.c_str();
cm.json_msg_["telnum"] = telnum.c_str();
cm.json_msg_["passwd"] = passwd.c_str();
/*3.獲取TCP實例化指針*/
TcpSvr* ts = TcpSvr::GetInstance();
/*4.發(fā)送消息*/
std::string msg;
cm.GetMsg(&msg);
ts->Send(msg);
/*5.獲取消息隊列的實例化指針*/
MsgQueue* mq = MsgQueue::GetInstance();
if (mq == NULL) {
exit(1);
}
/*6.按照消息類型獲取注冊應(yīng)答*/
msg.clear();
mq->Pop(Register_Resp,&msg);
/*
7.解析應(yīng)答,判斷應(yīng)答結(jié)果
注冊成功 ==》跳轉(zhuǎn)到登 錄界面
注冊失敗 ==》情空注冊框,提示失敗了(電話號碼重復(fù)了)
*/
cm.clear();
cm.PraseMsg(-1,msg);
if (cm.reply_status_ == REGISTER_FAILED) {
MessageBox(TEXT("電話號碼重復(fù)了,請檢查輸入。。。"));
}
else {
MessageBox(TEXT("注冊成功!"));
}
}
2、登錄
登錄的時候,需要做到是點擊登錄按鈕之后能夠跳轉(zhuǎn)到我們的聊天界面,需要注意的是,在跳轉(zhuǎn)到聊天界面之前,我們需要提前吧聊天界面的內(nèi)容給加載好,這樣一條轉(zhuǎn)到界面我們就能進行操作的,跳轉(zhuǎn)界面我們已經(jīng)知道了是調(diào)用domodel,用模態(tài)的方式打開界面,但是怎么加載數(shù)據(jù)呢?
我們這里可以找到方法:在調(diào)用domodel函數(shù)的時候,會調(diào)用一個函數(shù),這個函數(shù)是重寫父類的虛函數(shù)
BOOL CChatDlg::OnInitDialog()
在這個函數(shù)里面,我們可以能夠?qū)崿F(xiàn)獲取聊天界面內(nèi)容的功能,我們要獲取的內(nèi)容就兩個:好友列表和好友的消息記錄。
知道了這些之后,我就只剩下開工了:
如何展示:
?保存好友信息到本地
因為好友信息是動態(tài)變化的,我們?nèi)绻皇窃讷@取好友信息的時候就在列表當(dāng)中展示一次的話,那么當(dāng)添加好友或者刪除好友的時候,我們只能獲取到新的好友信息,原來的好友信息就不知道從哪里再去獲取了,方便起見,我們就直接保存在客戶端,用vector來進行保存。
3、發(fā)送消息
我們想要發(fā)送消息,第一步肯定是要知道消息到底要發(fā)送給誰,就像微信一樣,我們在好友列表當(dāng)中點擊誰,就代表要給哪個好友發(fā)送消息了,我們?yōu)榱四軐崿F(xiàn)這個功能:需要做到的就是能夠時刻識別出來我們點擊了好友列表,并且要知道到底點擊了誰。
這個功能我們用一個MFC的函數(shù)來實現(xiàn) : GetText,這個函數(shù)能夠知道我們點擊了哪個好友
/*
當(dāng)用戶點擊了 frilist,
1. 要及時的更新 recv_userid
2. 更新對話框
*/
/* 1. 獲取點擊的內(nèi)容 */
CString str;
m_frilist_.GetText(m_frilist_.GetCurSel(), str);
/* 2. 根據(jù)點擊的內(nèi)容進行比對*/
/* 3. 更新 recv_userid_ */
int i = 0;
for (; i < fris_info_.size(); i++) {
// eg: zs-bite zs-bite:10
// zs ls
std::string tmp = CT2A(str.GetString());
if (strstr((tmp.c_str()), fris_info_[i].nickname_.c_str()) != NULL) {
recv_userid_ = fris_info_[i].user_id_;
break;
}
}
當(dāng)我們切換了好友之后,我們也要同步更新聊天框的內(nèi)容
我們這里的做法也很簡單粗暴:將現(xiàn)在所有的消息全部清空,然后再將當(dāng)前好友的歷史記錄依次展示上去
/* 4. 刷新聊天界面 */
for (int i = m_output_.GetCount(); i >= 0; i--) {
m_output_.DeleteString(i);
}
std::vector<std::string> h_m = fris_info_[i].history_msg_;
for (int i = 0; i < h_m.size(); i++) {
m_output_.InsertString(m_output_.GetCount(), h_m[i].c_str());
}
fris_info_[i].unread_msg_ = 0;
/* 5. 清空輸入框 */
m_input_.Empty();
m_input_edit_.SetWindowTextA(0);
/* 6. 刷新UserList */
ReferFriList();
4、添加好友
我們想要做到的是輸入一個好友的電話號碼,然后給服務(wù)端發(fā)送一個添加好友的請求,在服務(wù)端這里進行判斷,如果有這個電話號碼是一個注冊用戶的話,我們就給這個用戶推送一個添加好友的請求,如果不是的話服務(wù)端就忽略這條消息。文章來源:http://www.zghlxwxcb.cn/news/detail-412616.html
當(dāng)用戶收到添加好友請求的時候,可以選擇同意或者拒絕,如果拒絕,就當(dāng)作無事發(fā)生就行;如果點擊了同意,就需要給服務(wù)端回一個同意添加好友的請求,然后服務(wù)端進行處理,將兩個人的關(guān)系設(shè)置為好友,并再次給客戶端推送應(yīng)答,讓客戶端的好友列表進行更新,將新添加的好友展示上去。文章來源地址http://www.zghlxwxcb.cn/news/detail-412616.html
UpdateData(true);
if (m_fritel.IsEmpty()) {
MessageBox(TEXT("輸入內(nèi)容不能為空..."));
return;
}
std::string input = CT2A(m_fritel.GetString());
ChatMsg cm;
cm.msg_type_ = AddFriend;
cm.user_id_ = userid_;
cm.json_msg_["fri_telnum"] = input.c_str();
/* 3. 獲取TCP的實例化指針 */
TcpSvr* ts = TcpSvr::GetInstance();
/* 4. 發(fā)送消息 */
std::string msg;
cm.GetMsg(&msg);
ts->Send(msg);
CDialog::OnCancel();
到了這里,關(guān)于項目---基于TCP的高并發(fā)聊天系統(tǒng)的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!