`

I/O 复用之select 函数

阅读更多
    select 函数允许进程指示内核等待多个事件中的任何一个发生,并只在有一个或多个事件发生或经历一段指定的时间后才唤醒它。感兴趣的描述符不局限于套接字,任何描述符都可以使用 select 来测试。
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
           const struct timeval *timeout);
               /* 返回值:若有就绪描述符则为其数目,若超时则为 0,若出错则为 -1 */

#define FD_SETSIZE    1024

void FD_ZERO(fd_set *fdset);             // clear all hits in fdset, initialize
void FD_SET(int fd, fd_set *fdset);      // turn on the bit for fd in fdset
void FD_CLR(int fd, fd_set *fdset);      // turn off the bit for fd in fdset
int  FD_ISSET(int fd, fd_set *fdset);    // is the bit for fd on in fdset ?

    这里从 select 函数的最后一个参数 timeout 开始介绍,它告知内核等待所指定描述符中的任何一个就绪可花多长时间。该参数值有以下三种可能:
    (1)为空指针,表示永远等待下去,仅在有一个描述符准备好 I/O 时才返回。
    (2)其中的 tv_sec 和 tv_usec 不全为 0,表示最多等待 I/O 就绪的时间。
    (3)其中的 tv_sec 和 tv_usec 都为 0,表示不等待,这称为轮询。
    前两种情形的等待通常会被进程在等待期间捕获的信号中断,并从信号处理函数返回。要注意的是,各种实现不一定会自动重启被中断的 select,这意味着如果在捕获信号,那么为了可移植性,必须做好处理 select 返回 EINTR 错误的准备。
    尽管 POSIX 规定该参数带有 const 限定词,但有些 Linux 版本可能会修改这个结构,因此应该假设该结构在 select 返回时未被定义,因而每次调用 select 之前都应该对它重新初始化。
    select 中间的三个参数 readset、writeset 和 exceptset 指定要让内核测试读、写和异常条件的描述符。如果对其中某一个条件不感兴趣,就可以把它设为空指针。事实上,当这三个指针均为空时,就得到了一个比 sleep 函数更为精确的定时器。
    目前支持的异常条件只有两个:
    (1)某个套接字的带外数据的到达。
    (2)某个已置为分组模式的伪终端存在可从其主端读取的控制状态信息。
    如何给这三个参数中的每一个指定一个或多个描述符值是一个设计上的问题。select 使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述符,不过所有这些实现细节都与应用程序无关,它们隐藏在名为 fd_set 的数据类型和 FD_ZERO、FD_SET、FD_CLR 和 FD_ISSET 这四个宏中。可以使用这四个宏设置或测试描述符集合中的每一位,也可以使用赋值语句把它赋值成另一个描述符集。
    描述符集的初始化非常重要,因为作为自动变量分配的一个描述符集如果没有初始化,那么可能发生不可预期的后果。FD_ZERO 宏就可以用来初始化描述符集。
    select 的第一个参数 maxfdp1 指定待测试的描述符的最大个数,它的值是待测试的最大描述符加 1,描述符 0 到 maxfdp1-1 均将被测试。存在该参数纯粹是为了效率原因,因为每个 fd_set 可以表示大量描述符(头文件 <sys/select.h> 中定义的 FD_SETSIZE 常量是数据类型 fd_set 中的描述符总数,其值通常是 1024),然而一个普通进程所用的数量却很少。内核正是通过在进程与内核之间不复制描述符集合中不必要的部分,从而不测试总是为 0 的那些位来提高效率的。
    select 函数会修改 readset、writeset 和 exceptset 所指向的描述符集,因此每次重新调用该函数时都应该再次把所有描述符集内所关心的位置为 1。调用该函数时,我们指定所关心的描述符的值,该函数返回后,结果将指示哪些描述符已就绪,可以使用宏 FD_ISSET 来测试其中的描述符。描述符集内任何与未就绪描述符对应的位返回时均清成 0。
    那么怎样才算达到了“就绪”条件呢?
    (1)满足下列四个条件中的任何一个时,一个套接字准备好读。
    a)该套接字接收缓冲区中的数据字节数大于等于套接字接收缓冲区低水位标记。对这样的套接字执行读操作不会阻塞并将返回一个大于 0 的值。可以使用 SO_RCVLOWAT 套接字选项设置该套接字的低水位标记。对于 TCP 和 UDP 套接字,其默认值为 1。
    b)该连接的读半部关闭(也就是接收了 FIN 的 TCP 连接)。对这样的套接字的读操作不会阻塞并返回 0(也就是返回 EOF)。
    c)该套接字是一个监听套接字且已完成的连接数不为 0。对这样的套接字的读操作通常不会阻塞。
    d)其上有一个套接字错误待处理。对这样的套接字的读操作不会阻塞并返回 -1,同时设置 errno 为确切的错误条件。这些待处理错误也可以通过指定 SO_ERROR 套接字选项调用 getsockopt 获取并清除。
    (2)满足下列四个条件中的任何一个时,一个套接字准备好写。
    a)该套接字发送缓冲区中的可用空间字节数大于等于套接字发送缓冲区低水位标记,并且或者该套接字已连接,或者该套接字不需要连接(如 UDP 套接字)。这意味着如果把这样的套接字设置成非阻塞,写操作将不阻塞并返回一个正值。可以使用 SO_SNDLOWAT 套接字选项来设置该套接字的低水位标记。对于 TCP 和 UDP 套接字,其默认值为 2048。
    b)该连接的写半部关闭。对这样的套接字的写操作将产生 SIGPIPE 信号。
    c)使用非阻塞式 connect 的套接字已建立连接,或者 connect 失败。
    d)其上有一个套接字错误待处理。对这样的套接字的写操作将不阻塞并返回 -1,同时设置 errno 为确切的错误条件。这些待处理的错误也可以通过指定 SO_ERROR 套接字选项调用 getsockopt 获取并清除。
    (3)如果一个套接字存在带外数据或者仍处于带外标记,那么它有异常条件待处理。
    下表汇总了上述导致 select 返回某个套接字就绪的条件。

    注意,当某个套接字上发生错误时,它将由 select 标记为既可读又可写。
    下面是用 select 实现的回射服务器中客户端用来处理连接部分的代码,它可以读取用户输入并发送到服务端,也可以从服务端接收数据并显示到标准输出。它会阻塞于 select 调用,直到用户输入或套接字可读。下图展示了调用 select 所处理的各种条件。

    客户的套接字的三个条件处理如下:
    (1)如果对端 TCP 发送数据,那么该套接字变为可读,并且 read 返回读入数据的字节数。
    (2)如果对端 TCP 发送一个 FIN(对端进程终止),那么该套接字变为可读,并且 read 返回 0(EOF)。
    (3)如果对端 TCP 发送一个 RST(对端主机崩溃并重新启动),那么该套接字变为可读,并且 read 返回 -1,而 errno 中含有确切的错误码。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>

#define MAX(n1, n2)	((n1)>(n2) ? (n1):(n2))

#define MAXLINE		4093

void str_cli(FILE *fp, int sockfd){
	int n;
	char buf[MAXLINE];
	fd_set readset;
	FD_ZERO(&readset);
	int stdineof = 0;
	int infd = fileno(fp);
	int maxfdp1 = MAX(infd, sockfd) + 1;
	for(;;){
		if(stdineof == 0)
			FD_SET(infd, &readset);
		FD_SET(sockfd, &readset);
		select(maxfdp1, &readset, NULL, NULL, NULL);
		if(FD_ISSET(sockfd, &readset)){		// socket is readable
			if((n=read(sockfd, buf, MAXLINE)) <= 0){
				if(n == 0 && stdineof == 1)
					return;		// normal termination
				else{
					printf("str_cli: server terminated prematurely\n");
					return;
				}
			}
			write(STDOUT_FILENO, buf, n);
		}
		if(FD_ISSET(infd, &readset)){		// input is readable
			if((n=read(infd, buf, MAXLINE)) <= 0){
				stdineof = 1;
				shutdown(sockfd, SHUT_WR);	// send FIN
				FD_CLR(infd, &readset);
				continue;
			}
			write(sockfd, buf, strlen(buf));
		}
	}
}


    除 select 外,POSIX 还提供了一个 pselect 变种。
#include <sys/select.h>
#include <time.h>
#include <signal.h>
int pselect(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset,
           const struct timespec *timeout, const sigset_t *sigmask);
               /* 返回值:若有就绪描述符则为其数目,若超时则为 0,若出错则为 -1 */

    该函数相对于 select 有两个变化。
    (1)pselect 使用 timespec 结构而不是 timeval 结构。
    (2)pselect 增加了一个指向信号掩码的指针 sigmask 作为参数。该参数允许程序先禁止递交某些信号,再测试由这些当前被禁止信号的信号处理函数设置的全局变量,然后调用 pselect,告诉它重新设置信号掩码。
  • 大小: 14.8 KB
  • 大小: 10.2 KB
分享到:
评论

相关推荐

    I/O复用对的实现

    3. 调用I/O复用函数(如`select`、`poll`或`epoll_wait`)进入等待状态,直到至少有一个文件描述符准备好进行I/O操作。 4. 检查返回的文件描述符集合,对每个就绪的描述符执行相应的读写操作。 5. 根据需要更新文件...

    采用I/O复用技术select实现socket通信,完成Linux下的多客户聊天室!

    本项目涉及的核心技术是I/O复用,具体使用了Linux环境下的`select`函数来实现多客户端的聊天室功能。`select`是多路复用I/O模型的一种,它允许程序同时监控多个文件描述符(如socket),等待它们准备好进行读写操作...

    利用I/O复用模型实现一个时间同步服务器

    服务端采用I/O复用模型(select函数)接收客户端的时间同步请求 服务端采用单线程,但要能同时接收多客户端的连接请 求,显示客户端IP和端口,并向其回送时间信息。 客户端尝试同时使用 UDP 和 TCP 来实现。 注:...

    linux socket最简单实现server和client通讯(I/O复用)

    - **select**:允许程序监视多个描述符,当其中任一描述符就绪(可读、可写或出现错误),select函数会返回,然后程序可以决定如何处理。 - **poll**:与select类似,但没有描述符数量的限制,适合大量连接的情况...

    Unix网络编程 第6章 I/O服用: select和poll函数 第6章.tar.gz 对本章的代码简易的练习

    I/O复用通过`select`和`poll`函数,使得单个进程可以监控多个文件描述符(包括套接字)的状态,以便于在它们准备就绪时进行读写操作。这种机制对于构建高并发、高效率的服务端程序至关重要。 首先,我们来详细了解`...

    windows下六种socket I/O模型示例

    Windows下的I/O复用主要通过`select`或`poll`函数实现。这些函数允许一个进程监视多个描述符,等待任意一个就绪,而不会阻塞。这种方式提高了程序的并发性。 4. **信号驱动I/O(Signal-Driven I/O)** 这种模型中...

    select I/O模型 客户端

    `select` I/O模型是一种在多路复用I/O中广泛使用的机制,它允许一个进程监控多个文件描述符(如套接字)的状态,以确定何时进行读写操作。在本篇中,我们将深入探讨`select` I/O模型在客户端应用中的运用,以及与...

    unix平台下I/O聚集和分离的一种方案

    Unix系统提供了多种I/O模型,如阻塞I/O、非阻塞I/O、I/O多路复用(如select、poll、epoll)、信号驱动I/O以及异步I/O。I/O聚集通常指的是在一个系统调用中处理多个文件描述符,而I/O分离则是在不同时间或通过不同...

    Socket I/O 模型的使用示例

    - `select`函数是最早的多路复用I/O机制,它可以监视多个文件描述符,等待它们中的任意一个就绪。当有活动发生时,`select`返回就绪的描述符集合。 - 在客户端和服务端代码中,`select`通常用于同时监听多个Socket...

    Linux下套接字I_O复用模型介绍.pdf

    1. **select**:是最早实现的I/O复用函数,它允许程序监控多个文件描述符,但存在一些限制,如文件描述符的数量有限(通常为1024个),并且在大量文件描述符中轮询效率较低。 2. **poll**:相对于select,poll没有...

    linux 设备驱动中的阻塞与非阻塞 I/O

    此外,还有多路复用I/O模型,如 select、poll 和 epoll,它们允许单个线程监视多个文件描述符,当其中任何一个描述符就绪时,系统会通知程序。这种方式特别适用于需要同时处理多个连接的服务器应用。 在编写设备...

    select选择模型 windows套接字I/O模型

    I/O模型主要有五种:同步阻塞、同步非阻塞、异步阻塞(即回调)、I/O复用和信号驱动I/O。其中,I/O复用模型就是select模型,它允许程序同时监控多个文件描述符(在Windows上,通常为套接字),等待它们就绪以便进行...

    socket I/O模型源代码

    本文将深入探讨五种主要的套接字I/O模型:阻塞I/O、非阻塞I/O、I/O复用(select/poll/epoll)、信号驱动I/O以及异步I/O,同时通过提供的源代码文件,我们可以更直观地理解这些模型的工作原理。 1. **阻塞I/O模型**...

    套接字I/O模型中的select模型源码

    在各种I/O模型中,`select`模型是一种广泛使用的机制,尤其在处理多路复用I/O时。本文将深入探讨`select`模型的工作原理、优缺点以及如何在实际代码中应用。 `select`模型是一种I/O多路复用技术,允许程序同时监控...

    I/O多路转接之select

    "准备工作代码"通常指的是为了使用select函数进行I/O多路复用而需要编写的初始化代码。这包括设置文件描述符集、定义超时时间以及调用select函数本身。下面,我们将详细讨论这个过程。 首先,你需要创建三个文件...

    1_WINSOCK的I/O模型_

    3. **I/O多路复用**:WINSOCK提供了`select()`和`poll()`函数来实现I/O多路复用。它们允许程序同时监视多个套接字,一旦有数据可读或可写,就通知程序。这对于群聊服务器特别有用,因为它可以高效地处理大量并发连接...

    iokey.rar_IO复用_Iok_Ioke_复用io_端口复用

    C语言的I/O复用通常涉及以下关键函数: - **select()**:用于监控文件描述符集合,直到某个描述符就绪或超时。 - **poll()**:与select类似,但提供了更灵活的结构来管理文件描述符。 - **epoll_create()**:创建一...

    IO 模型网络程序实验

    3. I/O复用模型(如select、poll、epoll):允许单个线程监视多个文件描述符,等待数据就绪后再进行读写。 4. 信号驱动I/O模型:在此模型中,当数据准备好时,系统会发送一个信号通知进程。 5. 异步非阻塞I/O模型...

    第四讲 I-O复用与套接口选项.ppt

    3. **I/O复用模型**:通过`select`或`poll`函数,进程可以在一组套接字上进行轮询,而不是在一个特定的I/O系统调用上阻塞。当有数据就绪时,这些函数会唤醒进程,然后进行实际的数据拷贝。 4. **信号驱动I/O模型...

    Winodws Socket I/O模型 电子书及附套代码

    2. **select函数**:select模型是一种多路复用I/O机制,它可以监控多个Socket,等待任意一个准备好读写操作。当有Socket可读或可写时,select函数会返回相应的描述符,从而避免了轮询检查的低效。 3. **事件驱动I/O...

Global site tag (gtag.js) - Google Analytics