局域网聊天软件(winsocket)

时间:2022-12-26 18:16:05

LANChat工作整理

2013/8/22

程序实现功能:

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

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

核心类的程序框图

局域网聊天软件(winsocket)

我觉得,这个程序中使用的最好的技术,应该就是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方法,所以对于程序的网络代码我并不想将太多,意义不大,等会介绍下核心类的接口就算完事了,先介绍界面的设计。

局域网聊天软件(winsocket)

看着win低画质的界面,顿时觉得,Win7下高画质的界面好多啦~

首先,主界面中有2个Edit编辑控件,一个List控件,两个按钮。在list控件中包含了两个列,目前仅仅对第一个IPAddr进行了使用。在线设备在list控件中会显示出来其IP地址。

目前仅做了双击显示出来的在线项弹出聊天对话框的功能,其余尚未添加。而且按下空格和Enter键会直接退出程序。这个问题属于还未处理好的键盘消息响应。

(1)       当有数据报到来并且完成接收后如何通知子窗口并且显示出来?

CLANChat构造函数的参数为一个CDialog的指针。而在主对话框程序中,作为显示的Dialog对话框派生类包含一个CLANChat的对象。在对话框对象启动的时候,在其构造函数的参数列表中为CLANChat对象传入this指针,用于其初始化。这样对于到来的消息,在未弹出窗口的情况下,就能够根据对话框窗口的句柄使用postmessage函数将消息派发出去。

同时CLANChat类中的在线列表是一个vector模板,作为其类型参数的是专门设计的OnLineNode类,其中包含了指向字聊天对话框窗口的指针。

 class OnLineNode
{
public:
//当年一个小小结构体,如今都需要做构造函数了……
OnLineNode();
//结构体和类在添加数据上确实很有优势~
SOCKET sClient;//I like this name
sockaddr_in sa_in;//保存和客户端的IP信息
vector<CString> vec_str_ChatHistory;//在这聊天记录也做了,全职保姆……
CDialog * pDlg;//指向与自己相绑定的对话框对象。
//用于支持STL方法
int operator == (const OnLineNode & OLN);
inline void AddtoChatHistory(CString & str);//这个函数太简单了
void PostMsg(CString & str);
};

这个类维护了很多的信息,包括:指向的socket句柄, 地址信息, 聊天记录, 聊天对话框指针。其构造函数会对其进行相应的初始化,比如pDlg在刚启动的时候,其值为NULL.

而且在其双击响应的消息函数中,我也专门对其进行了维护,这样才能对pDlg进行初始化操作。总体上说,这个类的任务还是不少的。

上面讲到的消息派发还只是传给主窗口的,那么什么时候传给聊天用的窗口呢?。这里在上面的框图中可以看到在IOCPServer中有消息派发一栏,在这里,会根据pDlg的值,也就是是否窗口已存在来决定这个消息发送给谁。

代码如下:

         //这里直接给自己的对话框窗口发消息这还挺复杂的。
//当窗口已经建立的时候,应该直接给自己的窗口发消息。
//当窗口未建立的时候,应该将消息发送给主程序,然后主程序使相应的item闪烁。
if(it_temp->pDlg != NULL)//窗口未建立
{
//在这只要把和自己相关联的在线节点地址值穿过去就OK了~
PostMessage(it_temp->pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&(*it_temp), );
}
else//将消息发送主窗口
{
PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr);
}

其中的it_temp为临时的迭代器指针,指向OnLineList的成员。所以在这里,当数据到来且聊天窗口已经创建的情况下,聊天窗口就会收到消息并且对数据进行更行。所以指针是个好东西。

另外关于上面的CListCtrl的初始化以及风格设置,我在前面的博文中专门进行了讲解。尽管只是一部分,但是做到上面的效果还是很容易的。

3 CLANChat类的接口


 class CLANChat
{
private:
//local veriable
CDialog * m_pDlgFrame;//指向主体窗口
DlgIOCP ** m_pDlgIOCPArray;
//socket about
static vector<OnLineNode> g_OnLineList;//需要维护的在线列表
static volatile HANDLE hIOCP;//IO完成端口句柄
static ULONG g_IPaddr;//本地IP地址
static SOCKET sListen;//本地SOCKET
static sockaddr_in m_local_sa_in;//本地sockaddr
private:
//Method Lock
CLANChat(CLANChat &);
CLANChat & operator = (CLANChat &);
public:
CLANChat(CDialog * pDlg);
~CLANChat(void);
void CreateUDPBroadcastThread(LPVOID lpParam);
DWORD GetCPUNumber();
int SendInfo(char * pbuf, int length, in_addr nListID);
SOCKET GetSocketWithIP(in_addr addr);
vector<OnLineNode> * GetOnLineListPtr();
CEvent m_eMainUDPServer;
//类线程函数必须是静态函数,因为静态函数无this指针
static DWORD WINAPI MainServer(LPVOID lpParam);
static DWORD WINAPI UDPServer(LPVOID lpParam);
static DWORD WINAPI IOCPTCPSever(LPVOID lpParam);
};

上面的这个类就是我代码中最核心的一个类吧,其中的重要数据并不算多,而且随着代码更改,有些变量和函数已经变得没用或者不合适这种生命方式了。其主要原因还是在于,我初次编写多线程的程序,对其中的部分内容使用的不熟练,改来改去,这代码我现在自己看着都有点觉得糟心。唉,如果再让我写一遍这个程序,我做的第一件事情肯定是先设计,把框图什么的都先设计好,然后再开始动手写代码。现在我觉得我写这个文档都有点不知道应该怎么写了~

这个类最主要的任务就是启动3中线程:MainServer UDPServer IOCPServer。就这些工作,然后还要负责消息的派送,仅此而已。

在这程序中我最满意的就是,我比较好的使用了IOCP,这一点我很高兴,因为之前在做串口调试助手的时候,曾经在《windows核心编程》这本书中看到过,但是当时没理解。如今到了Socket下,我用的还不错,而且算是理解其原理了。

4:CLANChat类的实现:(代码)

 #include "stdafx.h"
#include "LANChat.h"
#include "afxdialogex.h"
#include "LANChat20130815.h" #include <algorithm>
DWORD CLANChat::g_IPaddr = ;
SOCKET CLANChat::sListen = ;
HANDLE volatile CLANChat::hIOCP = ;
sockaddr_in CLANChat::m_local_sa_in;
vector<OnLineNode> CLANChat::g_OnLineList; using namespace std;
CLANChat::CLANChat(CDialog * pDlg):m_pDlgFrame(pDlg)
{
//线程同步时间初始化
m_eMainUDPServer.ResetEvent();
//sockaddr
m_local_sa_in.sin_family = AF_INET;
m_local_sa_in.sin_port = htons();
m_local_sa_in.sin_addr.S_un.S_addr = INADDR_ANY;
//这里有必要获取一下本地的IP地址
char szHostName[];
gethostname(szHostName, );
hostent * pht = gethostbyname(szHostName);
char * pIP = pht->h_addr_list[];//多网卡设备还暂时无法处理
memcpy(&g_IPaddr, pIP, pht->h_length);
//IOCP创建以及sever线程启动
//根据核心数来创建线程数
int cpunum = GetCPUNumber();
hIOCP = CreateIoCompletionPort(INVALID_HANDLE_VALUE, , , cpunum);
m_pDlgIOCPArray = new DlgIOCP * [cpunum];
for(int i = ;i < cpunum;++ i)
{
m_pDlgIOCPArray[i] = new DlgIOCP;
m_pDlgIOCPArray[i]->hIOCP = hIOCP;
m_pDlgIOCPArray[i]->pDlg = pDlg;
}
//在这里需要额外创建一个主服务线程,因为在console程序中,能够使用程序的线程,而这里无法使用。
CreateThread(, , MainServer, this, , );
//接着创建UDP线程。开始广播以及准备接受UDP广播
CreateUDPBroadcastThread(this);
//创建IOCP线程,并添加到线程池中
for(int i = ;i < cpunum;++ i)
{
CreateThread(NULL, , IOCPTCPSever, m_pDlgIOCPArray[i], , );
}
} CLANChat::~CLANChat(void)
{
//清理工作
delete [] m_pDlgIOCPArray;
} void CLANChat::CreateUDPBroadcastThread(LPVOID lpParam)
{
CreateThread(, , UDPServer, lpParam, , );
}
DWORD WINAPI CLANChat::MainServer(LPVOID lpParam)
{
CLANChat * pCLC = (CLANChat *)lpParam;
//建立本地TCP连接
SOCKET sListen = socket(AF_INET, SOCK_STREAM, );
//绑定到一个本地地址
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
if(bind(sListen, (sockaddr *)&m_local_sa_in, sizeof(sockaddr)) == -)
{
AfxMessageBox(_T("Bind Fail!!!"));
return ;
}
listen(sListen, );//随便设个数
//char pBufferRecv[1024];
//这个只要使用一次,就是最初的那次。
pCLC->m_eMainUDPServer.SetEvent();
while(TRUE)
{
sockaddr_in sa_temp;
int nsa_size = sizeof(sockaddr_in);
SOCKET snewsocket = accept(sListen, (sockaddr *)&sa_temp, &nsa_size);
if(snewsocket == INVALID_SOCKET)
{
continue;
}
//在有效的新连接建立后,立刻将其加入到在线列表中
OnLineNode tmpnode;
tmpnode.sClient = snewsocket;
tmpnode.sa_in = sa_temp;
EnterCriticalSection(&cs);
g_OnLineList.push_back(tmpnode);
LeaveCriticalSection(&cs);
//在这里使用IO完成端口来做连接监听
//先创建一个key结构
PIOCP_KEY pTempKey = (PIOCP_KEY)GlobalAlloc(GPTR, sizeof(IOCP_KEY));
if(pTempKey == NULL)
continue;
pTempKey->sClient = snewsocket;
pTempKey->sa_in = sa_temp;
pTempKey->sa_in.sin_port = htons();
//将IO完成端口和新创建的套接字连接绑定在一起
CreateIoCompletionPort((HANDLE)snewsocket,
pCLC->hIOCP,
(ULONG_PTR)pTempKey/*这个参数要用来传递socket所属以及目标地址,z暂时NULL*/,
);
//投递一个针对目标IO端口的数据接受请求
PMOV pMyOv = (PMOV)GlobalAlloc(GPTR, sizeof(MOV));
memset(pMyOv, , sizeof(MOV));
if(pMyOv == NULL)
return ;
pMyOv->wbuf.buf = pMyOv->buf;
pMyOv->wbuf.len = ;
pMyOv->OperateType = ;//read
DWORD flag = ;
//send(snewsocket, "HELLO", 7, 0);
WSARecv(snewsocket, &pMyOv->wbuf, , &pMyOv->nRecvLength, &flag, (LPWSAOVERLAPPED)pMyOv, NULL);
//后面这些都是用来将数据添加到在线列表后,向dialogbox发送消息
PostMessage(pCLC->m_pDlgFrame->m_hWnd, WM_NEWSOCKET, snewsocket, sa_temp.sin_addr.S_un.S_addr);
//pure test
send(snewsocket, "", , );
}
return ;
} DWORD WINAPI CLANChat::UDPServer(LPVOID lpParam)
{
CLANChat * pCLC = (CLANChat *)lpParam;
sockaddr_in sa_broadcast, sa_local;
//broadcast
sa_broadcast.sin_addr.S_un.S_addr = inet_addr("255.255.255.255");
sa_broadcast.sin_family = AF_INET;
sa_broadcast.sin_port = htons();
//local
sa_local.sin_addr.S_un.S_addr = INADDR_ANY;
sa_local.sin_family = AF_INET;
sa_local.sin_port = htons();
//socket
SOCKET sUDPBroadcast = socket(AF_INET, SOCK_DGRAM, );
SOCKET sUDPListen = socket(AF_INET, SOCK_DGRAM, );
if(bind(sUDPListen, (sockaddr *)&sa_local, sizeof(sockaddr)) == -)
{
AfxMessageBox(_T("UDP Bind Fail!"));
return ;
}
//设置广播套接字模式为广播通信
BOOL bBroadcast = TRUE;
setsockopt(sUDPBroadcast, SOL_SOCKET, SO_BROADCAST, (char *)&bBroadcast, sizeof(BOOL));
//等待MainServer基本完成初始化
WaitForSingleObject(pCLC->m_eMainUDPServer.m_hObject, INFINITE);
//AfxMessageBox(_T("Get The Event!"));
//广播通信
sendto(sUDPBroadcast, "C", , , (sockaddr *)&sa_broadcast, sizeof(sockaddr));
//发送完广播数据,这个socket就没什么用了
closesocket(sUDPBroadcast);
//数据接受相关变量
char BUF[];//不用很大
int nfromlength = sizeof(sockaddr);
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
while(TRUE)
{
//正在等待接收数据
//这里以后可以改成WSARecvFrom函数,再用一个专门的线程来尽心数据接受处理,效率会更高。
int recvlength = recvfrom(sUDPListen, BUF, , , (sockaddr *)&sa_broadcast, &nfromlength);
//检测收到的是否为本地IP
if(g_IPaddr == sa_broadcast.sin_addr.S_un.S_addr)
continue;
if(recvlength > )
{
//这一个有效的UDP数据
BUF[recvlength] = ;
#ifdef _DEBUG
;
#endif
//将该sockaddr结构添加到在线vector容器中,并且通知主控制线程将其添加到显示列表
//与其建立TCP连接,这样就能够很好的,很及时的实现在线列表的更新
//建立一个新的本地TCP套接字
SOCKET sNewConnectSock = socket(AF_INET, SOCK_STREAM, );
sa_broadcast.sin_port = htons();
#ifdef _DEBUG
;//cout << "Connecting IP " << inet_ntoa(sa_broadcast.sin_addr) << endl;
#endif
if(connect(sNewConnectSock, (sockaddr *)&sa_broadcast, sizeof(sockaddr)) == -)
{
#ifdef _DEBUG
;//cout << "UDP Thread Connect Fail!" << endl;
#endif
//别忘了在连接失败后释放资源
closesocket(sNewConnectSock);
continue;//等待下一个广播消息
}
//添加到全局列表中
OnLineNode tmpOLN;
tmpOLN.sClient = sNewConnectSock;
tmpOLN.sa_in = sa_broadcast;
EnterCriticalSection(&cs);
g_OnLineList.push_back(tmpOLN);
EnterCriticalSection(&cs);
#ifdef _DEBUG
;//cout << "UDP Thread's TCP Connect Success!" << endl;
#endif
//在这里既需要将其加入IO完成端口,还要投递一个WSARecv消息
PIOCP_KEY ptmpkey = (PIOCP_KEY)GlobalAlloc(GPTR, sizeof(IOCP_KEY));
ptmpkey->sClient = sNewConnectSock;
ptmpkey->sa_in = sa_broadcast;
ptmpkey->sa_in.sin_port = htons();
CreateIoCompletionPort((HANDLE)ptmpkey->sClient, pCLC->hIOCP, (ULONG_PTR)ptmpkey, );
//投递WSARecv请求
PMOV pMyOv = (PMOV)GlobalAlloc(GPTR, sizeof(MOV));
memset(pMyOv, , sizeof(MOV));
pMyOv->wbuf.buf = pMyOv->buf;
pMyOv->wbuf.len = ;
pMyOv->OperateType = ;//read
DWORD flag = ;
WSARecv(ptmpkey->sClient, &pMyOv->wbuf, , &pMyOv->nRecvLength, &flag, (LPWSAOVERLAPPED)pMyOv, NULL);
//添加完结构后,向主窗口发送一个更新消息,至于发什么数据,暂时还没想好。
PostMessage(pCLC->m_pDlgFrame->m_hWnd, WM_NEWSOCKET, tmpOLN.sClient, tmpOLN.sa_in.sin_addr.S_un.S_addr);
//pure test
send(ptmpkey->sClient, "", , );
}
else
continue;
}
return ;
} DWORD WINAPI CLANChat::IOCPTCPSever(LPVOID lpParam)//这个参数需要传入的是IOCP,调用GetQueue……函数时需要使用
{
DlgIOCP * DI = (DlgIOCP *)lpParam;
HANDLE hCompletion = DI->hIOCP;
CDialog * pDlg = DI->pDlg;
delete DI;//这个要立刻销毁,之后没用的。
PMOV pMyOv;
PIOCP_KEY pComKey;
DWORD dwRecvNum;
DWORD dwflag;
BOOL bOK;
CRITICAL_SECTION cs;
InitializeCriticalSection(&cs);
while(TRUE)
{
dwRecvNum = ;
dwflag = ;
OnLineNode tmpOLN;
bOK = GetQueuedCompletionStatus(hCompletion, &dwRecvNum, (PULONG_PTR)&pComKey, (LPOVERLAPPED *)&pMyOv, INFINITE);
//不管是什么消息,都要先获取其对应于表中的地址
EnterCriticalSection(&cs);
tmpOLN.sClient = pComKey->sClient;
ASSERT(!g_OnLineList.empty());
vector<OnLineNode>::iterator it_temp = find(g_OnLineList.begin(), g_OnLineList.end(), tmpOLN);
if(bOK == )
{
#ifdef _DEBUG
;//cout << "GetQueuedCompletionStatus Error!!!" << endl;
#endif
closesocket(pComKey->sClient);
//CString tmpstr;
//tmpstr = inet_ntoa(pComKey->sa_in.sin_addr);
//AfxMessageBox(tmpstr);
//删除列表中的数据
if(it_temp != g_OnLineList.end())
g_OnLineList.erase(it_temp);
PostMessage(pDlg->m_hWnd, WM_CLOSESOCKET, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr);
GlobalFree(pComKey);
GlobalFree(pMyOv);
LeaveCriticalSection(&cs);
continue;
}
//下面这句的判断条件是,当收到读或写数据后,数据长度却为0,则表明对方关闭了套接字
if( == dwRecvNum && (pMyOv->OperateType == || pMyOv->OperateType == ))
{
#ifdef _DEBUG
/*cout << "Socket From IP " <<
inet_ntoa(pComKey->sa_in.sin_addr)
<< " Has Closed" << endl;*/
#endif
//tmpOLN.sClient = pComKey->sClient;
closesocket(pComKey->sClient);
//删除列表中的数据
//CString tmpstr;
//tmpstr = inet_ntoa(pComKey->sa_in.sin_addr);
//AfxMessageBox(tmpstr);
//vector<OnLineNode>::iterator it_temp = find(g_OnLineList.begin(), g_OnLineList.end(), tmpOLN);
if(it_temp != g_OnLineList.end())
g_OnLineList.erase(it_temp);
PostMessage(pDlg->m_hWnd, WM_CLOSESOCKET, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr);
GlobalFree(pComKey);
GlobalFree(pMyOv);
LeaveCriticalSection(&cs);
continue;
}
;//cout << "IOCP Server Thread Get A Message!" << endl;
CString tmp_str;
switch(pMyOv->OperateType)
{
case ://读
WSARecv(pComKey->sClient, &pMyOv->wbuf, , &dwRecvNum, &dwflag, (LPOVERLAPPED)pMyOv, NULL);
//这里的数据必须做成Unicode
((TCHAR *)pMyOv->wbuf.buf)[dwRecvNum] = ;
/*这里的情况是收到了发送到本程序的消息,但是,在在线列表中并不存在
这种情况是不合理的。只有在在线列表中的数据才能收到消息。也就是说,肯能是在push_back
函数的调用次序上出现了问题。
*/
if(it_temp == g_OnLineList.end())
{
LeaveCriticalSection(&cs);
continue;
}
;//cout << "Message From" << inet_ntoa(pComKey->sa_in.sin_addr) << endl
;// << "Info :" << pMyOv->wbuf.buf << endl;
tmp_str = (TCHAR *)pMyOv->wbuf.buf;
tmp_str += "\r\n";
it_temp->vec_str_ChatHistory.push_back(tmp_str);
//这里直接给自己的对话框窗口发消息这还挺复杂的。
//当窗口已经建立的时候,应该直接给自己的窗口发消息。
//当窗口未建立的时候,应该将消息发送给主程序,然后主程序使相应的item闪烁。
if(it_temp->pDlg != NULL)//窗口未建立
{
//在这只要把和自己相关联的在线节点地址值穿过去就OK了~
PostMessage(it_temp->pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&(*it_temp), );
}
else//将消息发送主窗口
{
PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, tmpOLN.sClient, pComKey->sa_in.sin_addr.S_un.S_addr);
}
break;
case :
break;
case :
break;
}
LeaveCriticalSection(&cs);
}
return ;
} DWORD CLANChat::GetCPUNumber()
{
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors;
}
/*
2013 08 15 17:15
这个函数用来发送数据,参数1为要传送的字符串,参数2为字符串长度,3为在线列表中的ID号,这个号最好是用IP地址号。
返回值是-1或正常发送的字节数
*/
int CLANChat::SendInfo(char * pbuf, int length, in_addr nListID)
{
SOCKET sSend = GetSocketWithIP(nListID);
if(sSend == )//表明ID是错的
{
AfxMessageBox(_T("SendInfo Wrong ID"));
return -;
}
return send(sSend, pbuf, length, );
}
int OnLineNode::operator == (const OnLineNode & OLN)
{
if(OLN.sClient == sClient)
return ;
else
return ;
}
void OnLineNode::AddtoChatHistory(CString & str)
{
vec_str_ChatHistory.push_back(str);
}
void OnLineNode::PostMsg(CString & str)
{
if(pDlg != NULL)
{
PostMessage(pDlg->m_hWnd, WM_RECVMESSAGE, (DWORD)&str, );
AddtoChatHistory(str);
}
}
SOCKET CLANChat::GetSocketWithIP(in_addr addr)
{
int VecSize = g_OnLineList.size();
for(int i = ;i < VecSize;++ i)
{
if(g_OnLineList[i].sa_in.sin_addr.S_un.S_addr == addr.S_un.S_addr)
return g_OnLineList[i].sClient;
}
return ;
} vector<OnLineNode> * CLANChat::GetOnLineListPtr()
{
return &g_OnLineList;
} OnLineNode::OnLineNode()
{
sClient = ;
pDlg = NULL;
} // CChatDlg dialog IMPLEMENT_DYNAMIC(CChatDlg, CDialogEx) CChatDlg::CChatDlg(CWnd* pParent /*=NULL*/)
: CDialogEx(CChatDlg::IDD, pParent)
{ } CChatDlg::~CChatDlg()
{
} void CChatDlg::DoDataExchange(CDataExchange* pDX)
{
CDialogEx::DoDataExchange(pDX);
DDX_Control(pDX, IDC_RECVEDIT, m_RecvEditCtrl);
DDX_Control(pDX, IDC_SENDEDIT, m_EditSend);
} BEGIN_MESSAGE_MAP(CChatDlg, CDialogEx)
ON_MESSAGE(WM_RECVMESSAGE, &CChatDlg::OnRecvmessage)
ON_BN_CLICKED(IDC_BUTTONSEND, &CChatDlg::OnBnClickedButtonsend)
END_MESSAGE_MAP() // CChatDlg message handlers //参数1是一个指向和本对话框绑定的节点迭代器
afx_msg LRESULT CChatDlg::OnRecvmessage(WPARAM wParam, LPARAM lParam)
{
//这里主要的操作就是对输出窗口的操作了。
//获取自己所对应的节点
OnLineNode * pNode = (OnLineNode *)wParam;
if(!pNode->vec_str_ChatHistory.empty())
{
//这里每次只添加最后一个即可,第一次的初始化不是在这里做的。
int nLength = m_RecvEditCtrl.SendMessage(WM_GETTEXTLENGTH);
m_RecvEditCtrl.SetSel(nLength, nLength);
m_RecvEditCtrl.ReplaceSel(pNode->vec_str_ChatHistory[pNode->vec_str_ChatHistory.size() - ]);
}
return ;
} void CChatDlg::OnBnClickedButtonsend()
{
// TODO: Add your control notification handler code here
int nLineNumber = m_EditSend.GetLineCount();
if(nLineNumber == )
return;
//CString cstmp;
TCHAR buf[];
int nTxtLength = ;
memset(buf, , sizeof(TCHAR) * );
for(int i = ;i < nLineNumber;++ i)
{
nTxtLength += m_EditSend.GetLine(i, buf + nTxtLength, );
}
buf[nTxtLength] = ;
//AfxMessageBox(cstmp);
//第N次感慨万恶的Unicode
send(pOLN->sClient, (char *)buf, nTxtLength * sizeof(TCHAR), );
//数据发送完之后,还要对本地的数据进行更新,也就是聊天窗口上要进行处理。
//主动向聊天记录列表中添加一个记录 并且发送消息到本地的接受窗口对显示数据进行更新。
wcscat(buf, _T("\r\n"));
pOLN->vec_str_ChatHistory.push_back(buf);
int nLength = m_RecvEditCtrl.SendMessage(WM_GETTEXTLENGTH);
m_RecvEditCtrl.SetSel(nLength, nLength);
m_RecvEditCtrl.ReplaceSel(pOLN->vec_str_ChatHistory[pOLN->vec_str_ChatHistory.size() - ]);
}