CAsyncSocket是MFC中对WSAAsyncSelect异步非阻塞通知IO的一个封装类。我们在《Windows下使用WSAAsyncSelect实现窗口处理socket消息》一文中讨论过WSAAsyncSelect的用法,知道它绑定一个窗口到一个socket,并注册了我们自定义的消息和需要监视的IO事件类型(FD_ACCEPT、FD_READ、FD_WRITE等等)。当绑定的socket发生注册的IO事件后,操作系统会给上述窗口发送我们自定义的消息,而接下来我们就可以针对事件类型而做不同的处理。CAsyncSocket就是将WSAAsyncSelect进行了封装,内部使用了一个不可见的窗口并隐藏了注册绑定过程,其内部实现原理和WSAAsyncSelect的使用相同。
下面主要讨论下CAsyncSocket的用法,我们要实现的效果是,创建一个都带界面的服务端和客户端。客户端允许用户输入字符串,然后发送给服务端端,服务端接收客户端的字符串后原样返回,在客户端界面上显示。
CAsyncSocket主要包含以下成员:
BOOL Create( UINT nSocketPort = 0, int nSocketType = SOCK_STREAM, long lEvent = FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, LPCTSTR lpszSocketAddress = NULL );
我们用Create创建一个异步socket对象,在Create的参数中我们可以指定端口号、协议类型、注册的事件类型、IP地址。如果你不想传参,Create提供了默认形参;如果你想要修改,可以使用CAsyncSocket提供的Bind、AsyncSelect等接口进行修改。
BOOL Listen( int nConnectionBacklog = 5 );
用来开启监听。
virtual BOOL Accept( CAsyncSocket& rConnectedSocket, SOCKADDR* lpSockAddr = NULL, int* lpSockAddrLen = NULL );
用来接收客户端连接
BOOL Connect( LPCTSTR lpszHostAddress, UINT nHostPort );
用来连接服务端。
virtual void OnAccept( int nErrorCode );
virtual void OnClose ( int nErrorCode );
virtual void OnConnect ( int nErrorCode );
virtual void OnReceive ( int nErrorCode );
virtual void OnSend ( int nErrorCode );
上述函数都是系统的回调函数,在socket发生相应的IO事件时进行调用。它们都被定义成了虚函数,需要我们进行继承,并进行相应的处理。
在使用CAsyncSocket时我们需要定义自己的CAsyncSocket派生类,在派生类中我们通过继承虚函数的形式可以自由的处理各类socket的io事件,我们定义的类名称叫做CMySocket:
class CMySocket : public CAsyncSocket
{
....
}
在CMySocket中,我们定义一个CWnd*类型的成员变量用来接收服务端和客户端窗口的指针:
CWnd* m_pWnd;
我们重载OnAccept等回调函数,在每个函数中向m_pWnd发送自定义的消息,这样我们在服务端和客户端的窗口处理函数中就可以处理这些消息。
void CMySocket::OnAccept(int nErrorCode)
{
int param=ACCEPT;
if(m_pWnd!=NULL)
m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
CAsyncSocket::OnAccept(nErrorCode);
}
void CMySocket::OnReceive(int nErrorCode)
{
int param=RECIEVE;
if(m_pWnd!=NULL)
m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
CAsyncSocket::OnReceive(nErrorCode);
}
void CMySocket::OnClose(int nErrorCode)
{
int param=CLOSE;
if(m_pWnd!=NULL)
m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
CAsyncSocket::OnClose(nErrorCode);
}
void CMySocket::OnConnect(int nErrorCode)
{
int param=CONNECT;
if(m_pWnd!=NULL)
m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
CAsyncSocket::OnConnect(nErrorCode);
}
void CMySocket::OnSend(int nErrorCode)
{
int param=SEND;
if(m_pWnd!=NULL)
m_pWnd->SendMessage(WM_MYSOCKET,(WPARAM)this,(LPARAM)¶m);
CAsyncSocket::OnSend(nErrorCode);
}
可以看到,在上述每个虚函数中,我们都调用m_pWnd的SendMessage函数向系统的消息队列中发送了自定义的WM_MYSOCKET消息。并通过WPARAM和LPARAM参数把当前的CMySocket对象和代表事件类型的宏附加到消息的参数中。
在服务端我们自定义WM_MYSOCKET的消息处理函数如下:
afx_msg LRESULT CServDlg::OnMysocket(WPARAM wParam, LPARAM lParam)
{
CMySocket* pservSock=(CMySocket*)wParam;
CMySocket* pClntSock=new CMySocket();
SOCKADDR_IN clntAddr;
int clntAddrSz=sizeof(clntAddr);
int param=*((int*)(lParam));
int recvLen;
char buf[BUF_SIZE];
switch(param)
{
case ACCEPT:
{
pservSock->Accept(*pClntSock,(SOCKADDR*)&clntAddr,&clntAddrSz);
pClntSock->m_pWnd=this;
m_pClnts.AddTail(pClntSock);
}
break;
case RECIEVE:
{
int strLen =pservSock->Receive(buf,BUF_SIZE,0);
pservSock->Send(buf,strLen,0);
}
break;
default:
break;
}
return 0;
}
在上述消息处理函数中我们new了一个客户端socket的指针:
CMySocket* pClntSock=new CMySocket();
在这里我们不可以将客户端的socket声明为局部变量,因为CAsyncSocket对象离开作用域中会调用析构函数进行析构。如果我们这里在栈中创建一个clntSock而非new一个pClntSock,在OnMysocket调用结束后,已经连接的客户端socket会自动断开连接,后续将无法进行send和receive等操作。
在处理ACCEPT消息时,我们调用了Accept函数,这里和普通的accept函数类似,我们获取到了连接到服务端的pClntSock,接下来将窗口的this指针赋值给了pClntSock的m_pWnd,这点很重要,因为我们在调用pservSock->Send(buf,strLen,0)进行数据的发送时,Send函数内部实际上会调用pClntSock的OnSend回调函数,我们需要在这个回调函数中向pClntSock的m_pWnd发送自定义消息。
在客户端中,我们也要添加一个自定义消息的处理函数:
afx_msg LRESULT CClntDlg::OnMysocket(WPARAM wParam, LPARAM lParam)
{
CMySocket* pSock=(CMySocket*)wParam;
int param=*((int*)(lParam));
int recvLen;
char buf[BUF_SIZE];
switch(param)
{
case RECIEVE:
{
int strLen = pSock->Receive(buf,BUF_SIZE-1,0);
buf[strLen]=0;
CString str;
str.Format("%s",buf);
m_recv.SetWindowText(str);
}
break;
default:
break;
}
return 0;
}
上述内容,只是对几处关键性的代码进行了解释,若需要全部的代码,请自行从Github下载,服务端和客户端的运行效果如下:
客户端:
客户端:
Github位置:
https://github.com/HymanLiuTS/NetDevelopment
克隆本项目:
git clone [email protected]:HymanLiuTS/NetDevelopment.git
获取本文源代码:
git checkout NL57