一、select的使用
主旨思想:
1、构造一个关于文件描述符的列表,将要监听的文件描述符添加到列表当中;
2、调用一个系统函数,监听该列表中的文件描述符,直到这些描述符中的一个或者多个进行IO操作时,该函数才会返回。
(1)这个函数是阻塞的;
(2)函数对文件描述符的检测的操作由内核完成
3、在返回时,它会告诉进程有多少描述符进行IO操作。
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
-参数:
-nfds:委托内核检测的最大文件描述符的值+1
-readfds:要检测的文件描述符的读的集合,委托内核检测哪些文件描述符的读的属性
-一般检测读操作
-对应的是对方发送过来的数据,因为读是被动的接收数据,检测的是读缓冲区
-是一个传入传出参数
-writefds:要检测的文件描述符的写的操作,委托内核检测哪些文件描述符写的属性
-委托内核检测写缓冲区是不是还可以写数据(不满就可以写)
-exceptfds:检测发生异常的文件描述符的集合
-timeout:设置的超时时间
struct timeval{
long tv_sec; //seconds
long tv_usec; //microseconds
};
-NULL:永久堵塞,直到检测到了文件描述符有变化
-tv_sec = 0 tv_usec = 0,不阻塞
-tv_sec > 0 tv_usec > 0,阻塞对应时间
返回值:
-1,失败
>0(n),检测的集合中有n个文件描述符发生了变化
void FD_CLR(int fd,fd_set *set);
功能:将参数文件描述符fd对应的标志位设置为0
int FD_ISSET(int fd,fd_set *set);
功能:判断fd对应的标志位是0还是1,返回值:fd对应的标志位的值如果是0则返回0,如果是1则返回1
void FD_SET(int fd,fd_set *set);
功能:将参数文件描述符fd对应的标志位置为1
void FD_ZERO(fd_set *set);
功能:fd_set 一共有1024bit,全部初始化为0
分析工作原理:客户端A、B、C、D对应的文件描述符分别为3、4、100、101,则说明3、4、100、101文件描述符对应的标志位设置为1,如果这四个客户端向服务端发送了读信号,则使用FD_SET进行设置。如果一共有101个客户端,则需要遍历102个数据(0~101),所以select的第一个位置要设置为101+1。此时会把用户态的文件描述符状态位拷贝到内核态,如果此时只有A、B两个客户端发送了数据,而C、D没有发送数据,那么内核态的文件描述符循环遍历的时候只检测到A、B客户端发来的数据,而没有检测到C、D客户端发来的数据,此时内核态的文件描述符就会把C、D的文件描述符标志位置为0,并且把目前的这种状态返回给用户态,用户态会重新遍历文件描述符的状态,以上就是select的运行过程。接下来服务器就会进行读操作。
使用select可以实现多线程或者多进程的效果,接下来代码走起来
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <sys/select.h>
4 #include <string.h>
5 #include <arpa/inet.h>
6 #include <sys/time.h>
7 #include <sys/types.h>
8 #include <unistd.h>
9 int main()
10 {
11 //1、socket
12 int socketfd=socket(AF_INET,SOCK_STREAM,0);
13 if(socketfd==-1)
14 {
15 perror("socket");
16 exit(-1);
17 }
18 //2、bind绑定
19 struct sockaddr_in seraddr;
20 seraddr.sin_family=AF_INET;
21 seraddr.sin_port=htons(9999);
22 seraddr.sin_addr.s_addr=INADDR_ANY;
23 int bindfd=bind(socketfd,(struct sockaddr*)&seraddr,sizeof(seraddr));
24 if(bindfd==-1)
25 {
26 perror("bindfd");
27 exit(-1);
28 }
29 //3、监听listen
30 int listenfd=listen(socketfd,128);
31 if(listenfd==-1)
32 {
33 perror("listenfd");
34 exit(-1);
35 }
36 //4、创建一个fd_set的集合,存放的是需要检测的文件描述符
37 fd_set rdset,tmp;
38 FD_ZERO(&rdset);//将rdset全部初始化为0
39 FD_SET(socketfd,&rdset);//将socketfd文件描述符初始化为1
40 int maxfd=socketfd;
41 while(1)
42 {
43 tmp=rdset;
44 //调用select系统函数,让内核帮检测哪些文件描述符有数据
45 int ret=select(maxfd+1,&tmp,NULL,NULL,NULL);
46 if(ret==-1)
47 {
48 perror("select");
49 exit(-1);
50 }
51 else if(ret==0)
52 {
53 continue;
54 }
55 else if(ret>0)
56 {
57 //说明检测到了文件描述符的对应缓冲区的数据发生了变化
58 if(FD_ISSET(socketfd,&tmp))
59 {
60 struct sockaddr_in cliaddr;
61 int len=sizeof(cliaddr);
62 int cfd=accept(socketfd,(struct sockaddr *)&cliaddr,&len);
63 //将新的文件描述符加入到集合中
64 FD_SET(cfd,&rdset);
65 //更新最大的文件描述符
66 maxfd=maxfd > cfd ? maxfd : cfd;
67 }
68 for(int i=socketfd+1;i<=maxfd;i++)
69 {
70 if(FD_ISSET(i,&tmp))
71 {
72 //说明这个文件描述符对应的客户端发来了数据
73 char buf[1024]={0};
74 int len=read(i,buf,sizeof(buf));
75 if(len==-1)
76 {
77 perror("read");
78 exit(-1);
79 }
80 else if(len==0)
81 {
82 printf("client closed...\n");
83 FD_CLR(i,&rdset);
84 }
85 else if(len>0)
86 {
87 printf("read buf=%s\n",buf);
88 write(i,buf,strlen(buf)+1);
89 }
90 }
91 }
92 }
93 }
94
95 return 0;
96 }
上面代码是服务器的,客户端的代码没有往上放,用前面的代码复制过来就能用,基本上客户端的代码不用大改。
虽然select优点多多,但是他也有很多的缺点,比如:
1、每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd非常多的时候会很大;
2、同时每次调用select都需要在内核遍历传递进来所有的fd,这个开销在fd非常多的时候会很大;
3、select支持的文件描述符数量太小了,默认是1024;
4、fds集合不能重复使用,每次都需要重置。
太棒了,又学会一个知识点,记得复习哦。