Winsock IO模型之select模型

    之所以称其为select模型是因为它主要是使用select函数来管理I/O的。这个模型的设计源于UNIX系统,目的是允许那些想要避免在套接字调用上阻塞的应用程序有能力管理多个套接字。

    int select(

         int nfds,                                                 // 忽略,仅是为了与Berkeley套接字兼容

         fd_set* readfds,                                  // 指向一个套接字集合,用来检查其可读性

         fd_set* writefds,                                 // 指向一个套接字集合,用来检查其可写性

         fd_set* exceptfds,                              // 指向一个套接字集合,用来检查错误

         const struct timeval* timeout           // 指定此函数等待的最长时间,如果为NULL,则最长时间为无限大

    );

    函数调用成功,返回发生网络事件的所有套接字数量的总和。如果超过了时间限制返回0,失败则返回SOCKET_ERROR。

    typedef struct fd_set {

                  u_int fd_count;                                       // 下面数组的大小

                  SOCKET fd_array[FD_SETSIZE];       // 套接字句柄数组

     } fd_set;

  • FD_ZERO(*set)                            初始化set为空集合。集合在使用前应该总是清空
  • FD_CLR(s, *set)                          从set移除套接字s
  • FD_ISSET(s, *set)                       检查s是不是set的成员,如果是返回TRUE
  • FD_SET(s, *set)                           添加套接字到集合
 
    typedef struct timeval{
                  long tv_sec;                        // 指示等待多少秒
                  long tv_usec;                      // 指示等待多少毫秒
    } timeval;
 
下面给出使用select模式的例子,运行后在4567端口监听,接受客户端连接请求,打印出接收到的数据。(原来单线程也可以管理多个套接字)
//
// initsock.h文件

#include <winsock2.h>
#pragma comment(lib, "WS2_32")	// 链接到WS2_32.lib

class CInitSock		
{
public:
	CInitSock(BYTE minorVer = 2, BYTE majorVer = 2)
	{
		// 初始化WS2_32.dll
		WSADATA wsaData;
		WORD sockVersion = MAKEWORD(minorVer, majorVer);
		if(::WSAStartup(sockVersion, &wsaData) != 0)
		{
			exit(0);
		}
	}
	~CInitSock()
	{	
		::WSACleanup();	
	}
};


//
// select.cpp文件


#include "../common/initsock.h"
#include <stdio.h>

CInitSock theSock;		// 初始化Winsock库
int main()
{
	USHORT nPort = 4567;	// 此服务器监听的端口号

	// 创建监听套节字
	SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);	
	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(nPort);
	sin.sin_addr.S_un.S_addr = INADDR_ANY;
	// 绑定套节字到本地机器
	if(::bind(sListen, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR)
	{
		printf(" Failed bind() \n");
		return -1;
	}
	// 进入监听模式
	::listen(sListen, 5);

		// select模型处理过程
	// 1)初始化一个套节字集合fdSocket,添加监听套节字句柄到这个集合
	fd_set fdSocket;		// 所有可用套节字集合
	FD_ZERO(&fdSocket);
	FD_SET(sListen, &fdSocket);
	while(TRUE)
	{
		// 2)将fdSocket集合的一个拷贝fdRead传递给select函数,
		// 当有事件发生时,select函数移除fdRead集合中没有未决I/O操作的套节字句柄,然后返回。
		fd_set fdRead = fdSocket;
		int nRet = ::select(0, &fdRead, NULL, NULL, NULL);
		if(nRet > 0)
		{
			// 3)通过将原来fdSocket集合与select处理过的fdRead集合比较,
			// 确定都有哪些套节字有未决I/O,并进一步处理这些I/O。
			for(int i=0; i<(int)fdSocket.fd_count; i++)
			{
				if(FD_ISSET(fdSocket.fd_array[i], &fdRead))
				{
					if(fdSocket.fd_array[i] == sListen)		// (1)监听套节字接收到新连接
					{
						if(fdSocket.fd_count < FD_SETSIZE)
						{
							sockaddr_in addrRemote;
							int nAddrLen = sizeof(addrRemote);
							SOCKET sNew = ::accept(sListen, (SOCKADDR*)&addrRemote, &nAddrLen);
							FD_SET(sNew, &fdSocket);
							printf("接收到连接(%s)\n", ::inet_ntoa(addrRemote.sin_addr));
						}
						else
						{
							printf(" Too much connections! \n");
							continue;
						}
					}
					else
					{
						char szText[256];
						int nRecv = ::recv(fdSocket.fd_array[i], szText, strlen(szText), 0);
						if(nRecv > 0)						// (2)可读
						{
							szText[nRecv] = '\0';
							printf("接收到数据:%s \n", szText);
						}
						else								// (3)连接关闭、重启或者中断
						{
							::closesocket(fdSocket.fd_array[i]);
							FD_CLR(fdSocket.fd_array[i], &fdSocket);
						}
					}
				}
			}
		}
		else
		{
			printf(" Failed select() \n");
			break;
		}
	}
	return 0;
}

    使用select的好处是程序能够在单个线程内同时处理多个套接字连接,这避免了阻塞模式下的线程膨胀问题。但是,添加到fd_set结构的套接字数量是有限制的,默认情况下,最大值是FD_SETSIZE,它在winsock2.h文件中定义为64。为了增加套接字数量,应用程序可以将FD_SETSIZE定义为更大的值(这个定义必须在包含winsock2.h之前出现)。不过,自定义的值也不能超过Winsock下层提供者的限制(通常是1024)。
    另外,FD_SETSIZE的值太大的话,服务器性能就会受到影响。例如有1000个套接字,那么在调用select之前就不得不设置这1000个套接字,select返回之后,又必须检查这1000个套接字。
 
参考及引用:《Windows网络与通信程序设计》王艳平 张越 编著
转载请注明!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值