//int select(int maxfdp, fd_set *readset, fd_set *writeset, fd_set *exceptset,struct timeval *timeout);
//参数1 描素符的总数 它比所有文件描述符集合中的文件描述符的最大值大1, readfds、writefds、exceptset:分别指向可读、可写和异常等事件对应的描述符集合。timeout:用于设置select函数的超时时间,即告诉内核select等待多长时间之后就放弃等待。timeout == NULL 表示等待无限长的时间
服务器上sizeof(fd_set)=512,每bit表示一个文件描述符,则我服务器上支持的最大文件描述符是512*8=4096。
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
fd_set rd; //结构体
struct timeval tv; //结构体
int err;
FD_ZERO(&rd); //初始化放到_fd_set 变量
FD_SET(0,&rd); //把标准输入 放入rd中
tv.tv_sec = 5;
tv.tv_usec = 0;
err = select(1,&rd,NULL,NULL,&tv);
if(err == 0) //超时
{
printf("select time out!\n");
}
else if(err == -1) //失败
{
printf("fail to select!\n");
}
else //成功
{
printf("data is available!\n");
}
char ch;
while(ch=getchar()!='\n');
return 0;
}
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
int main(int argc, char* argv[])
{
if(argc <= 2)
{
printf("usage: ip address + port numbers\n");
return -1;
}
struct timeval tv;
const char* ip = argv[1];
int port = atoi(argv[2]);
tv.tv_sec = 5;
tv.tv_usec = 0;
printf("ip: %s\n",ip);
printf("port: %d\n",port);
int ret = 0;
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port = htons(port);
int listenfd = socket(PF_INET,SOCK_STREAM,0);
if(listenfd < 0)
{
printf("Fail to create listen socket!\n");
return -1;
}
ret = bind(listenfd,(struct sockaddr*)&address,sizeof(address));
if(ret == -1)
{
printf("Fail to bind socket!\n");
return -1;
}
ret = listen(listenfd,5); //监听队列最大排队数设置为5
if(ret == -1)
{
printf("Fail to listen socket!\n");
return -1;
}
struct sockaddr_in client_address; //记录进行连接的客户端的地址
socklen_t client_addrlength = sizeof(client_address);
int connfd = accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
if(connfd < 0)
{
printf("Fail to accept!\n");
close(listenfd);
}
char buff[1024]; //数据接收缓冲区
fd_set read_fds; //读文件操作符
fd_set exception_fds; //异常文件操作符
FD_ZERO(&read_fds);
FD_ZERO(&exception_fds);
while(1)
{
memset(buff,0,sizeof(buff));
//每次调用select之前都要重新在read_fds和exception_fds中设置文件描述符connfd,因为事件发生以后,文件描述符集合将被内核修改
FD_SET(connfd,&read_fds);
FD_SET(connfd,&exception_fds);
ret = select(connfd+1,&read_fds,NULL,&exception_fds,&tv);
if(ret < 0)
{
printf("Fail to select!\n");
return -1;
}
if(FD_ISSET(connfd, &read_fds))
{
ret = recv(connfd,buff,sizeof(buff)-1,0);
if(ret <= 0)
{
break;
}
printf("get %d bytes of normal data: %s \n",ret,buff);
}
else if(FD_ISSET(connfd,&exception_fds)) //异常事件
{
ret = recv(connfd,buff,sizeof(buff)-1,MSG_OOB);
if(ret <= 0)
{
break;
}
printf("get %d bytes of exception data: %s \n",ret,buff);
}
}
close(connfd);
close(listenfd);
return 0;
}
select本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是:
1、单个进程可监视的fd数量被限制,即能监听端口的大小有限。一般来说这个数目和系统内存关系很大,具体数目可以cat/proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048.
2、 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低:当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍。这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的。
3、需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大。
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <netinet/in.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
int server_sockfd, client_sockfd;
int server_len, client_len;
struct sockaddr_in server_address;
struct sockaddr_in client_address;
int result;
fd_set readfds, testfds;
server_sockfd = socket(AF_INET, SOCK_STREAM, 0);//建立服务器端socket
server_address.sin_family = AF_INET;
server_address.sin_addr.s_addr = htonl(INADDR_ANY);
server_address.sin_port = htons(8888);
server_len = sizeof(server_address);
bind(server_sockfd, (struct sockaddr *)&server_address, server_len);
listen(server_sockfd, 5); //监听队列最多容纳5个
FD_ZERO(&readfds); //初始化文件描素符集合
FD_SET(server_sockfd, &readfds);//将服务器端socket加入到集合中
while(1)
{
char ch;
int fd; //用于循环
int nread;
testfds = readfds;//将需要监视的描述符集copy到select查询队列中,select会对其修改,所以一定要分开使用变量
printf("server waiting\n");
/*无限期阻塞,并测试文件描述符变动 FD_SETSIZE==1024*/
result = select(FD_SETSIZE, &testfds, (fd_set *)0,(fd_set *)0, (struct timeval *) 0); //FD_SETSIZE:系统默认的最大文件描述符
if(result < 1)
{
perror("server5");
exit(1);
}
/*扫描所有的文件描述符*/
for(fd = 0; fd < FD_SETSIZE; fd++)
{
/*找到相关文件描述符*/
if(FD_ISSET(fd,&testfds))
{
/*判断是否为服务器套接字,是则表示为客户请求连接。*/
if(fd == server_sockfd)
{
client_len = sizeof(client_address);
client_sockfd = accept(server_sockfd,
(struct sockaddr *)&client_address, &client_len);
FD_SET(client_sockfd, &readfds);//将客户端socket加入到集合中
printf("adding client on fd %d\n", client_sockfd);
printf("ip,port%ld\n",client_address.sin_port);
}
/*客户端socket中有数据请求时*/
else
{
ioctl(fd, FIONREAD, &nread);//取得数据量交给nread
/*客户数据请求完毕,关闭套接字,从集合中清除相应描述符 */
if(nread == 0) //
{
close(fd);
FD_CLR(fd, &readfds); //去掉关闭的fd
printf("removing client on fd %d\n", fd);
}
/*处理客户数据请求*/
else
{
read(fd, &ch, 1);
sleep(5);
printf("serving client on fd %d\n", fd);
ch++;
write(fd, &ch, 1);
}
}
}
}
}
return 0;
}
*/