局域网聊天软件(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 += " "; 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(" ")); 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 }