【网络编程】之六、选择select

  select模型在五中模型中是最简单,最容易实现的,当然他的效率当然不如其他四种;

select可以去监视一个套接字,看哪个socket有消息到来;

int select(
  _In_     int nfds,//忽略
  _Inout_  fd_set *readfds,//一个用于检测可读性的参数
  _Inout_  fd_set *writefds,//检查可写性
  _Inout_  fd_set *exceptfds,//用于例外数据
  _In_     const struct timeval *timeout//最大等待时间
);

来看一下fd_set:

typedef struct fd_set { 
 u_int fd_count;//指定可放套接字的数目
 SOCKET fd_array[FD_SETSIZE];//一个套接字的数组
} fd_set;
其实fd_set就是一个socket的集合。

其中#define FD_SETSIZE 64

所以fd_set最多可以放64个套接字。

fdset 代表着一系列特定套接字的集合。其中, readfds 集合包括符合下述任何一个条件的套接字:
● 有数据可以读入。
● 连接已经关闭、重设或中止。
● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。

writefds 集合包括符合下述任何一个条件的套接字:
● 有数据可以发出。
● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。

exceptfds 集合包括符合下述任何一个条件的套接字:
● 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
● 有带外(Out-of-band,OOB)数据可供读取。


在select函数中,中间三个参数(readfds, writefds, exceptfds)中,你至少有一个不是NULL,不为空了也必须要至少包含一个套接字句柄。如果不满足这些要求,select函数就没有任何东西等待了。

最后一个参数timeout是一个指向timeval结构的指针,

typedef struct timeval {
  long tv_sec;//秒
  long tv_usec;//毫秒
} timeval;
他指定用于select函数等待io操作的最长时间。  如果你传一个空进去,那么select函数调用就会无限的锁定或者停止下去,直到有一个描述符符合指定的条件。

如果你把最后一个参数设置为(0,0),那么就是说select会立即返回,处于性能的考虑,要避免这样的设置。



ok,现在我们的select函数成功返回了,她会在fd_set结构中返回刚好有未完成的io操作的所有套接字句柄的总量。如果超时了,那么就会返回0。 如果失败了那么就返回SOCKET_ERROR,我们可以调用WSAGetLastError函数来获取错误码。

当我们调用select函数之前,我们必须要分配一个fd_set结构集合。然后才可以调用select函数。

ok,winsock提供给我们一些宏,我们来看一下:

sock s;

fd_set set;

FD_CLR(s, *set); 从set中删除套接字s。

FD_ISSET(s, *set);检查s是否在set中,如果在就返回TRUE,否则就是FALSE;

FD_SET(s, *set);将套接字s放入结合set中。

FDZERO(*set);将set初始化为空集合。



最后看实例之前,我们先来看一下select的使用步骤:

1、使用FDZERO初始化一个fd_set对象。

2、调用FD_SET,把套接字加入到fd_set集合中。

3、调用select函数,等待返回。

4、完成后返回所有的fd_set集合中设置的套接字句柄的总数,对每个集合进行了相应的更新。

5、根据select函数的返回值,联合FD_ISSET对fd_set进行检查。

6、知道了集合中待解决的IO操作后,对IO进行处理。  返回1,继续进行select的调用处理。


贴上服务器端代码:

/**************************************************
文件名server.cpp
windows下socket网络编程实例  -- 服务器端基于TCP
服务器地址:'127.0.0.1'
端口号 8888
作者:peter
***************************************************/
#include<WinSock2.h>
#include<stdio.h>
#pragma comment(lib,"WS2_32.lib")

bool select_server(SOCKET sock, int nTime = 100, bool bRead = true);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	WORD sockVersion = MAKEWORD(2,0);//指定版本号
	::WSAStartup(sockVersion, &wsaData);//载入winsock的dll
	//创建套接字基于TCP
	SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if(s == INVALID_SOCKET)
	{
		printf("error");
		::WSACleanup();//清理,释放资源
		return 0;
	}

	sockaddr_in sin;
	sin.sin_family = AF_INET;
	sin.sin_port = htons(8888);//端口号8888
	sin.sin_addr.S_un.S_addr = INADDR_ANY;//地址全是0,也就是所有的地址
	//绑定socket
	if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR)
	{
		printf("error");
		::WSACleanup();//清理释放资源
		return 0;
	}
	//监听socket
	if(::listen(s, 2) == SOCKET_ERROR)
	{
		printf("error");
		::WSACleanup();//释放资源
		return 0;
	}

	sockaddr_in remoteAddr;
	int nAddrLen = sizeof(remoteAddr);
	SOCKET client;
	char szText[] = "peter\n";//缓冲区数据
	char szBuf[1024] = {0};

	while(1)
	{
		fd_set fdset;
		FD_ZERO(&fdset);
		FD_SET(s, &fdset);
		timeval tv;
		tv.tv_sec  = 3;
		tv.tv_usec = 0;
		int n;
		if (n = select(0, &fdset,NULL, NULL, &tv) == SOCKET_ERROR)
		{
			continue;
		}
		if(FD_ISSET(s,&fdset))
		{
			/*accept服务器端使用,调用函数进入阻塞状态,等待用户连接,如果没有客户端进行连接,程序就在这个地方。
				不会看到后面。如果有客户端连接,那么程序就执行一次然后继续循环到这里等待。*/
			client = ::accept(s, (SOCKADDR*)&remoteAddr, &nAddrLen);
			if(client == INVALID_SOCKET)
			{
				printf("error");
				continue;
			}

			printf("接收到一个连接:%s\r\n",inet_ntoa(remoteAddr.sin_addr));

			::send(client, szText, strlen(szText), 0); //发送数据

			int re = ::recv(client, szBuf, 1024, 0);
			if(re > 0)
			{
				szBuf[re] = '\n';
				printf(szBuf);
			}
		
			::closesocket(client);//关闭套接字
		}
	}

	::closesocket(s);
	::WSACleanup();
	return 0;
}

客户端见前面的【网络编程】之四、socket网络编程例解  ;

稍后会写一个简易的聊天程序,将源码贴出,稍后!




2012/8/22

jofranks 于南昌

原文地址:https://www.cnblogs.com/java20130723/p/3211407.html