select原型:
参数说明:
3> write写fd集合
4> error错误fd集合
5> 超时时间
timeval原型:
struct timeval {
long tv_sec;
long tv_usec;
};
实测:
客户端关闭时:
超时!
错误!
错误!
错误!
。。。
客户端连接时:
两种情况:
A(客户端在超时时间内连接)
连接数:1
连接数:1
连接数:1
。。。
B(客户端在超时时间外连接)
超时!
错误!
错误!
错误!
。。。
这里返回错误代码:10022
10022:返回了一个无效的参数
后查实,发现是FD_SET(listfd,&readfd);没有放在while里面。
加了一段测试代码:
1
0
超时
0
0
错误
。。。
造成结果的原因:
select(0,NULL,NULL,NULL,wait_timeval);
这种方式在Linux下就相当于sleep,而Windows下却之间返回了-1,认为你传递的参数错误。查询了一下MSDN发现有如下说明。
以上说明了为什么会返回-1,至于为什么cout打印的值从1变成了0,请看下面:
fd_set的原型:
----------------------------------------------------------------
linux下的处理方法:
windows下的处理方法:
以linux为例:
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
基于上面的讨论,可以轻松得出select模型的特点:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。
参考文章:http://blog.csdn.net/lingfengtengfei/article/details/12392449
[windows]WINSOCK_API_LINKAGE int PASCAL select(int nfds,fd_set*,fd_set*,fd_set*,const struct timeval*);
[linux]int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
参数说明:
1> fd集合里面最大的+1(因为是从0开始)
windows下填0就好
3> write写fd集合
4> error错误fd集合
5> 超时时间
timeval原型:
struct timeval {
long tv_sec;
long tv_usec;
};
实测:
while(true)
{
//cout << "1" << endl;
nRet=select(0,&readfd,NULL,NULL,&vl);
if(nRet==0)
puts("超时!");
if(nRet==-1)
puts("错误!");
if(nRet > 0)
{
cout << "连接数:" << nRet << endl;
}
//Sleep(1);
}
客户端关闭时:
超时!
错误!
错误!
错误!
。。。
客户端连接时:
两种情况:
A(客户端在超时时间内连接)
连接数:1
连接数:1
连接数:1
。。。
B(客户端在超时时间外连接)
超时!
错误!
错误!
错误!
。。。
这里返回错误代码:10022
10022:返回了一个无效的参数
后查实,发现是FD_SET(listfd,&readfd);没有放在while里面。
加了一段测试代码:
cout << readfd.fd_count << endl;
nRet=select(0,&readfd,NULL,NULL,&vl);
cout << readfd.fd_count << endl;
结果:
1
0
超时
0
0
错误
。。。
造成结果的原因:
select(0,NULL,NULL,NULL,wait_timeval);
这种方式在Linux下就相当于sleep,而Windows下却之间返回了-1,认为你传递的参数错误。查询了一下MSDN发现有如下说明。
Microsoft MSDN:
Any two of the parameters, readfds, writefds, or exceptfds, can be given as null. At least one must be non-null,
and any non-null descriptor set must contain at least one handle to a socket.
参考文章:http://www.cnblogs.com/fullsail/archive/2012/08/12/2634336.html
以上说明了为什么会返回-1,至于为什么cout打印的值从1变成了0,请看下面:
fd_set的原型:
----------------------------------------------------------------
[windows,down]
typedef struct fd_set {
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
} fd_set;
----------------------------------------------------------------
[linux,down]
typedef struct {
unsigned long fds_bits [__FDSET_LONGS]; //用ulong数组来表示bitmap
} __kernel_fd_set;
typedef __kernel_fd_set fd_set;
----------------------------------------------------------------
linux下的处理方法:
//每个ulong为32位,可以表示32个bit。
//fd >> 5 即 fd / 32,找到对应的ulong下标i;fd & 31 即fd % 32,找到在ulong[i]内部的位置
#define __FD_SET(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] |= (1<<((fd) & 31))) //设置对应的bit
#define __FD_CLR(fd, fdsetp) (((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] &= ~(1<<((fd) & 31))) //清除对应的bit
#define __FD_ISSET(fd, fdsetp) ((((fd_set *)(fdsetp))->fds_bits[(fd) >> 5] & (1<<((fd) & 31))) != 0) //判断对应的bit是否为1
#define __FD_ZERO(fdsetp) (memset (fdsetp, 0, sizeof (*(fd_set *)(fdsetp)))) //memset bitmap
参考文章:linux中fd_set的内部实现
windows下的处理方法:
#define FD_SET(fd, set) do { u_int __i;\
for (__i = 0; __i < ((fd_set *)(set))->fd_count ; __i++) {\
if (((fd_set *)(set))->fd_array[__i] == (fd)) {\
break;\
}\
}\
if (__i == ((fd_set *)(set))->fd_count) {\
if (((fd_set *)(set))->fd_count < FD_SETSIZE) {\
((fd_set *)(set))->fd_array[__i] = (fd);\
((fd_set *)(set))->fd_count++;\
}\
}\
} while(0)
#define FD_ZERO(set) (((fd_set *)(set))->fd_count=0)
#define FD_ISSET(fd, set) __WSAFDIsSet((SOCKET)(fd), (fd_set *)(set))
以linux为例:
理解select模型的关键在于理解fd_set,为说明方便,取fd_set长度为1字节,fd_set中的每一bit可以对应一个文件描述符fd。则1字节长的fd_set最大可以对应8个fd。
(1)执行fd_set set;FD_ZERO(&set);则set用位表示是0000,0000。
(2)若fd=5,执行FD_SET(fd,&set);后set变为0001,0000(第5位置为1)
(3)若再加入fd=2,fd=1,则set变为0001,0011
(4)执行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都发生可读事件,则select返回,此时set变为0000,0011。注意:没有事件发生的fd=5被清空。
基于上面的讨论,可以轻松得出select模型的特点:
(1)可监控的文件描述符个数取决与sizeof(fd_set)的值。我这边服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。据说可调,另有说虽然可调,但调整上限受于编译内核时的变量值。
(2)将fd加入select监控集的同时,还要再使用一个数据结构array保存放到select监控集中的fd,一是用于再select返回后,array作为源数据和fd_set进行FD_ISSET判断。二是select返回后会把以前加入的但并无事件发生的fd清空,则每次开始 select前都要重新从array取得fd逐一加入(FD_ZERO最先),扫描array的同时取得fd最大值maxfd,用于select的第一个参数。
(3)可见select模型必须在select前循环array(加fd,取maxfd),select返回后循环array(FD_ISSET判断是否有时间发生)。
参考文章:http://blog.csdn.net/lingfengtengfei/article/details/12392449
windows下select代码(服务端):
#include <iostream>
#include <cstdlib>
#include <winsock2.h>
#include<cstdio>
#include "fcntl.h"
using namespace std;
int main()
{
WSADATA wd;
WSAStartup(0x0202,&wd);
fd_set readfd;
FD_ZERO(&readfd);
timeval vl;
vl.tv_sec=3;
vl.tv_usec=0;
SOCKET listfd=socket(AF_INET,SOCK_STREAM,0);
if(listfd==INVALID_SOCKET)
{
cout << "创建套接字失败,err:" << GetLastError() << endl;
}
//linux下的阻塞模型调整
// int flag=fcntl(listfd,F_GETFL,0);
// fcntl(listfd,F_SETFL,flag | O_NONBLOCK);
u_long non_blk = 1; //1:非阻塞 0:阻塞
ioctlsocket(listfd, FIONBIO, &non_blk); //FIONBIO:socket阻塞模式
sockaddr_in sa= {AF_INET};
sa.sin_addr.s_addr=inet_addr("0.0.0.0");
sa.sin_port=htons(8444);
int nRet=bind(listfd,(sockaddr*)&sa,sizeof(sa));
if(nRet==SOCKET_ERROR)
{
cout << "绑定端口失败,error:" << GetLastError() <<endl;
}
listen(listfd,5);
fd_set sockfd;
FD_ZERO(&sockfd);
FD_SET(listfd,&sockfd);
char sBuf[256];
while(true)
{
memcpy(&readfd,&sockfd,sizeof(fd_set));
nRet=select(0,&readfd,NULL,NULL,&vl);
if(nRet==0)
puts("超时!");
if(nRet==-1)
cout << "错误:" << GetLastError() << endl;
if(nRet > 0)
{
cout << "it is coming" << endl;
for(int i=0; i < readfd.fd_count; i++)
{
if(FD_ISSET(readfd.fd_array[i],&readfd) )
{
if(readfd.fd_array[i] == listfd)
{
SOCKET winsock=accept(listfd,NULL,NULL);
FD_SET(winsock,&sockfd);
FD_SET(winsock,&readfd);
}
else
{
memset(sBuf,0x00,sizeof(sBuf));
int n=recv(readfd.fd_array[i],sBuf,sizeof(sBuf),0);
if(n < 0 )
{
if(GetLastError() == 10054) //10054:远程主机强迫关闭了一个现有的连接。
{
cout << "quit!" << endl;
closesocket(readfd.fd_array[i]);
FD_CLR(readfd.fd_array[i],&sockfd);
}
else
{
cout << "recv error" << GetLastError() << endl; //10035:无法立即完成一个非阻止性套接字操作。
}
//阻塞模式下,只要n<0,就执行FD_CLR
//FD_CLR(readfd.fd_array[i],&sockfd);
}
else
{
cout << sBuf << endl;
//int nnn=EWOULDBLOCK;
}
}
}
}
}
//Sleep(200);
}
WSACleanup(); //不要忘了
return 0;
}
先看这张图,区分下linux与windows
linux下的实现(客户端):
#include <iostream>
#include <cstdlib>
#include<cstdio>
#include <cstring>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "fcntl.h"
#include <error.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
using namespace std;
int main()
{
// WSADATA wd;
//WSAStartup(0x0202,&wd);
int listfd=socket(AF_INET,SOCK_STREAM,0);
if(listfd==INVALID_SOCKET)
{
cout << "创建套接字失败,err:" << endl;
}
sockaddr_in sa={AF_INET};
sa.sin_addr.s_addr=inet_addr("127.0.0.1");
sa.sin_port=htons(8444);
int n = connect(listfd,(sockaddr*)&sa,sizeof(sa));
if(n != 0)
{
cout << "connect failed!" << endl;
return 0;
}
cout << "sock=" << listfd << endl;
char sBuf[256] ={0};
while(1)
{
memset(sBuf,0x00,sizeof(sBuf));
cout << "input any ..." << endl;
cin >> sBuf ;
n = send(listfd,sBuf,strlen(sBuf),0);
if(n > 0)
{
cout << "send success!" << endl;
}
}
//EWOULDBLOCK
return 0;
}
linux下的服务端:
#include <iostream>
#include <cstdlib>
#include<cstdio>
#include <cstring>
#include<sys/socket.h>
#include<arpa/inet.h>
#include <unistd.h>
#include "fcntl.h"
#include <error.h>
#define INVALID_SOCKET -1
#define SOCKET_ERROR -1
using namespace std;
int g_nCount=0;
int g_max=0;
int main()
{
//WSADATA wd;
//WSAStartup(0x0202,&wd);
fd_set readfd;
FD_ZERO(&readfd);
timeval vl;
int listfd=socket(AF_INET,SOCK_STREAM,0);
if(listfd==INVALID_SOCKET)
{
cout << "创建套接字失败,err:" << endl;
}
//linux下的阻塞模型调整
int flag=fcntl(listfd,F_GETFL,0);
fcntl(listfd,F_SETFL,flag | O_NONBLOCK);
//windows
//unsigned long flag=1;
//ioctlsocket(listfd,FIONBIO,&flag);
sockaddr_in sa= {AF_INET};
sa.sin_addr.s_addr=inet_addr("0.0.0.0");
sa.sin_port=htons(8444);
int nRet=bind(listfd,(sockaddr*)&sa,sizeof(sa));
if(nRet==SOCKET_ERROR)
{
cout << "绑定端口失败,error:" <<endl;
}
listen(listfd,5);
int sockfd[1000]={0};
sockfd[0]=listfd;
char sBuf[256];
g_nCount=1;
g_max=listfd+1;
while(true)
{
FD_ZERO(&readfd); //一定不能忘
memset(&vl,0x00,sizeof(vl));
vl.tv_sec=4;
vl.tv_usec=0;
for(int i=0;i < g_nCount;i++)
{
FD_SET(sockfd[i],&readfd);
cout << sockfd[i] << endl;
}
nRet=select(g_max,&readfd,NULL,NULL,&vl);
if(nRet==0)
puts("超时!");
if(nRet==-1)
cout << "错误!" << endl;
if(nRet > 0)
{
cout << "it is coming" << endl;
//for(i= sock+1; i <max_fd+1; ++i) ,也是一个思路
for(int i=0; i < g_nCount; i++)
{
if(FD_ISSET(sockfd[i],&readfd) )
{
g_max=(sockfd[i] >= g_max ? (sockfd[i]+1) : g_max);
cout << "coming,and sockfd[i]=" << sockfd[i] << endl;
if(sockfd[i] == listfd)
{
int unixsock=accept(listfd,NULL,NULL);
sockfd[g_nCount++]=unixsock;
FD_SET(unixsock,&readfd);
}
else
{
memset(sBuf,0x00,sizeof(sBuf));
cout << "test,i=" << i << ",sock="<< sockfd[i] << ",g_nCount=" << g_nCount << endl;
int n=recv(sockfd[i],sBuf,sizeof(sBuf),0);
if(n < 0 )
{
cout << "recv error" << endl;
//阻塞模式下,只要n<0,就执行FD_CLR
//FD_CLR(readfd.fd_array[i],&sockfd);
}
else if(n==0)
{
cout << "quit!" << endl;
close(sockfd[i]);
int delete_fd=sockfd[i];
//memcpy(sockfd,sockfd+i+1,g_nCount-i); 效率比较低
if(i < g_nCount-1) //如果i=g_nCount,这个数就抹不掉了
{
sockfd[i--]=sockfd[--g_nCount]; //很不错的思路
}
else
{
sockfd[i]=0;
g_nCount--;
}
//max的值可能已经发生了变化,这里暂不考虑修改max,可以再while后更新max
//影响不大,无非select多遍历了几个数
}
else
{
cout << sBuf << endl;
//int nnn=EWOULDBLOCK;
}
}
}
}
}
//Sleep(200);
//sleep(1);
}
return 0;
}
//linux下select后,描述符集合,timeval,都会清零
//windows下,只有描述集清零.
//linux下,第一个参数不能为0.是最大值+1,+n(n > 1)就行
//windows下,本参数忽略,仅起到兼容作用,设为0即可