深入剖析Linux Select函数使用方法 (linux select 详解)

Linux操作系统作为一个开源、自由的操作系统,其运行速度快、安全稳定、容易维护等优点,已经被广泛应用于各种设备和场景。在Linux系统中,select函数是常用的I/O复用函数,它可以实现同时监听多个I/O事件,提高程序的效率和可靠性。本文将深入剖析Linux Select函数的使用方法,希望能帮助读者更好地理解和使用该函数。

一、Select函数介绍

Select函数是一种I/O多路复用机制,用于同时监听多个I/O事件。当监视的任何一种事件发生时,select函数就会返回,并通知应用程序发生的事件类型。select函数的基本语法如下:

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

参数说明:

1. nfds:监视的更大文件描述符,即需要监听事件的文件描述符的更大值加1。

2. readfds:需要监听读事件的文件描述符。

3. writefds:需要监听写事件的文件描述符。

4. exceptfds:需要监听异常事件的文件描述符。

5. timeout:超时时间,可以让select函数在等待一段时间后超时返回。

返回值说明:

1. 返回-1:表示select函数调用失败。

2. 返回0:表示在超时时间内没有任何事件发生。

3. 返回大于0的整数:表示有事件发生,返回值表示有多少个文件描述符有事件发生。

由上述语法可知,select函数的调用方式较为复杂,需注意各参数的设置和使用方式。

二、Select函数使用方法

(一) 函数调用模式

在使用Select函数时,应该首先考虑Select函数的调用模式。Select函数在调用时有两种模式:阻塞模式和非阻塞模式。在阻塞模式下,当Select函数未检测到I/O事件时,应用程序会一直被阻塞,直到有事件发生后才返回。而在非阻塞模式下,当Select函数未检测到I/O事件时,应用程序不会被阻塞,会立即返回。非阻塞模式通常用于提高程序的效率和响应速度。

(二) 文件描述符的设置

在使用Select函数时,需要设置文件描述符的。文件描述符是文件操作函数中的一种参数,其通过整数来标识已打开的文件。在使用Select函数时,需要将需要监听的文件描述符添加到对应的中去。对于不需要监听的文件描述符,则不应该添加到对应的中。在使用文件描述符时,可以通过以下函数进行设置操作:

1. FD_ZERO(fd_set *set):将指定的文件描述符清空。

2. FD_SET(int fd, fd_set *set):将指定的文件描述符添加到中。

3. FD_CLR(int fd, fd_set *set):将指定的文件描述符从中删除。

4. FD_ISSET(int fd, fd_set *set):判断指定的文件描述符是否在中。

需要注意的是,在设置和使用fd_set结构体时,不要直接修改底层文件描述符fd_set中的描述符,而应该使用fd_set结构体中提供的函数。

(三) Select函数的超时设置

Select函数可以设置超时时间,这条语句可以在函数调用之前或之后设置。当Select函数超过指定的时间后,即使没有I/O事件发生,函数也会自动返回,以防止程序一直被阻塞。timeout参数是一个结构体,里面包含了两个变量:tv_sec表示等待时间的整秒部分,tv_usec表示等待时间的微秒部分。需要注意的是,当timeout设置为NULL时,Select函数会一直等待I/O事件的发生,直到有事件发生才会返回。

(四) Select函数的错误处理

在使用Select函数时,需要注意错误处理。当Select函数返回-1时,表示函数调用出错。这时应该使用perror()函数打印出错误信息。错误的原因可能是传入函数的参数不合法,也可能是系统资源不足等。正确处理Select函数的返回值和错误信息可以使程序更加稳定和安全。

(五) Select函数的示例程序

下面给出一个简单的Select函数示例程序,以演示Select函数的基本使用方法。

“`c

#include

#include

#include

#include

#include

#include

#include

#include

#define PORT 8080

#define BUF_SIZE 1024

int mn(int argc, char *argv[]) {

int listenfd, connfd;

struct sockaddr_in servaddr, clientaddr;

socklen_t clientlen = sizeof(clientaddr);

char buf[BUF_SIZE];

fd_set readfds;

int maxfd;

listenfd = socket(AF_INET, SOCK_STREAM, 0);

if (listenfd

perror(“socket error”);

exit(1);

}

memset(&servaddr, 0, sizeof(servaddr));

servaddr.sin_family = AF_INET;

servaddr.sin_port = htons(PORT);

servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(listenfd, (struct sockaddr*) &servaddr, sizeof(servaddr))

perror(“bind error”);

exit(1);

}

if (listen(listenfd, 5)

perror(“listen error”);

exit(1);

}

FD_ZERO(&readfds);

FD_SET(listenfd, &readfds);

maxfd = listenfd;

while (1) {

int ret = select(maxfd+1, &readfds, NULL, NULL, NULL);

if (ret

perror(“select error”);

exit(1);

} else if (ret == 0) {

printf(“select timeout\n”);

continue;

}

if (FD_ISSET(listenfd, &readfds)) {

connfd = accept(listenfd, (struct sockaddr*) &clientaddr, &clientlen);

if (connfd

perror(“accept error”);

exit(1);

}

printf(“accept a new client: %s:%d\n”, inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port));

FD_SET(connfd, &readfds);

maxfd = connfd > maxfd ? connfd : maxfd;

} else {

int fd;

for (fd = listenfd+1; fd

if (FD_ISSET(fd, &readfds)) {

int len = recv(fd, buf, sizeof(buf), 0);

if (len

perror(“recv error”);

exit(1);

} else if (len == 0) {

printf(“client closed\n”);

close(fd);

FD_CLR(fd, &readfds);

} else {

printf(“recv from client: %s\n”, buf);

send(fd, buf, len, 0);

}

}

}

}

}

close(listenfd);

return 0;

}

“`

上述代码是一个简单的TCP服务器代码。该代码使用了Select函数实现并发处理多个客户端的请求。代码中首先创建了一个监听套接字,设置了服务器的IP和端口,并将其绑定到监听套接字上。然后,将监听套接字添加到读取文件描述符中,并初始化所有的客户端描述符为空。在循环中,通过调用Select函数等待所有文件描述符中的一个或多个文件描述符将准备好进行I/O操作。当Select函数返回时,使用FD_ISSET()函数轮询读取文件描述符,判断哪个读取事件已经准备好了,并接收或发送数据。当有新连接到来时,使用accept()函数处理连接请求,并将客户端描述符添加到读取文件描述符中。

三、

相关问题拓展阅读:

Linux内核(七)轮询操作

在用户程序中,select()和poll()也是与设备阻塞和非阻塞访问相关的内容。

使用非阻塞IO的应用程序通常会使用select()和poll()系统调用查询是否可以对设备进行无阻塞的访问。

select()和poll()系统调用最终会使设备驱动中的poll()函数执行,在后续的Linux内核版本中还引入了epoll(),即扩展的poll()。

select()和poll()系统调用的本质是一样的,前者在BSD Unix中引入,后者在System V中引入。

应用程序中使用最广泛的是BSD Unix中引入的select()系统调用,原型如下:

如下图所示,

之一次对n个文件进行select()的时候,若任何一个文件满足要求,select()就直接返回;

第二次再进行select()的时候,没有文件满足读写要求,select()的进程阻塞且睡眠。

由于调用select()的时候,每个驱动的poll()接口都会被调用到。实际上执行select()的进程被挂到了每个驱动的等待队列上,可以被任何一个驱动唤醒。如果FDn变得可读写,select()返回。

poll()的功能和实现原理与select()类似,其原型函数为:

当多路复用的文件数量庞大、IO流量频繁的时候,一般不太适合使用select()和poll(),这种情况下select()和poll()表现较差,推荐使用epoll()。

使用epoll()更大的好处就是不会随着fd数目的增长而降低效率,select()则会随冲厅着fd数量增大性能明显下降。

相关接口:

创建一个epoll()的句柄,size用来告诉内核要监听多少个fd,当创建好epoll()句柄时,它本身也会占用一个fd值,所以在使用完epoll()后,必须调用close()关闭。

告诉内核要监听什么类型的事件:

之一个参数epfd是epoll_create()的返回值,

第二个参数表示动作,包含散洞隐:

第3个参数是需要监听的fd,

第4个参数是告诉内核需要监听的事件类型,struct_epoll_event结构如下:

events可以是以下几个宏的”或“:

一般来说,当涉及的fd数量较少时,使用select是合适的;如果涉及的fd很多,如颤判在大规模并发的服务器中监听许多socket的时候,则不太适合选用select,适合使用epoll。

关于linux下select函数问题

需不需要是写api的人决定的, 他觉得有用你就得给, 想多没意义

都检查

nfds: 需要检查的文件描述字个数(即检查到胡明fd_set的第几位),数值应该比三组fd_set中所含的更大fd值更大,一般设为三组fd_set中所含的更大fd值加1(如在readset, writeset, exceptset中所含更大的fd为5,则nfds=6,因为fd是从0开始的 )。设这个值是为了提高效率,使函数不必检查fd_set的所有1024位。 否则函数默认会检查到更大值

至于你那个补充内容,是这样的 当调用悄做余select()时,由内核根据IO状态修改fe_set的内容,由此来通知执行了select()的进程哪一socket或文件可读写。 这个 是系统内核负责管理的,如果fd1或fd2那个一启滚旦有变化,系统内核会修改其内容就通知select的进程了,如果fd1 fd2都可读 首先肯定有个先后顺序,会记录下来,挨个处理。

关于select函数在linux下的问题

因为LINUX下的标准输森稿出stdout,是行缓冲的,缓冲区大小8192字节是一个典型值.

必须遇到字符’\n’才真正刷新缓冲区输出到屏幕上,或者输出缓冲区被填满也会输出到屏幕。

我估计,你等足够长的时间,搏春伍等你的修改后的基或程序把输出缓冲区填满,会一次性的打印出一大堆abc.

linux select 详解的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于linux select 详解,深入剖析Linux Select函数使用方法,Linux内核(七)轮询操作,关于linux下select函数问题,关于select函数在linux下的问题的信息别忘了在本站进行查找喔。


数据运维技术 » 深入剖析Linux Select函数使用方法 (linux select 详解)