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 hereCFile 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();}