windows下多路复用IO(select,WSAAsyncSelect,WSAEventSelect)

Winsock提供的编程接口中socket默认是阻塞的,比如send,recv,connect,可以通过ioctlsocket进行设置非阻塞,server端要管理多个连接可能不是一件容易的事,windows下提供了不少模型可供使用,比如标题的三个,然后完成端口,libevent等库,此文仅写标题的三个,另外两个单独写。先看MSDN的介绍
MSDN:
The WSAEventSelect function specifies an event object to be associated with the specified set of FD_XXX network events.
The return value is zero if the application's specification of the network events and the associated event object was successful.

The WSAAsyncSelect function requests Windows message-based notification of network events for a socket.
If the WSAAsyncSelect function succeeds, the return value is zero provided the application's declaration of interest in the network event set was successful. 

The select function determines the status of one or more sockets, waiting if necessary, to perform synchronous I/O.
The select function returns the total number of socket handles that are ready and contained in the fd_set structures, zero if the time limit expired, or SOCKET_ERROR if an error occurred

我个人理解是select需要指定一个集合,把所有需要监听的socket添加到这个集合中,再轮询集合,select函数返回后删除没有自己所关心事件的socket,然后对有用的socket进行处理。所以缺点就是如果集合内的socket数过多,则效率会直线下降。WSAAsyncSelect是最简单的一个,但需要创建一个窗口,对于每个socket调用WSAAsyncSelect后即为这个socket绑定了一个消息,当有关心的事件发生就会给这个窗口发送消息,然后WPARAM为消息的句柄,LPARAM通过WSAGETSELECTEVENT与 WSAGETSELECTERROR两个宏定义可以获取到事件以及错误,然后就像处理一般消息一样处理socket的网络消息。WSAEventSelect与WSAAsyncSelect类似,但是它不需要创建窗口,它对事件的绑定是在一个内核对象上,然后使用一个辅助函数来得到内核对象上发生的网络事件,然后就跟WSAAsyncSelect处理一样了。WSAAsyncSelect与WSAEventSelect都会自动将socket设置为非阻塞,select不会自动设置。分别看他们的工作流程

分别记录下每个模型中的注意点:
select:
1.当使用int nFds = select(0, &readSocketFd, &writeSocketFd, NULL, NULL); 当正确调用时nFds为当前有多少个socket上有网络事件,加入一个socket上有两个网络事件,返回值为1,不是2
2.select调用后比如有可读事件,此时readSocketFd中只会保留那个有可读事件的socket,其他项都会被删除。
3.select本身不会把socket设为非阻塞模式,所以还是有可能发生阻塞
WSAAsyncSelect:
1.每次未读完缓冲区的recv()调用,都会重新触发一个FD_READ消息,所以如果需要循环读取的话则需要先关闭对FD_READ的监听
2.FD_WRITE的触发条件(这个应该在其他模型应该也是适用的)

  • 套接字刚建立连接时,表明准备就绪可以立即发送数据。
  • 一次失败的send()调用后缓冲区再次可用时。如果系统缓冲区已经被填满,那么此时调用send()发送数据,将返回SOCKET_ERROR,使用WSAGetLastError()会得到错误码WSAEWOULDBLOCK表明被阻塞。这种情况下当缓冲区重新整理出可用空间后,会向应用程序发送FD_WRITE消息

     所以说如果需要发送消息,直接调用send()发送即可。如果该次调用返回值为SOCKET_ERROR且WSAGetLastError()得到错误码WSAEWOULDBLOCK, 这意味着缓冲区已满暂时无法发送,此刻需要将待发数据保存起来,等到系统发出FD_WRITE消息后尝试重新发送。  

WSAEventSelect
1.通过 WSACreateEvent();创建的对象默认为要手动重置为非信号态

下面是记录下一些socket编程时的一些经验:

  1. 对于非阻塞socket,当发生WSAEWOULDBLOCK之后此时的操作无效,比如发送数据,其实没有发送出去
  2. 对于send函数,有可能想要发送的长度为10k,但实际只发了7k,此时返回值为7k,那剩下的3k还需要自己重新发送,所以可以自己封装一个函数
    bool CClient::SendData(char *data, int len, bool isFile)
    {
    	int totalLen = len;
    	int sendLen = 0;
    	int haveSendLen = 0;
    	while (haveSendLen != len)
    	{
    		//send返回值大于0,则为发送的长度,=0则为关闭了sokcet, 小于0则为异常
    		sendLen = send(m_socket, data + haveSendLen, totalLen, 0);
    		if (sendLen>0)
    		{
    			haveSendLen += sendLen;
    			//此处用于客户端接收不全的情况,比如发送10k,但只接收了3k,则sendLen返回3k
    			totalLen -= sendLen;
    		}
    		else if (sendLen == 0)
    		{
    			//关闭了Socket
    			::closesocket(m_socket);
    		}
    		else
    		{
    			if (WSAGetLastError() == WSAEWOULDBLOCK)
    			{
    				sendLen = 0;
    			}
    			else
    			{
    				AddLog("SendData error");
    				return false;
    			}
    		}
    
    	}
    	return true;
    }
    

     

  3. 缓冲区默认大小为8k,最大可设置64k
  4. 对于文件的分包发送(假设服务端发送给客户端文件),可先发送一个数据包头,里面包含了文件大小等其他文件信息,然后等待客户端返回自己已经收到了这个数据包,然后正式开始发送文件时肯定需要把文件分成很多个小份来发送,可每次发送这个小份前先发送一个类似的数据包给客户端,表明此次的包的大小,客户端收到后确认一个应答包,服务端收到后开始正式发送第一个文件包,客户端不断接收,发现与数据包中给出的长度相同,则发送确认信息给服务端,然后服务端再发送下一个数据包头,表明此次文件大小,客户端再确认收到数据包,等待接收。。。。重复以上动作,直到全部发送完成。
  5. 如果在发送数据的过程中(send()没有完成,还有数据没发送)而调用了closesocket(),以前我们一般采取的措施是"从容关闭"shutdown(s,SD_BOTH),但是数据是肯定丢失了,如何设置让程序满足具体应用的要求(即让没发完的数据发送出去后在关闭socket)?
    struct linger {
        u_short    l_onoff;
        u_short    l_linger;
    };
    linger m_sLinger;
    m_sLinger.l_onoff=1;//(在closesocket()调用,但是还有数据没发送完毕的时候容许逗留)
    // 如果m_sLinger.l_onoff=0;则功能和2.)作用相同;
    m_sLinger.l_linger=5;//(容许逗留的时间为5秒)
    setsockopt(s,SOL_SOCKET,SO_LINGER,(const char*)&m_sLinger,sizeof(linger))
     

上面的一些实例代码:https://download.csdn.net/download/hlw0522/10791026

其中WSAAsyncSelect使用了MFC窗口程序,客户端用的select,实现的文件传输

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值