CAsyncSocket是MFC中对WSAAsyncSelect异步非阻塞通知IO的1个封装类。我们在《Windows下使用WSAAsyncSelect实现窗口处理socket消息》1文中讨论过WSAAsyncSelect的用法,知道它绑定1个窗口到1个socket,并注册了我们自定义的消息和需要监视的IO事件类型(FD_ACCEPT、FD_READ、FD_WRITE等等)。当绑定的socket产生注册的IO事件后,操作系统会给上述窗口发送我们自定义的消息,而接下来我们就能够针对事件类型而做不同的处理。CAsyncSocket就是将WSAAsyncSelect进行了封装,内部使用了1个不可见的窗口并隐藏了注册绑定进程,其内部实现原理和WSAAsyncSelect的使用相同。
下面主要讨论下CAsyncSocket的用法,我们要实现的效果是,创建1个都带界面的服务端和客户端。客户端允许用户输入字符串,然后发送给服务端端,服务端接收客户真个字符串后原样返回,在客户端界面上显示。
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创建1个异步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中,我们定义1个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了1个客户端socket的指针:
CMySocket* pClntSock=new CMySocket();
在这里我们不可以将客户真个socket声明为局部变量,由于CAsyncSocket对象离开作用域中会调用析构函数进行析构。如果我们这里在栈中创建1个clntSock而非new1个pClntSock,在OnMysocket调用结束后,已连接的客户端socket会自动断开连接,后续将没法进行send和receive等操作。
在处理ACCEPT消息时,我们调用了Accept函数,这里和普通的accept函数类似,我们获得到了连接到服务真个pClntSock,接下来将窗口的this指针赋值给了pClntSock的m_pWnd,这点很重要,由于我们在调用pservSock->Send(buf,strLen,0)进行数据的发送时,Send函数内部实际上会调用pClntSock的OnSend回调函数,我们需要在这个回调函数中向pClntSock的m_pWnd发送自定义消息。
在客户端中,我们也要添加1个自定义消息的处理函数:
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⑴,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 git@github.com:HymanLiuTS/NetDevelopment.git
获得本文源代码:
git checkout NL57