LANChat工作整理
2013/8/22
程序實現功能:
局域網聊天軟件,啟動即可找到在線設備,並能夠進行簡單的文字聊天。
其實下面這個框圖已經說明了程序的絕大部分功能原理。
核心類的程序框圖
我覺得,這個程序中使用的最好的技術,應該就是IOCP了。后面我會針對IOCP好好地寫一篇博文,這個技術雖然剛學的時候有點亂,但是確實很好用。
上面的框圖中中間的UDPServer線程等待的事件完成是MainServer線程在Listen函數調用結束后設置的事件。這里忘了標了。
說明
前幾天在實驗室看《Windows網絡與通信程序設計》這本書,看完了前5章吧,就覺得目前手頭的技術去做一個局域網聊天軟件應該差不多了。雖然還有很多細節性的東西還不是非常懂,但是做一個小規模的軟件,我自認為是問題不大的。就這樣,在大約4天之后,也就是這個月的18號,這款LANChat程序誕生~
首先我聲明:因為我第一次寫網絡相關的程序,所以肯定存在疏忽,在相關方面肯定是存在不少bug的,另外我在測試的時候也經常遇到程序啟動失敗的情況,而且原因尚未查明。所以我並不保證這款程序的穩定性和安全性。(作為這個程序的設計人員真是感到汗顏~以后會好的)
另外代碼中大部分析構函數形同虛設,畢竟最初實現的時候尚不清楚能夠實現與其功能,所以根本就沒顧忌資源釋放這一塊。比如,聊天窗口建立這一塊我就沒使用delete。
多線程部分因為涉及到對數據的訪問問題,所以我使用了關鍵段:CriticalSection結構以及相關函數。
因為這個文檔是在寢室寫的,所以沒有在線設備,也無法打開聊天窗口,在實驗室三台計算機之間使用沒問題。
另外winsock2初始化是在工程中選擇的,在如console類的程序中使用socket程序前一定要做好相關的初始化以及庫,文件包含工作。
socket使用程序初次嘗試。
1 程序實現基本思想
首先用UDP廣播通知在線設備,在本地IP對應的位置有設備上線。局域網內的在線設備收到UDP廣播消息后,首先會做的事情是,獲取目標的IP地址,然后初始化一個sockaddr結構,向其發送TCP連接請求。
剛上線的設備此時會已經做好接受准備(這里使用的事件線程同步),在TCP連接請求建立后,會將其加入到IOCP中,並且為其投遞一個數據接受的消息。之后任何到來的消息都會由IOCP的線程函數專門進行處理,十分的方便。而IOCP線程函數是通過調用GetQueueCompletionStatues函數將線程添加到IOCP的線程池中的。
這樣任何一台剛上限的設備都能夠找到局域網內的其他在線設備並與其簡歷TCP鏈接,為局域網聊天軟件的實現奠定了理論基礎。
2 界面實現
因為這個程序其實並不設計什么算法性的代碼,連數據查找我用的貌似也是STL的find方法,所以對於程序的網絡代碼我並不想將太多,意義不大,等會介紹下核心類的接口就算完事了,先介紹界面的設計。
看着win低畫質的界面,頓時覺得,Win7下高畫質的界面好多啦~
首先,主界面中有2個Edit編輯控件,一個List控件,兩個按鈕。在list控件中包含了兩個列,目前僅僅對第一個IPAddr進行了使用。在線設備在list控件中會顯示出來其IP地址。
目前僅做了雙擊顯示出來的在線項彈出聊天對話框的功能,其余尚未添加。而且按下空格和Enter鍵會直接退出程序。這個問題屬於還未處理好的鍵盤消息響應。
(1) 當有數據報到來並且完成接收后如何通知子窗口並且顯示出來?
CLANChat構造函數的參數為一個CDialog的指針。而在主對話框程序中,作為顯示的Dialog對話框派生類包含一個CLANChat的對象。在對話框對象啟動的時候,在其構造函數的參數列表中為CLANChat對象傳入this指針,用於其初始化。這樣對於到來的消息,在未彈出窗口的情況下,就能夠根據對話框窗口的句柄使用postmessage函數將消息派發出去。
同時CLANChat類中的在線列表是一個vector模板,作為其類型參數的是專門設計的OnLineNode類,其中包含了指向字聊天對話框窗口的指針。
1 class OnLineNode 2 { 3 public: 4 //當年一個小小結構體,如今都需要做構造函數了…… 5 OnLineNode(); 6 //結構體和類在添加數據上確實很有優勢~ 7 SOCKET sClient;//I like this name 8 sockaddr_in sa_in;//保存和客戶端的IP信息 9 vector<CString> vec_str_ChatHistory;//在這聊天記錄也做了,全職保姆…… 10 CDialog * pDlg;//指向與自己相綁定的對話框對象。 11 //用於支持STL方法 12 int operator == (const OnLineNode & OLN); 13 inline void AddtoChatHistory(CString & str);//這個函數太簡單了 14 void PostMsg(CString & str); 15 };
這個類維護了很多的信息,包括:指向的socket句柄, 地址信息, 聊天記錄, 聊天對話框指針。其構造函數會對其進行相應的初始化,比如pDlg在剛啟動的時候,其值為NULL.
而且在其雙擊響應的消息函數中,我也專門對其進行了維護,這樣才能對pDlg進行初始化操作。總體上說,這個類的任務還是不少的。
上面講到的消息派發還只是傳給主窗口的,那么什么時候傳給聊天用的窗口呢?。這里在上面的框圖中可以看到在IOCPServer中有消息派發一欄,在這里,會根據pDlg的值,也就是是否窗口已存在來決定這個消息發送給誰。
代碼如下:
1 //這里直接給自己的對話框窗口發消息這還挺復雜的。 2 //當窗口已經建立的時候,應該直接給自己的窗口發消息。 3 //當窗口未建立的時候,應該將消息發送給主程序,然后主程序使相應的item閃爍。 4 if(it_temp->pDlg != NULL)//窗口未建立 5 { 6 //在這只要把和自己相關聯的在線節點地址值穿過去就OK了~ 7 PostMessage(it_temp->pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&(*it_temp), 0); 8 } 9 else//將消息發送主窗口 10 { 11 PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); 12 }
其中的it_temp為臨時的迭代器指針,指向OnLineList的成員。所以在這里,當數據到來且聊天窗口已經創建的情況下,聊天窗口就會收到消息並且對數據進行更行。所以指針是個好東西。
另外關於上面的CListCtrl的初始化以及風格設置,我在前面的博文中專門進行了講解。盡管只是一部分,但是做到上面的效果還是很容易的。
3 CLANChat類的接口
1 class CLANChat 2 { 3 private: 4 //local veriable 5 CDialog * m_pDlgFrame;//指向主體窗口 6 DlgIOCP ** m_pDlgIOCPArray; 7 //socket about 8 static vector<OnLineNode> g_OnLineList;//需要維護的在線列表 9 static volatile HANDLE hIOCP;//IO完成端口句柄 10 static ULONG g_IPaddr;//本地IP地址 11 static SOCKET sListen;//本地SOCKET 12 static sockaddr_in m_local_sa_in;//本地sockaddr 13 private: 14 //Method Lock 15 CLANChat(CLANChat &); 16 CLANChat & operator = (CLANChat &); 17 public: 18 CLANChat(CDialog * pDlg); 19 ~CLANChat(void); 20 void CreateUDPBroadcastThread(LPVOID lpParam); 21 DWORD GetCPUNumber(); 22 int SendInfo(char * pbuf, int length, in_addr nListID); 23 SOCKET GetSocketWithIP(in_addr addr); 24 vector<OnLineNode> * GetOnLineListPtr(); 25 CEvent m_eMainUDPServer; 26 //類線程函數必須是靜態函數,因為靜態函數無this指針 27 static DWORD WINAPI MainServer(LPVOID lpParam); 28 static DWORD WINAPI UDPServer(LPVOID lpParam); 29 static DWORD WINAPI IOCPTCPSever(LPVOID lpParam); 30 };
上面的這個類就是我代碼中最核心的一個類吧,其中的重要數據並不算多,而且隨着代碼更改,有些變量和函數已經變得沒用或者不合適這種生命方式了。其主要原因還是在於,我初次編寫多線程的程序,對其中的部分內容使用的不熟練,改來改去,這代碼我現在自己看着都有點覺得糟心。唉,如果再讓我寫一遍這個程序,我做的第一件事情肯定是先設計,把框圖什么的都先設計好,然后再開始動手寫代碼。現在我覺得我寫這個文檔都有點不知道應該怎么寫了~
這個類最主要的任務就是啟動3中線程:MainServer UDPServer IOCPServer。就這些工作,然后還要負責消息的派送,僅此而已。
在這程序中我最滿意的就是,我比較好的使用了IOCP,這一點我很高興,因為之前在做串口調試助手的時候,曾經在《windows核心編程》這本書中看到過,但是當時沒理解。如今到了Socket下,我用的還不錯,而且算是理解其原理了。
4:CLANChat類的實現:(代碼)
1 #include "stdafx.h" 2 #include "LANChat.h" 3 #include "afxdialogex.h" 4 #include "LANChat20130815.h" 5 6 #include <algorithm> 7 DWORD CLANChat::g_IPaddr = 0; 8 SOCKET CLANChat::sListen = 0; 9 HANDLE volatile CLANChat::hIOCP = 0; 10 sockaddr_in CLANChat::m_local_sa_in; 11 vector<OnLineNode> CLANChat::g_OnLineList; 12 13 using namespace std; 14 CLANChat::CLANChat(CDialog * pDlg):m_pDlgFrame(pDlg) 15 { 16 //線程同步時間初始化 17 m_eMainUDPServer.ResetEvent(); 18 //sockaddr 19 m_local_sa_in.sin_family = AF_INET; 20 m_local_sa_in.sin_port = htons(4567); 21 m_local_sa_in.sin_addr.S_un.S_addr = INADDR_ANY; 22 //這里有必要獲取一下本地的IP地址 23 char szHostName[256]; 24 gethostname(szHostName, 256); 25 hostent * pht = gethostbyname(szHostName); 26 char * pIP = pht->h_addr_list[0];//多網卡設備還暫時無法處理 27 memcpy(&g_IPaddr, pIP, pht->h_length); 28 //IOCP創建以及sever線程啟動 29 //根據核心數來創建線程數 30 int cpunum = GetCPUNumber(); 31 hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, cpunum); 32 m_pDlgIOCPArray = new DlgIOCP * [cpunum]; 33 for(int i = 0;i < cpunum;++ i) 34 { 35 m_pDlgIOCPArray[i] = new DlgIOCP; 36 m_pDlgIOCPArray[i]->hIOCP = hIOCP; 37 m_pDlgIOCPArray[i]->pDlg = pDlg; 38 } 39 //在這里需要額外創建一個主服務線程,因為在console程序中,能夠使用程序的線程,而這里無法使用。 40 CreateThread(0, 0, MainServer, this, 0, 0); 41 //接着創建UDP線程。開始廣播以及准備接受UDP廣播 42 CreateUDPBroadcastThread(this); 43 //創建IOCP線程,並添加到線程池中 44 for(int i = 0;i < cpunum;++ i) 45 { 46 CreateThread(NULL, 0, IOCPTCPSever, m_pDlgIOCPArray[i], 0, 0); 47 } 48 } 49 50 CLANChat::~CLANChat(void) 51 { 52 //清理工作 53 delete [] m_pDlgIOCPArray; 54 } 55 56 void CLANChat::CreateUDPBroadcastThread(LPVOID lpParam) 57 { 58 CreateThread(0, 0, UDPServer, lpParam, 0, 0); 59 } 60 DWORD WINAPI CLANChat::MainServer(LPVOID lpParam) 61 { 62 CLANChat * pCLC = (CLANChat *)lpParam; 63 //建立本地TCP連接 64 SOCKET sListen = socket(AF_INET, SOCK_STREAM, 0); 65 //綁定到一個本地地址 66 CRITICAL_SECTION cs; 67 InitializeCriticalSection(&cs); 68 if(bind(sListen, (sockaddr *)&m_local_sa_in, sizeof(sockaddr)) == -1) 69 { 70 AfxMessageBox(_T("Bind Fail!!!")); 71 return 0; 72 } 73 listen(sListen, 10);//隨便設個數 74 //char pBufferRecv[1024]; 75 //這個只要使用一次,就是最初的那次。 76 pCLC->m_eMainUDPServer.SetEvent(); 77 while(TRUE) 78 { 79 sockaddr_in sa_temp; 80 int nsa_size = sizeof(sockaddr_in); 81 SOCKET snewsocket = accept(sListen, (sockaddr *)&sa_temp, &nsa_size); 82 if(snewsocket == INVALID_SOCKET) 83 { 84 continue; 85 } 86 //在有效的新連接建立后,立刻將其加入到在線列表中 87 OnLineNode tmpnode; 88 tmpnode.sClient = snewsocket; 89 tmpnode.sa_in = sa_temp; 90 EnterCriticalSection(&cs); 91 g_OnLineList.push_back(tmpnode); 92 LeaveCriticalSection(&cs); 93 //在這里使用IO完成端口來做連接監聽 94 //先創建一個key結構 95 PIOCP_KEY pTempKey = (PIOCP_KEY)GlobalAlloc(GPTR, sizeof(IOCP_KEY)); 96 if(pTempKey == NULL) 97 continue; 98 pTempKey->sClient = snewsocket; 99 pTempKey->sa_in = sa_temp; 100 pTempKey->sa_in.sin_port = htons(4567); 101 //將IO完成端口和新創建的套接字連接綁定在一起 102 CreateIoCompletionPort((HANDLE)snewsocket, 103 pCLC->hIOCP, 104 (ULONG_PTR)pTempKey/*這個參數要用來傳遞socket所屬以及目標地址,z暫時NULL*/, 105 0); 106 //投遞一個針對目標IO端口的數據接受請求 107 PMOV pMyOv = (PMOV)GlobalAlloc(GPTR, sizeof(MOV)); 108 memset(pMyOv, 0, sizeof(MOV)); 109 if(pMyOv == NULL) 110 return 0; 111 pMyOv->wbuf.buf = pMyOv->buf; 112 pMyOv->wbuf.len = 1024; 113 pMyOv->OperateType = 0;//read 114 DWORD flag = 0; 115 //send(snewsocket, "HELLO", 7, 0); 116 WSARecv(snewsocket, &pMyOv->wbuf, 1, &pMyOv->nRecvLength, &flag, (LPWSAOVERLAPPED)pMyOv, NULL); 117 //后面這些都是用來將數據添加到在線列表后,向dialogbox發送消息 118 PostMessage(pCLC->m_pDlgFrame->m_hWnd, WM_NEWSOCKET, snewsocket, sa_temp.sin_addr.S_un.S_addr); 119 //pure test 120 send(snewsocket, "0816", 4, 0); 121 } 122 return 0; 123 } 124 125 DWORD WINAPI CLANChat::UDPServer(LPVOID lpParam) 126 { 127 CLANChat * pCLC = (CLANChat *)lpParam; 128 sockaddr_in sa_broadcast, sa_local; 129 //broadcast 130 sa_broadcast.sin_addr.S_un.S_addr = inet_addr("255.255.255.255"); 131 sa_broadcast.sin_family = AF_INET; 132 sa_broadcast.sin_port = htons(4567); 133 //local 134 sa_local.sin_addr.S_un.S_addr = INADDR_ANY; 135 sa_local.sin_family = AF_INET; 136 sa_local.sin_port = htons(4567); 137 //socket 138 SOCKET sUDPBroadcast = socket(AF_INET, SOCK_DGRAM, 0); 139 SOCKET sUDPListen = socket(AF_INET, SOCK_DGRAM, 0); 140 if(bind(sUDPListen, (sockaddr *)&sa_local, sizeof(sockaddr)) == -1) 141 { 142 AfxMessageBox(_T("UDP Bind Fail!")); 143 return 0; 144 } 145 //設置廣播套接字模式為廣播通信 146 BOOL bBroadcast = TRUE; 147 setsockopt(sUDPBroadcast, SOL_SOCKET, SO_BROADCAST, (char *)&bBroadcast, sizeof(BOOL)); 148 //等待MainServer基本完成初始化 149 WaitForSingleObject(pCLC->m_eMainUDPServer.m_hObject, INFINITE); 150 //AfxMessageBox(_T("Get The Event!")); 151 //廣播通信 152 sendto(sUDPBroadcast, "C", 1, 0, (sockaddr *)&sa_broadcast, sizeof(sockaddr)); 153 //發送完廣播數據,這個socket就沒什么用了 154 closesocket(sUDPBroadcast); 155 //數據接受相關變量 156 char BUF[3];//不用很大 157 int nfromlength = sizeof(sockaddr); 158 CRITICAL_SECTION cs; 159 InitializeCriticalSection(&cs); 160 while(TRUE) 161 { 162 //正在等待接收數據 163 //這里以后可以改成WSARecvFrom函數,再用一個專門的線程來盡心數據接受處理,效率會更高。 164 int recvlength = recvfrom(sUDPListen, BUF, 3, 0, (sockaddr *)&sa_broadcast, &nfromlength); 165 //檢測收到的是否為本地IP 166 if(g_IPaddr == sa_broadcast.sin_addr.S_un.S_addr) 167 continue; 168 if(recvlength > 0) 169 { 170 //這一個有效的UDP數據 171 BUF[recvlength] = 0; 172 #ifdef _DEBUG 173 ; 174 #endif 175 //將該sockaddr結構添加到在線vector容器中,並且通知主控制線程將其添加到顯示列表 176 //與其建立TCP連接,這樣就能夠很好的,很及時的實現在線列表的更新 177 //建立一個新的本地TCP套接字 178 SOCKET sNewConnectSock = socket(AF_INET, SOCK_STREAM, 0); 179 sa_broadcast.sin_port = htons(4567); 180 #ifdef _DEBUG 181 ;//cout << "Connecting IP " << inet_ntoa(sa_broadcast.sin_addr) << endl; 182 #endif 183 if(connect(sNewConnectSock, (sockaddr *)&sa_broadcast, sizeof(sockaddr)) == -1) 184 { 185 #ifdef _DEBUG 186 ;//cout << "UDP Thread Connect Fail!" << endl; 187 #endif 188 //別忘了在連接失敗后釋放資源 189 closesocket(sNewConnectSock); 190 continue;//等待下一個廣播消息 191 } 192 //添加到全局列表中 193 OnLineNode tmpOLN; 194 tmpOLN.sClient = sNewConnectSock; 195 tmpOLN.sa_in = sa_broadcast; 196 EnterCriticalSection(&cs); 197 g_OnLineList.push_back(tmpOLN); 198 EnterCriticalSection(&cs); 199 #ifdef _DEBUG 200 ;//cout << "UDP Thread's TCP Connect Success!" << endl; 201 #endif 202 //在這里既需要將其加入IO完成端口,還要投遞一個WSARecv消息 203 PIOCP_KEY ptmpkey = (PIOCP_KEY)GlobalAlloc(GPTR, sizeof(IOCP_KEY)); 204 ptmpkey->sClient = sNewConnectSock; 205 ptmpkey->sa_in = sa_broadcast; 206 ptmpkey->sa_in.sin_port = htons(4567); 207 CreateIoCompletionPort((HANDLE)ptmpkey->sClient, pCLC->hIOCP, (ULONG_PTR)ptmpkey, 0); 208 //投遞WSARecv請求 209 PMOV pMyOv = (PMOV)GlobalAlloc(GPTR, sizeof(MOV)); 210 memset(pMyOv, 0, sizeof(MOV)); 211 pMyOv->wbuf.buf = pMyOv->buf; 212 pMyOv->wbuf.len = 1024; 213 pMyOv->OperateType = 0;//read 214 DWORD flag = 0; 215 WSARecv(ptmpkey->sClient, &pMyOv->wbuf, 1, &pMyOv->nRecvLength, &flag, (LPWSAOVERLAPPED)pMyOv, NULL); 216 //添加完結構后,向主窗口發送一個更新消息,至於發什么數據,暫時還沒想好。 217 PostMessage(pCLC->m_pDlgFrame->m_hWnd, WM_NEWSOCKET, tmpOLN.sClient, tmpOLN.sa_in.sin_addr.S_un.S_addr); 218 //pure test 219 send(ptmpkey->sClient, "0816", 4, 0); 220 } 221 else 222 continue; 223 } 224 return 0; 225 } 226 227 DWORD WINAPI CLANChat::IOCPTCPSever(LPVOID lpParam)//這個參數需要傳入的是IOCP,調用GetQueue……函數時需要使用 228 { 229 DlgIOCP * DI = (DlgIOCP *)lpParam; 230 HANDLE hCompletion = DI->hIOCP; 231 CDialog * pDlg = DI->pDlg; 232 delete DI;//這個要立刻銷毀,之后沒用的。 233 PMOV pMyOv; 234 PIOCP_KEY pComKey; 235 DWORD dwRecvNum; 236 DWORD dwflag; 237 BOOL bOK; 238 CRITICAL_SECTION cs; 239 InitializeCriticalSection(&cs); 240 while(TRUE) 241 { 242 dwRecvNum = 0; 243 dwflag = 0; 244 OnLineNode tmpOLN; 245 bOK = GetQueuedCompletionStatus(hCompletion, &dwRecvNum, (PULONG_PTR)&pComKey, (LPOVERLAPPED *)&pMyOv, INFINITE); 246 //不管是什么消息,都要先獲取其對應於表中的地址 247 EnterCriticalSection(&cs); 248 tmpOLN.sClient = pComKey->sClient; 249 ASSERT(!g_OnLineList.empty()); 250 vector<OnLineNode>::iterator it_temp = find(g_OnLineList.begin(), g_OnLineList.end(), tmpOLN); 251 if(bOK == 0) 252 { 253 #ifdef _DEBUG 254 ;//cout << "GetQueuedCompletionStatus Error!!!" << endl; 255 #endif 256 closesocket(pComKey->sClient); 257 //CString tmpstr; 258 //tmpstr = inet_ntoa(pComKey->sa_in.sin_addr); 259 //AfxMessageBox(tmpstr); 260 //刪除列表中的數據 261 if(it_temp != g_OnLineList.end()) 262 g_OnLineList.erase(it_temp); 263 PostMessage(pDlg->m_hWnd, WM_CLOSESOCKET, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); 264 GlobalFree(pComKey); 265 GlobalFree(pMyOv); 266 LeaveCriticalSection(&cs); 267 continue; 268 } 269 //下面這句的判斷條件是,當收到讀或寫數據后,數據長度卻為0,則表明對方關閉了套接字 270 if(0 == dwRecvNum && (pMyOv->OperateType == 0 || pMyOv->OperateType == 1)) 271 { 272 #ifdef _DEBUG 273 /*cout << "Socket From IP " << 274 inet_ntoa(pComKey->sa_in.sin_addr) 275 << " Has Closed" << endl;*/ 276 #endif 277 //tmpOLN.sClient = pComKey->sClient; 278 closesocket(pComKey->sClient); 279 //刪除列表中的數據 280 //CString tmpstr; 281 //tmpstr = inet_ntoa(pComKey->sa_in.sin_addr); 282 //AfxMessageBox(tmpstr); 283 //vector<OnLineNode>::iterator it_temp = find(g_OnLineList.begin(), g_OnLineList.end(), tmpOLN); 284 if(it_temp != g_OnLineList.end()) 285 g_OnLineList.erase(it_temp); 286 PostMessage(pDlg->m_hWnd, WM_CLOSESOCKET, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); 287 GlobalFree(pComKey); 288 GlobalFree(pMyOv); 289 LeaveCriticalSection(&cs); 290 continue; 291 } 292 ;//cout << "IOCP Server Thread Get A Message!" << endl; 293 CString tmp_str; 294 switch(pMyOv->OperateType) 295 { 296 case 0://讀 297 WSARecv(pComKey->sClient, &pMyOv->wbuf, 1, &dwRecvNum, &dwflag, (LPOVERLAPPED)pMyOv, NULL); 298 //這里的數據必須做成Unicode 299 ((TCHAR *)pMyOv->wbuf.buf)[dwRecvNum] = 0; 300 /*這里的情況是收到了發送到本程序的消息,但是,在在線列表中並不存在 301 這種情況是不合理的。只有在在線列表中的數據才能收到消息。也就是說,肯能是在push_back 302 函數的調用次序上出現了問題。 303 */ 304 if(it_temp == g_OnLineList.end()) 305 { 306 LeaveCriticalSection(&cs); 307 continue; 308 } 309 ;//cout << "Message From" << inet_ntoa(pComKey->sa_in.sin_addr) << endl 310 ;// << "Info :" << pMyOv->wbuf.buf << endl; 311 tmp_str = (TCHAR *)pMyOv->wbuf.buf; 312 tmp_str += "\r\n"; 313 it_temp->vec_str_ChatHistory.push_back(tmp_str); 314 //這里直接給自己的對話框窗口發消息這還挺復雜的。 315 //當窗口已經建立的時候,應該直接給自己的窗口發消息。 316 //當窗口未建立的時候,應該將消息發送給主程序,然后主程序使相應的item閃爍。 317 if(it_temp->pDlg != NULL)//窗口未建立 318 { 319 //在這只要把和自己相關聯的在線節點地址值穿過去就OK了~ 320 PostMessage(it_temp->pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&(*it_temp), 0); 321 } 322 else//將消息發送主窗口 323 { 324 PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr); 325 } 326 break; 327 case 1: 328 break; 329 case 2: 330 break; 331 } 332 LeaveCriticalSection(&cs); 333 } 334 return 0; 335 } 336 337 DWORD CLANChat::GetCPUNumber() 338 { 339 SYSTEM_INFO si; 340 GetSystemInfo(&si); 341 return si.dwNumberOfProcessors; 342 } 343 /* 344 2013 08 15 17:15 345 這個函數用來發送數據,參數1為要傳送的字符串,參數2為字符串長度,3為在線列表中的ID號,這個號最好是用IP地址號。 346 返回值是-1或正常發送的字節數 347 */ 348 int CLANChat::SendInfo(char * pbuf, int length, in_addr nListID) 349 { 350 SOCKET sSend = GetSocketWithIP(nListID); 351 if(sSend == 0)//表明ID是錯的 352 { 353 AfxMessageBox(_T("SendInfo Wrong ID")); 354 return -1; 355 } 356 return send(sSend, pbuf, length, 0); 357 } 358 int OnLineNode::operator == (const OnLineNode & OLN) 359 { 360 if(OLN.sClient == sClient) 361 return 1; 362 else 363 return 0; 364 } 365 void OnLineNode::AddtoChatHistory(CString & str) 366 { 367 vec_str_ChatHistory.push_back(str); 368 } 369 void OnLineNode::PostMsg(CString & str) 370 { 371 if(pDlg != NULL) 372 { 373 PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&str, 0); 374 AddtoChatHistory(str); 375 } 376 } 377 SOCKET CLANChat::GetSocketWithIP(in_addr addr) 378 { 379 int VecSize = g_OnLineList.size(); 380 for(int i = 0;i < VecSize;++ i) 381 { 382 if(g_OnLineList[i].sa_in.sin_addr.S_un.S_addr == addr.S_un.S_addr) 383 return g_OnLineList[i].sClient; 384 } 385 return 0; 386 } 387 388 vector<OnLineNode> * CLANChat::GetOnLineListPtr() 389 { 390 return &g_OnLineList; 391 } 392 393 OnLineNode::OnLineNode() 394 { 395 sClient = 0; 396 pDlg = NULL; 397 } 398 399 400 // CChatDlg dialog 401 402 IMPLEMENT_DYNAMIC(CChatDlg, CDialogEx) 403 404 CChatDlg::CChatDlg(CWnd* pParent /*=NULL*/) 405 : CDialogEx(CChatDlg::IDD, pParent) 406 { 407 408 } 409 410 CChatDlg::~CChatDlg() 411 { 412 } 413 414 void CChatDlg::DoDataExchange(CDataExchange* pDX) 415 { 416 CDialogEx::DoDataExchange(pDX); 417 DDX_Control(pDX, IDC_RECVEDIT, m_RecvEditCtrl); 418 DDX_Control(pDX, IDC_SENDEDIT, m_EditSend); 419 } 420 421 422 BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx) 423 ON_MESSAGE(WM_RECVMESSAGE, &CChatDlg::OnRecvmessage) 424 ON_BN_CLICKED(IDC_BUTTONSEND, &CChatDlg::OnBnClickedButtonsend) 425 END_MESSAGE_MAP() 426 427 428 // CChatDlg message handlers 429 430 //參數1是一個指向和本對話框綁定的節點迭代器 431 afx_msg LRESULT CChatDlg::OnRecvmessage(WPARAM wParam, LPARAM lParam) 432 { 433 //這里主要的操作就是對輸出窗口的操作了。 434 //獲取自己所對應的節點 435 OnLineNode * pNode = (OnLineNode *)wParam; 436 if(!pNode->vec_str_ChatHistory.empty()) 437 { 438 //這里每次只添加最后一個即可,第一次的初始化不是在這里做的。 439 int nLength = m_RecvEditCtrl.SendMessage(WM_GETTEXTLENGTH); 440 m_RecvEditCtrl.SetSel(nLength, nLength); 441 m_RecvEditCtrl.ReplaceSel(pNode->vec_str_ChatHistory[pNode->vec_str_ChatHistory.size() - 1]); 442 } 443 return 0; 444 } 445 446 447 void CChatDlg::OnBnClickedButtonsend() 448 { 449 // TODO: Add your control notification handler code here 450 int nLineNumber = m_EditSend.GetLineCount(); 451 if(nLineNumber == 0) 452 return; 453 //CString cstmp; 454 TCHAR buf[256]; 455 int nTxtLength = 0; 456 memset(buf, 0, sizeof(TCHAR) * 256); 457 for(int i = 0;i < nLineNumber;++ i) 458 { 459 nTxtLength += m_EditSend.GetLine(i, buf + nTxtLength, 256); 460 } 461 buf[nTxtLength] = 0; 462 //AfxMessageBox(cstmp); 463 //第N次感慨萬惡的Unicode 464 send(pOLN->sClient, (char *)buf, nTxtLength * sizeof(TCHAR), 0); 465 //數據發送完之后,還要對本地的數據進行更新,也就是聊天窗口上要進行處理。 466 //主動向聊天記錄列表中添加一個記錄 並且發送消息到本地的接受窗口對顯示數據進行更新。 467 wcscat(buf, _T("\r\n")); 468 pOLN->vec_str_ChatHistory.push_back(buf); 469 int nLength = m_RecvEditCtrl.SendMessage(WM_GETTEXTLENGTH); 470 m_RecvEditCtrl.SetSel(nLength, nLength); 471 m_RecvEditCtrl.ReplaceSel(pOLN->vec_str_ChatHistory[pOLN->vec_str_ChatHistory.size() - 1]); 472 }