您的位置:首页 > 理论基础 > 计算机网络

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

2012-08-22 20:48 148 查看
  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 于南昌
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: