局域網聊天軟件(winsocket)


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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM