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 于南昌