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

C++网络编程之select

2017-10-27 19:09 225 查看


C++网络编程之select

select函数决定一个或者多个套接字(socket)的状态,如果需要的话,等待执行异步I/O。
int select(
              __in        int    nfds,
              __inout    fd_set *readfds,
              __inout  fd_set *writefds,
              __inout  fd_set *exceptfds,
              __int       const struct timeval *timeout
              );
这是在Windows系统:
参数
 nfds:忽略。
 readnfds: 指向检查可读性的套接字集合的可选的指针。
 writefds: 指向检查可写性的套接字集合的可选的指针。
 exceptfds: 指向检查错误的套接字集合的可选的指针。
 timeout: select函数需要等待的最长时间,需要以TIMEVAL结构体格式提供此参数,对于阻塞操作,此参数为null。

这是在linux系统:

Select的函数格式:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval*timeout); 

先说明两个结构体: 
第一,struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(filedescriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合FD_ZERO(fd_set *),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set*),将一个给定的文件描述符从集合中删除FD_CLR(int,fd_set*),检查集合中指定的文件描述符是否可以读写FD_ISSET(int
,fd_set* )。

第二,struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。 

具体解释select的参数: 

int maxfdp是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。 

fd_set * readfds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的读变化的,即我们关心是否可以从这些文件中读取数据了,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。 

fd_set * writefds是指向fd_set结构的指针,这个集合中应该包括文件描述符,我们是要监视这些文件描述符的写变化的,即我们关心是否可以向这些文件中写入数据了,如果这个集合中有一个文件可写,select就会返回一个大于0的值,表示有文件可写,如果没有可写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的写变化。 

fd_set * errorfds同上面两个参数的意图,用来监视文件错误异常。 

struct timeval * timeout是select的超时时间,这个参数至关重要,它可以使select处于三种状态,第一,若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;第二,若将时间值设为0秒0毫秒,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值;第三,timeout的值大于0,这就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,返回值同上述。 

返回值:返回状态发生变化的描述符总数。 

负值:select错误

正值:某些文件可读写或出错

0:等待超时,没有可读写或错误的文件

 
返回值
       select函数返回那些准备好并且包含在fd_set结构体的套接字的总数,如果超时,则返回0;如果错误发生,返回SOCKET_ERROR。如果返回值为SOCKET_ERROR,可以通过WSAGetLastError函数检索指定的错误码。
错误码
解释
WSANOTINITIALISTED
在使用此函数之前,WSAStartup函数必须成功的执行
WSAEFALUT
套接字执行时不能分配需要的资源或者readfds、writefds、exceptfds、timeval参数不是用户地址空间的一部分。
WSAENETDOWN
网络子系统失败
WSAEINVAL
超时值不合法的,或者其他的三个参数为空。
WSAEINTR
阻塞的套接字1.1调用通过WSACancelBlockingCall取消
WSAEINPROGRESS
阻塞的套接字1.1调用正在处理或者服务提供者正在处理一个掉用户函数。
WSAENOTSOCK
描述集中包括一个不是套接字的入口。
说明
       select函数用于决定一个或者多个套接字的状态。对于每一个套接字,调用者可以请求读、写或者错误状态信息。一个请求给定状态的套接字集由fd_set结构体指定。在fd_set结构体中的套接字必须和单个服务提供者联系在一起。基于此,如果WSAPROTOCOL_INFO结构体中有相同的providerId值,套接字被认为来自同一个服务提供者。直到返回,结构体更新去反映满足指定条件套接字子集。select函数返回满足条件的套接字个数。fd_set集合可以通过一些宏手动操作。这些宏也适合伯克利套接字,但是它们的机理是根本不同的。
       参数readfds指示检查套接字的可读性。当套接字在listen状态,如果已经接收一个连接请求,这个套接字会被标记为可读,例如一个accept会确保不会阻塞的完成。对于其他的套接字,可读性意味着队列中的数据适合读,当调用recv,WSARecv,WSARecvFrom或者recvfrom后不会阻塞。
       对于面向连接的套接字,可读性也可以指示关闭套接字的从另一端接收的请求。如果虚电路正常关闭,并且所有的数据都已经接收,然后recv会立刻返回(没有数据接收),如果虚电路重置,recv会立刻返回错误码,例如WSAECONNRESET。如果套接字选项SO_OOBINLINE置位(参见setsockop),出现的OOB数据将会被检查。
      参数writefds指示检查套接字的可写性。如果套接字处理connect调用(非阻塞的),并且完全建立连接,这时套接字是可写。如果套接字没有处理connect调用,可写性意味着担保send,sendto或者WSASendto执行成功。但是,如果len参数超过系统的缓存空间大小,它们在阻塞套接字中是可以阻塞的。不确定多长的长度是合法的,尤其在多线程环境下。
      参数exceptfds指示套接字被检查OOB数据出现或者异常错误环境。
      注意:OOB数据仅仅应用当SO_OOBINLINE设置为FALSE的情况下。如果一个套接字处理连接调用(非阻塞模式),试图连接的错误信息在exceptfds中,这个文档并没有定义那些错误需要包含其中。
      readfd,writefds或者exceptfds中任何两个参数在调用的时候需要为null。至少一个必须为非空,并且任何一个非空描述设置必须包括至少一个套接字句柄。
      总之,一个套接字将会被指定在一个特殊的集合当select返回如果:
readfds:
①     如果listen函数已经调用并且连接挂起,accept会执行成功。
②     数据适合读(如果SO_OOBINLINE置位,包括OOB数据)
③     连接被关/重置/终止
writefds:
①     如果处理一个connect调用(非阻塞),连接成功。
②     数据可以发送。
exceptfds:
①     如果处理一个connect调用(非阻塞),连接失败。
②     OOB数据适合读(仅当SO_OOBINLINE未置位)
 
在头文件Winsock2.h中定义四个宏来操作和检查描述集。FD_SETSIZE决定在描述集合中最大数量(FD_SETSIZE的默认值为64,此值可以在导入Winsock2.h之前通过FD_SETSIZE修改)。
使用这些宏是为了在不同的套接字环境中维护软件便利。这些宏操作和检查fd_set内容为:
FD_CLR(s, *set)
         从set集合中移除描述符s
FD_ISSET(s, *set)
         如果s在set中,返回非0,否则返回0
FD_SET(s, *set)
         增加描述符s到set中
FD_ZERO(*set)
         初始化set集合为null集合

#include<WinSock2.h>
#include<stdio.h>
#include<Windows.h>
#include<string>
#include<iostream>
#include<thread>
#include<exception>
#include<future>
#include<vector>
using namespace std;
#pragma comment(lib,"WS2_32.lib")//显示连接套接字库
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define SIZE 5

FILE * ffp;
struct fd_set rfds;
struct sockaddr_in ipadd;
struct timeval timeout = { 0, 200 };
char * readbuff[10] = { 0 };

string ss;
vector<SOCKET> v;
char sztext[1024] = { 0 };
char sztext1[1024] = { 0 };
SOCKET s;
int n;
sockaddr_in addr, addr2;
int ret;

void Close()
{
::closesocket(s);
if (!v.empty())
{
v.clear();
}
::WSACleanup();
}

void Initialize()
{
WSADATA data;
WORD w = MAKEWORD(2, 0);//版本号
//strcpy(sztext, lastSend.c_str());
::WSAStartup(w, &data); //动态链接库初始化
s = ::socket(AF_INET, SOCK_STREAM, 0);
n = sizeof(addr2);
addr.sin_family = AF_INET;
addr.sin_port = htons(75);
addr.sin_addr.S_un.S_addr = INADDR_ANY;
::bind(s, (sockaddr*)&addr, sizeof(addr));
::listen(s, SIZE);
printf("服务器已经启动\n");
}

void MyRecv()
{
try{
while (true)
{
for (int i = 0; i < v.size(); ++i)
{
FD_ZERO(&rfds);  /* 清空集合 */
FD_SET(v.at(i), &rfds);  /* 将fp添加到集合,后面的FD_ISSET和FD_SET没有必然关系,这里是添加检测 */

switch (select(0, &rfds, NULL, NULL, &timeout)) //select使用
{
case -1:
v.erase(v.begin() + i);
printf("客服端断开\n");
break; //select错误退出程序
case 0:
continue; //再次轮询
default:
if (FD_ISSET(v.at(i), &rfds)) //测试sock是否可读即是否网络上有数据
{
if (::recv(v.at(i), sztext1, sizeof(sztext1), 0) != -1)
{
printf("%s\r\n", sztext1);
}
else
{
v.erase(v.begin() + i);
printf("客服端断开\n");
continue;
}
ss = "有";
ss += sztext1;
strcpy(sztext, ss.c_str());
for (int j = 0; j < v.size(); ++j)
{
::send(v.at(j), sztext, sizeof(sztext), 0);
}
}
}
}
}
}
catch (const exception& e)
{
cerr << "出错了" << endl;
Close();
}
return;
}

int main()
{
SOCKET s1;
Initialize();
auto w = async(launch::async, [&]{
while (true)
{
if (v.size() < SIZE)
{
for (int i = v.size(); i < SIZE; ++i)
{
s1 = ::accept(s, (sockaddr*)&addr2, &n);
if (s1 != NULL)
{
v.push_back(s1);
printf("%s已经连接上\r\n", inet_ntoa(addr2.sin_addr));
}
}
printf("服务器接收额已满!\n");
}
}
return;
});
try{
thread t1(MyRecv);
t1.join();
}
catch (const exception& e)
{
cerr << "出错了" << endl;
}
Close();
system("pause");
return 0;
}


 
         参数time-out控制select函数完成的时间(超过这个时间返回超时)。如果time-out是个空指针,select会一直保持阻塞指导至少一个描述符符合指定的准则。否则,time-out指向一个TIMEVAL结构体,这个结构体指定select在返回之前应该等待最大时间。当select返回,TIMEVAL结构体中的内容是不会改变的。如果TIMEVAL初始化为{0,0},select会立刻返回;这用于得到选择的套接字的状态。如果select立刻返回,然后select调用认为是非阻塞的,此时非阻塞调用的标准假设适用。例如,阻塞钩子不会调用,窗体套接字不会退出。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: