MFC对SOCKET编程的支持其实是很充分的,然而其文档是语焉不详的。以至于大多数用VC编写的功能稍
复杂的网络程序,还是使用API的。故CAsyncSocket及CSocket事实上成为疑难,群众多敬而远之。余
好事者也,不忍资源浪费,特为之注解。网友关于阻塞、非阻塞等的说法不是很可信。
====================================================
1. MSDN winsocket API 函数说明:
int WSAAsyncSelect(__in SOCKET s,
__in HWND hWnd,
__in unsigned int wMsg,
__in long lEvent
);
The WSAAsyncSelect function is used to request that WS2_32.DLL should send a message to the window hWnd when it detects any network event specified by the lEvent parameter. The message that should be sent is specified by the wMsg parameter. The socket for which notification is required is identified by the s parameter.
The WSAAsyncSelect function automatically sets socket s to nonblocking mode, regardless of the value of lEvent. To set socket s back to blocking mode, it is first necessary to clear the event record associated with socket s via a call to WSAAsyncSelect with lEvent set to zero. You can then call ioctlsocket or WSAIoctl to set the socket back to blocking mode. For more information about how to set the nonblocking socket back to blocking mode, see the ioctlsocket and WSAIoctl functions.
=========================================================
2. windows 网路编程摘录:第七章 WINSOCKET I/O 方法:
就像我们前面提到的那样, Wi n d o w s套接字在两种模式下执行I / O操作:锁定和非锁定。
在锁定模式下,在I / O操作完成前,执行操作的Wi n s o c k函数(比如s e n d和r e c v)会一直等候下去,不会立即返回程序(将控制权交还给程序)。而在非锁定模式下, Wi n s o ck函数无论如何都会立即返回。所以我们必须采取一些适当的步骤,让锁定和非锁定套接字能够满足各种场合的要求。若应用程序针对一个套接字调用了W S A A s y n c S e l e c t,那么套接字的模式会从“锁定”自动变
成“非锁定”,我们在前面已提到过这一点。这样一来,假如调用了像W S A R e c v这样的Wi n s o c kI / O函数,但当时却并没有数据可用,那么必然会造成调用的失败,并返回W S A E W O U L D B L O C K错误。为防止这一点,应用程序应依赖于由W S A A s y n c S e l e c t的u M s g参数指定的用户自定义窗口消息,来判断网络事件类型何时在套接字上发生;而不应盲目地进行调用。
===================================================
3. 关于 CSocket、CAsyncSocket MFC源码 摘录:
分析代码以及实际测试得知,CSocket 默认是非塞调用的,而CSocket::accept函数是阻塞的
代码位于 afxsock.h sockcore.cpp afxsock.inl 等文件
CSocket::CSocket()
{
m_pbBlocking = NULL;
m_nConnectError = -1;
m_nTimeOut = 2000;
}
AFXSOCK_INLINE BOOL CSocket::Create(UINT nSocketPort, int nSocketType, LPCTSTR lpszSocketAddress)
{
return CAsyncSocket::Create(nSocketPort, nSocketType, FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE, lpszSocketAddress); }
BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,
long lEvent, LPCTSTR lpszSocketAddress)
{
if (Socket(nSocketType, lEvent))
{
if (Bind(nSocketPort,lpszSocketAddress))
return TRUE;
int nResult = GetLastError();
Close();
WSASetLastError(nResult);
}
return FALSE;
}
BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,
int nProtocolType, int nAddressFormat)
{
ASSERT(m_hSocket == INVALID_SOCKET);
m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);
if (m_hSocket != INVALID_SOCKET)
{
CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);
return AsyncSelect(lEvent);
}
return FALSE;
}
BOOL CAsyncSocket::AsyncSelect(long lEvent)
{ ASSERT(m_hSocket != INVALID_SOCKET);
_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
ASSERT(pState->m_hSocketWindow != NULL);
// WSAAsyncSelect
return WSAAsyncSelect(m_hSocket, pState->m_hSocketWindow,WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;}
3.测试代码 CSocket 收发文件:
1.发送文件
void CSendFileDlg::OnButtonSendfile()
{
// TODO: Add your control notification handler code here
CSocket sockTemp;
sockTemp.Create(7803); //端口为7803,任意的
AfxMessageBox("开始调用 Listen");
sockTemp.Listen(1);//只接受一个连接
AfxMessageBox("Listen函数返回");
CSocket sockSend;
AfxMessageBox("开始调用用 Accept)");//经测试,CSocket::Accept是阻塞函数
sockTemp.Accept(sockSend);//注意,sockTemp已交了自己的指针地址到sockSend,故不用Close
AfxMessageBox(" Accept返回)");
CFile file;
if( !file.Open("D:\\movie.mkv", CFile::modeRead) )
{
AfxMessageBox("打开D:\\Test文件出错!");
sockSend.Close();
return;
}
int nBufSize = 1024 * 5; //默认为拔号,5K。当为ADSL时,可将此值改大为约等于下载K数,如300K
int nSize = nBufSize;
LPBYTE pBuf = new BYTE[nBufSize];
DWORD dwTemp = 0;
BOOL bTest = sockSend.AsyncSelect(0);//由于CSocket实际是异步,将它变为同步(阻塞)方式。
sockSend.IOCtl( FIONBIO, &dwTemp);//用IOCtl要将AsyncSelect的第一个参数为0,参看MSDN
UINT uLength = file.GetLength();
sockSend.Send(&uLength, 4);//传送文件大小到接收方(Client端)
int nNumByte;
UINT uTotal = 0;
while(uTotal < uLength)
{
if(uLength - uTotal < nBufSize)
nSize = uLength - uTotal;//当小于缓冲区nTEST时的处理
file.Read(pBuf , nSize);
nNumByte = sockSend.Send(pBuf, nSize);//注意nNumByte为实际的发送字节数,不要以nSize为准
if(nNumByte == SOCKET_ERROR)
{
AfxMessageBox("发送错误!");
goto LabelExit;
}
uTotal += nNumByte;
}
AfxMessageBox("发送文件成功!");
LabelExit:
delete[] pBuf;
file.Close();
sockSend.Close();
}
3.2接收文件:
void CRecvFileDlg::OnButtonRecvfile()
{
// TODO: Add your control notification handler code here
CFile file;
file.Open("C:\\copymovie.mkv", CFile::modeCreate | CFile::modeWrite);
CSocket sockRecv;
sockRecv.Create();
if(!sockRecv.Connect("127.0.0.1", 7803))//接收方地址,若上网,可改为实际IP地址,端口要跟Server端相同。
{ //Connect函数马上返回,不会阻塞
AfxMessageBox("链接失败");
return;
}
DWORD dwTemp = 0;
sockRecv.AsyncSelect(0);
sockRecv.IOCtl( FIONBIO, &dwTemp);//变为阻塞方式
UINT uLength;
sockRecv.Receive(&uLength, 4);//接收发方(Server端)的文件大小
int nBufSize = 1024 * 5;//用默认的拔号上网下载速度为准 5K,如果用ADSL,可改为300K,或按你实际的IE DownLoad值
int nSize = nBufSize;
LPBYTE pBuf = new BYTE[nBufSize];
int nNumByte;
UINT uTotal = 0;
while(uTotal < uLength)
{
if(uLength - uTotal < nBufSize)
nSize = uLength - uTotal;
nNumByte = sockRecv.Receive(pBuf, nSize);
if(nNumByte == SOCKET_ERROR)
{
AfxMessageBox("接收错误!");
goto LabelExit;
}
file.Write(pBuf, nNumByte);
uTotal += nNumByte;//以实际接收字节为准
}
AfxMessageBox("接收文件成功!");
LabelExit:
delete[] pBuf;
file.Close();
sockRecv.Close();
}