socket编程中select()函数服务器端的使用

问题1:socket编程中,为何select放在while(1)中?

因为select需要将监听到多个客户端分别 fd = accept()一下,然后再进行读写判断。

select()在服务器端和客户端的区别:

服务器端多了一个以的形式监听“建立连接的”lfd。(lfd = socket();)其他读写的fds(fd = accept();)

客户端中只有监听读、写的连接。

多路IO转接,通过内核来监听多个客户端的连接,监听对端发送数据,代替accept函数的监听阻塞功能了。

故:

1)不需要阻塞等待连接(accept不会阻塞了)。

2)不需要阻塞去读对端(read不会阻塞了),这样server.c就可以解放去做其他事情了。

内核一旦给返回了,一定是事件发生了。

服务器通过accept监听客户端1,首先是建立连接的的请求,然后是读写请求;

服务器通过accept监听客户端2,首先是建立连接的的请求,然后是读写请求;

但是会在select()这里进行阻塞,select()函数是在accept()函数前面的,帮忙解决了accept的阻塞,read函数的阻塞。

 listenfd也应该在读事件中(服务器监听客户端,相当于读操作),listenfd只需要一个就行了,每accept()一次,会多一个客户端的fd

内核监听是通过select函数来监听的。

#include <sys/select.h>

int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

参数解释:

nfds:监听的文件描述符(fd)中,最大的那个+1;(多个fd,多路复用的体现)+1的实质是指监听了前n+1个描述符0,1,2,3

readfds:文件描述符可读事件,数据类型是个位图,本质上是一个long数组的各个位。

writefds:文件描述符可写事件

exceptfds:文件描述符异常事件

timeout:阻塞时间,如果监听的事件在timeout时间内都没有来,则停止阻塞,则NULL,则select永久等待。

readfds\writefds\exceptfds都是传入传出参数

struct timeval{     //都是相对时间

long tv_sec;  //秒

long tv_usec;  //微秒

};

timeout的值决定了select函数的返回状态:

1)传入NULL,就将select函数置于阻塞状态,一定要等到监视的文件描述符有变化才会返回

2)将时间设为0秒0毫秒,就成为一个非阻塞的函数,不管文件描述符有无变化,都立刻返回继续执行,文件无变化返回0,有变化返回一个正值。

3)设为大于0的值,即为等待的超时时间,即在timeout时间内阻塞,如为超时,且有事件到来则返回,否则超时后不管怎么一定返回,返回值同2)

(ps:select是通过timeout值来避免阻塞的吧。。。,只要不设为NULL,就不会阻塞)

返回值:

1)如果成功,则返回的是所监听的所有监听集合满足条件的总数:是读+写+异常的和

2)等待超时,没有可读写的文件,则返回0

3)失败,则返回-1,并设置errno

void FD_ZERO(int fd,fd_set *set);//将set清空为0//位操作 0000  0000//每次都要清零

void FD_SET(int fd,fd_set *set);//将fd设置到set集合中,即将文件描述符中fd的位置置1

int  FD_ISSET(int fd,fd_set *set);//判断fd是否在集合中//返回1,满足条件;返回0,不满足条件

void FD_CLR(int fd,fd_set *set);//将fd在set中清除,即将描述符在位图中的对应位置置0,对端关闭了,将描述符清除。

fd_set readfds;
FD_ZERO(&readfsd);
FD_SET(lfd,&readfsd);//监听连接文件描述符lfd,相当于读事件。
FD_SET(fd1,&readfsd);//监听fd1的读事件
FD_SET(fd2,&readfsd);//监听fd2的读事件
FD_SET(fd3,&readfsd);//监听fd3的读事件
n = select(fds+1,&readfds,NULL,NULL,NULL);//readfds是传入传出参数,有事件发生则该值发生变化

    FD_ISSET(fd1,&readfsd);
    accept....

 readfds、writefds、exceptfds是传入传出参数,如上,当传入到select后,readfds传入的有3值,假如此时只有fd1和fd2中有读事件发生,则select函数返回,则readfds中传出的是2值

readfds传入

 

 

fd3

fd2

fd1

lfd

位图

...

0

1

1

1

1

readfds传出

 

 

fd3

fd2

fd1

lfd

位图

...

0

0

1

1

1

 传入的时候位图全是1,传出的时候,没有读事件发生的置0了。

所以,如上述程序,readfds传入前位图中有4个1,假设lfd,fd1和fd2有读事件了,则select返回值n=3.位图中fd3被置0了。因此会使用数组来存储要监听的文件描述符。

文件描述符上限是1024,所以select同时监听的文件描述符最大是1024个。

一般用的时候,会定义数组来存储所监听的fd,否则,select去找的话,会从3遍历到1023,浪费时间(0.1.2都被占用)。

因为 readfds、writefds、exceptfds是传入传出参数,所以要对上一次的这三个值进行保存,否则这次就会被改写了。即,监听集合和满足条件的集合是一个集合,为了防止下次监听的集合被改写,所以先对原先的集合进行保存。

总结:

1)select函数及以上的代码写好后,就完成了内核监听注册;

2)然后通过FD_ISSET(fd1,&readfsd);判断到底是哪个fd的事件是否发生。

3) 在设置timeout参数为NULL的情况下,select函数若返回,一定是有监听事件满足了。

具体步骤:

1)FD_set(lfd,&rset); maxfd=lfd;

2)select(madfd+1,&rset,,)//只是在监听lfd

3)判断FD_ISSET(lfd),//进行判断是否有连接,为1表明有客户端在请求连接

4)接着调用confd = accept(),返回一个新的文件描述符,服务器与客户端建立连接。

5)FD_SET(confd,&rset);if(confd>maxfd) maxfd = confd;

6)selcet(maxfd+1,&rset,,,);

7)FD_ISSET(confd,&rset),为1,则read.......

发布了100 篇原创文章 · 获赞 26 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/modi000/article/details/105269884
今日推荐