• 局域网聊天软件(winsocket)


    局域网聊天软件(winsocket)

    LANChat工作整理

    2013/8/22

    程序实现功能:

             局域网聊天软件,启动即可找到在线设备,并能够进行简单的文字聊天。

             其实下面这个框图已经说明了程序的绝大部分功能原理。

    核心类的程序框图

    我觉得,这个程序中使用的最好的技术,应该就是IOCP了。后面我会针对IOCP好好地写一篇博文,这个技术虽然刚学的时候有点乱,但是确实很好用。

             上面的框图中中间的UDPServer线程等待的事件完成是MainServer线程在Listen函数调用结束后设置的事件。这里忘了标了。

    说明

             前几天在实验室看《Windows网络与通信程序设计》这本书,看完了前5章吧,就觉得目前手头的技术去做一个局域网聊天软件应该差不多了。虽然还有很多细节性的东西还不是非常懂,但是做一个小规模的软件,我自认为是问题不大的。就这样,在大约4天之后,也就是这个月的18号,这款LANChat程序诞生~

             首先我声明:因为我第一次写网络相关的程序,所以肯定存在疏忽,在相关方面肯定是存在不少bug的,另外我在测试的时候也经常遇到程序启动失败的情况,而且原因尚未查明。所以我并不保证这款程序的稳定性和安全性。(作为这个程序的设计人员真是感到汗颜~以后会好的)

       另外代码中大部分析构函数形同虚设,毕竟最初实现的时候尚不清楚能够实现与其功能,所以根本就没顾忌资源释放这一块。比如,聊天窗口建立这一块我就没使用delete。

       多线程部分因为涉及到对数据的访问问题,所以我使用了关键段:CriticalSection结构以及相关函数。

       因为这个文档是在寝室写的,所以没有在线设备,也无法打开聊天窗口,在实验室三台计算机之间使用没问题。

       另外winsock2初始化是在工程中选择的,在如console类的程序中使用socket程序前一定要做好相关的初始化以及库,文件包含工作。

       socket使用程序初次尝试。

    程序实现基本思想

             首先用UDP广播通知在线设备,在本地IP对应的位置有设备上线。局域网内的在线设备收到UDP广播消息后,首先会做的事情是,获取目标的IP地址,然后初始化一个sockaddr结构,向其发送TCP连接请求。

             刚上线的设备此时会已经做好接受准备(这里使用的事件线程同步),在TCP连接请求建立后,会将其加入到IOCP中,并且为其投递一个数据接受的消息。之后任何到来的消息都会由IOCP的线程函数专门进行处理,十分的方便。而IOCP线程函数是通过调用GetQueueCompletionStatues函数将线程添加到IOCP的线程池中的。

             这样任何一台刚上限的设备都能够找到局域网内的其他在线设备并与其简历TCP链接,为局域网聊天软件的实现奠定了理论基础。

    界面实现

           因为这个程序其实并不设计什么算法性的代码,连数据查找我用的貌似也是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 }
    复制代码
     
     
    分类: C/C++
  • 相关阅读:
    Django 常见问题
    post和get的区别
    Django 基础学习笔记二
    Django 中的分页器
    Python 微服务框架 Nameko 微服务通信(RabbitMQ)
    《大数据白皮书 2020.12》解读
    练习Div+Css
    利用JAVAScript调用WebService
    统计在线人数和历史访问人数
    自己写的一个DBHelper
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3276497.html
Copyright © 2020-2023  润新知