來源:微信公衆號「編程學習基地」
web
基於 tcp 實現羣聊功能,本項目設計是在windows環境下基於套接字(Socket)和多線程編程進行開發的簡易聊天室,實現了羣聊功能,在VC6.0和VS2019運行測試無誤。編程
Windows下基於windows網絡接口Winsock的通訊步驟爲WSAStartup 進行初始化--> socket 建立套接字--> bind 綁定--> listen 監聽--> connect 鏈接--> accept 接收請求--> send/recv 發送或接收數據--> closesocket 關閉 socket--> WSACleanup 最終關閉。windows
瞭解完了一個 socket 的基本步驟後咱們瞭解一下多線程以及線程的同步。數組
線程是進程的一條執行路徑,它包含獨立的堆棧和CPU寄存器狀態,每一個線程共享全部的進程資源,包括打開的文件、信號標識及動態分配的內存等。一個進程內的全部線程使用同一個地址空間,而這些線程的執行由系統調度程序控制,調度程序決定哪一個線程可執行以及何時執行線程。
簡而言之多線程是爲了提升系統的運行效率。
安全
Win32 API下的多線程編程 也就是兩個函數的應用CreateThread
以及WaitForSingleObject
,具體案例這裏很少作介紹。服務器
每一個線程均可以訪問進程中的公共變量,資源,因此使用多線程的過程當中須要注意的問題是如何防止兩個或兩個以上的線程同時訪問同一個數據,以避免破壞數據的完整性。數據之間的相互制約包括
一、直接制約關係,即一個線程的處理結果,爲另外一個線程的輸入,所以線程之間直接制約着,這種關係能夠稱之爲同步關係
二、間接制約關係,即兩個線程須要訪問同一資源,該資源在同一時刻只能被一個線程訪問,這種關係稱之爲線程間對資源的互斥訪問,某種意義上說互斥是一種制約關係更小的同步微信
windows線程間的同步方式有四種:臨界區、互斥量、信號量、事件。網絡
本項目是基於事件內核對象實現的線程同步,事件內核對象是一種抽象的對象,有受信和未授信兩種狀態,經過等待WaitForSingleObject
實現線程同步。多線程
HANDLE CreateEvent( LPSECURITY_ATTRIBUTES lpEventAttributes, //安全屬性 BOOL bManualReset, //是否手動重置事件對象爲未受信對象 BOOL bInitialState, //指定事件對象建立時的初始狀態 LPCSTR lpName //事件對象的名稱 );
設置內核對象狀態socket
BOOL SetEvent( HANDLE hEvent /*設置事件內核對象受信*/ ); BOOL ResetEvent( HANDLE hEvent /*設置事件內核對象未受信*/ );
堵塞等待事件內核對象直到事件內核對象的狀態爲受信。
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
具體使用閱讀全文在個人我的網站裏看,篇幅太多。
在建立套接字綁定監聽以後會有一個等待鏈接的過程,在接收到新鏈接以後,須要建立一個線程來處理新鏈接,當有多個新鏈接時可經過建立多個線程來處理新鏈接,
定義最大鏈接數量以及最大套接字和最大線程
#define MAX_CLNT 256 int clnt_cnt = 0; //統計套接字 int clnt_socks[MAX_CLNT]; //管理套接字 HANDLE hThread[MAX_CLNT]; //管理線程
當有新鏈接來臨的時候建立線程處理新鏈接,並將新鏈接添加到套接字數組裏面管理
hThread[clnt_cnt] = CreateThread( NULL, // 默認安全屬性 NULL, // 默認堆棧大小 ThreadProc, // 線程入口地址(執行線程的函數) (void*)&clnt_sock, // 傳給函數的參數 0, // 指定線程當即運行 &dwThreadId); // 返回線程的ID號 clnt_socks[clnt_cnt++] = clnt_sock;
線程的處理函數ThreadProc不作講解,大體就是數據的收以及羣發。
主要講解線程同步,當有多個新鏈接來臨的時候,可能會形成多個線程同時訪問同一個數據(例如clnt_cnt)。這個時候就須要線程的同步來避免破壞數據的完整性。
首先是建立一個內核事件
HANDLE g_hEvent; /*事件內核對象*/ // 建立一個自動重置的(auto-reset events),受信的(signaled)事件內核對象 g_hEvent = CreateEvent(NULL, FALSE, TRUE, NULL);
而後再須要訪問公共變量(例如clnt_cnt
)以前進行加鎖(設置等待),訪問完成以後解鎖(設置受信)
/*等待內核事件對象狀態受信*/ WaitForSingleObject(g_hEvent, INFINITE); hThread[clnt_cnt] = CreateThread(NULL,NULL,ThreadProc,(void*)&clnt_sock,0,&dwThreadId); clnt_socks[clnt_cnt++] = clnt_sock; SetEvent(g_hEvent); /*設置受信*/
經過套接字數組來進行數據的轉發實現羣聊功能,此時也用到了線程同步
void send_msg(char* msg, int len) { int i; /*等待內核事件對象狀態受信*/ WaitForSingleObject(g_hEvent, INFINITE); for (i = 0; i < clnt_cnt; i++) send(clnt_socks[i], msg, len, 0); SetEvent(g_hEvent); /*設置受信*/ }
等待線程返回的過程當中最早用的是WaitForSingleObject
,很遺憾這是個阻塞函數,直到線程執行完成返回以後纔會繼續往下執行,因此後面經過WaitForMultipleObjects
這個windowsAPI調用對hThread線程數組進行線程等待釋放。
整個過程不算太難,主要是僅僅實現了羣聊功能,因此只須要了解windows下的網絡編程以及多線程編程和線程的同步方法就能夠實現這個樣一個功能。
源代碼後臺發送關鍵字C語言聊天室獲取,socket網絡編程方法可經過上期C語言實現web服務器學習,多線程以及線程的同步可經過閱讀全文在個人我的網站裏面