windows 网络编程笔记 winsocket API CSocket CAsyncSocket

这篇博客探讨了MFC中对SOCKET编程的支持,尤其是CAsyncSocket和CSocket类的应用。文章指出,尽管MFC提供了支持,但其文档不清晰导致开发者更倾向于使用winsocket API。文中详细解释了如何在非阻塞和阻塞模式间切换,并通过代码示例展示了CSocket类在收发文件时的行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值