国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

項目---基于TCP的高并發(fā)聊天系統(tǒng)

這篇具有很好參考價值的文章主要介紹了項目---基于TCP的高并發(fā)聊天系統(tǒng)。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

目錄

服務(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ù)端視角下的流程圖

項目---基于TCP的高并發(fā)聊天系統(tǒng)

一、數(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。分別用來存放用戶信息,用戶好友信息。

項目---基于TCP的高并發(fā)聊天系統(tǒng)

1.2 .對于數(shù)據(jù)庫的操作

1.2.1首先得連接數(shù)據(jù)庫

項目---基于TCP的高并發(fā)聊天系統(tǒng)

1.2.2執(zhí)行數(shù)據(jù)庫語句

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?1.2.3 返回數(shù)據(jù)庫中存放的所有用戶的信息

用戶管理模塊剛初始化的時候就需要一個工作:從數(shù)據(jù)庫當(dāng)中獲取所有用戶的信息,還有每個用戶的好友信息

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?1.2.4返回數(shù)據(jù)庫中存放的所有用戶的好友信息

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?二、用戶管理模塊

數(shù)據(jù)庫模塊是整個項目的最低層,而用戶管理模塊是倒數(shù)第二層,是建立在數(shù)據(jù)庫模塊之上的

2.1、UserInfo類:描述單個用戶的信息

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?2.2UserMana類:組織用戶的信息

2.2.1初始化

用戶管理模塊剛初始化的時候就需要一個工作:從數(shù)據(jù)庫當(dāng)中獲取所有用戶的信息,還有每個用戶的好友信息,并將這些信息放于user_map當(dāng)中去,方便后續(xù)的查詢

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?剩下的就是各類業(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 數(shù)據(jù)類型:對象,數(shù)組,字符串,數(shù)字
對象:使?花括號 {} 括起來的表??個對象。

示例:

{
????????" 姓名" : " ?張 " ,
????????" 年齡" : 20,
???????? "成績 " : [ 99 , 89 , 66 ]
}

這就是一個Json對象,Json還支持嵌套,可以有無數(shù)種格式

數(shù)組:使?中括號 [] 括起來的表??個數(shù)組。
這個就是一個數(shù)組的形式,一個元素的類型就是一個Json對象
[
????????{
????????????????" 姓名" : " ?張 " ,
????????????????" 年齡" : 20,
???????????????? "成績 " : [ 99 , 89 , 66 ]
????????},
????????
????????{
????????????????" 姓名" : " ?王 " ,
????????????????" 年齡" : 22,
???????????????? "成績 " : [ 77 , 88 , 66 ]
????????},
]
字符串:使?常規(guī)雙引號 "" 括起來的表??個字符串
數(shù)字:包括整形和浮點型,直接使?
jsoncpp庫,已經(jīng)幫助我們封裝了json對象,數(shù)組,以及序列化和反序列化的接口,我們在這里只需要會用就行了。

我們來測試一下:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?能夠看到,json對象用起來是非常方便的。

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?我們再來測試看一下Json的NB之處:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?我們來運行一下看能打印出什么:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?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博客

序列化:項目---基于TCP的高并發(fā)聊天系統(tǒng)

?反序列化:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

3.3、模擬注冊請求和發(fā)送請求

注冊請求:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?注冊響應(yīng):

項目---基于TCP的高并發(fā)聊天系統(tǒ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);
            }
        

客戶端

客戶端視角下的流程圖

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?

客戶端我們這里選取了MFC框架來進行,我們這里只是用到了MFC最基本的使用方法,想要熟練掌握MFC的同學(xué)可以去看b站上面的視頻,上面的講解是非常清晰的,我們這里只是簡單來用。

我們在客戶端主要是實現(xiàn)了四個基本的業(yè)務(wù):注冊,登錄,添加好友,給好友發(fā)送消息

我們先來看看四個窗口:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?

1、注冊

我們首先要插入一個Dialog,然后去對其進行操作,具體做法就是:插入一個Dialog,然后點擊這個Dialog,我們就能看到一個窗口,然后再對窗口添加button。。。啥的各種內(nèi)容。

在窗口當(dāng)中對 : 姓名、學(xué)校、電話、密碼添加變量,用來保存之后輸入的值。

我們右鍵去對其添加變量、控件什么的,雙擊就是會給我們自動生成代碼。

我們這個注冊窗口是需要點擊提交之后,成功的話就直接跳轉(zhuǎn)到登錄的界面,我們這里選擇的是雙擊提交,生成代碼,然后在這個函數(shù)里面實現(xiàn)我們想要的功能

項目---基于TCP的高并發(fā)聊天系統(tǒng)

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)容就兩個:好友列表好友的消息記錄。

知道了這些之后,我就只剩下開工了:

如何展示:

項目---基于TCP的高并發(fā)聊天系統(tǒng)

?保存好友信息到本地

因為好友信息是動態(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ù)端就忽略這條消息。

當(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)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包