select函数是一个有着较复杂返回值的系统级函数,如果你使用过它,你就会知道这个返回值是一个按照文件描述符分成三类的系统级数据结构。下面我们将从多个方面对select函数的返回值做详细的阐述。
一、返回值格式及其含义
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
select函数的返回参数有3个,分别是readfds、writefds、以及exceptfds。来了解下这三个参数具体代表什么意思:
- readfds是一个集合,其中包含一些文件描述符,在这些文件描述符中存在一些可读数据。
- writefds是一个集合,其中包含一些文件描述符,在这些文件描述符中可以进行写操作而不会被阻塞。
- exceptfds是一个集合,其中包含一些文件描述符,表示这些文件描述符的异常条件(如关闭连接、收到信号等)已经发生。
在接下来的小节中,我们会围绕这3个参数分别展开讲解,深入探究select函数的返回值的更多细节。
二、readfds的使用方法
readfds是select函数返回值的第一个参数,它的具体含义是有数据可以读取的文件描述符集合。当调用select函数时,会一直阻塞直到readfds中至少有一个文件描述符被设置,表示它们中至少有一个可以进行读操作且不会被阻塞。
下面是一个获取TCP/IP连接数据的例子:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(12345); inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); fd_set readSet; FD_ZERO(&readSet); FD_SET(sockfd, &readSet); if (select(sockfd + 1, &readSet, NULL, NULL, NULL) < 0) { // TODO: 错误处理 } char buffer[1024]; recv(sockfd, buffer, sizeof(buffer), 0);
在上述代码中,我们使用了select函数来等待从sockfd连接中读取数据。通过FD_ZERO和FD_SET将需要等待的文件描述符(sockfd)设置到readSet中,再将readSet作为select函数的参数,使其在readSet中有数据时返回。
三、writefds的使用方法
writefds是select函数返回值的第二个参数,它的具体含义是可以进行写入操作的文件描述符集合。当调用select函数时,会一直阻塞直到writefds中至少有一个文件描述符被设置,表示它们中至少有一个可以进行写操作且不会被阻塞。
下面是一个获取TCP/IP连接数据的例子:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(12345); inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); connect(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); fd_set writeSet; FD_ZERO(&writeSet); FD_SET(sockfd, &writeSet); if (select(sockfd + 1, NULL, &writeSet, NULL, NULL) < 0) { // TODO: 错误处理 } char buffer[] = "hello world"; send(sockfd, buffer, strlen(buffer), 0);
在上述代码中,我们使用了select函数来等待从sockfd连接中写入数据。通过FD_ZERO和FD_SET将需要等待的文件描述符(sockfd)设置到writeSet中,再将writeSet作为select函数的参数,使其在writeSet中有数据时返回。
四、exceptfds的使用方法
exceptfds是select函数返回值的第三个参数,它的具体含义是异常文件描述符集合。当调用select函数时,会一直阻塞直到exceptfds中至少有一个文件描述符被设置,表示这些文件描述符有异常条件发生(如关闭连接、收到信号等)。
下面是一个监听TCP/IP连接异常的例子:
int sockfd = socket(AF_INET, SOCK_STREAM, 0); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_port = htons(12345); inet_pton(AF_INET, "127.0.0.1", &serverAddr.sin_addr); bind(sockfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); listen(sockfd, 1024); int connfd = accept(sockfd, NULL, NULL); fd_set exceptSet; FD_ZERO(&exceptSet); FD_SET(connfd, &exceptSet); if (select(connfd + 1, NULL, NULL, &exceptSet, NULL) < 0) { // TODO: 错误处理 } if (FD_ISSET(connfd, &exceptSet)) { // TODO: 处理异常 }
在上述代码中,我们使用了select函数来等待从connfd连接中的异常条件。通过FD_ZERO和FD_SET将需要等待的文件描述符(connfd)设置到exceptSet中,再将exceptSet作为select函数的参数,使其在exceptSet中有数据时返回。然后我们再使用FD_ISSET来判断是否有异常发生。
五、超时参数timeout的使用
select函数的最后一个参数是timeout,用于设置超时时间。它可以为NULL(表示一直阻塞到有数据到来),也可以设置为一个指向timeval结构体的指针(指定一个等待时间,在等待时间内如果没有数据到来,则select函数会超时并返回)。
下面是一个等待超时的例子:
fd_set readSet; FD_ZERO(&readSet); FD_SET(fileno(stdin), &readSet); struct timeval tv; tv.tv_sec = 5; tv.tv_usec = 0; if (select(fileno(stdin) + 1, &readSet, NULL, NULL, &tv) < 0) { // TODO: 错误处理 } if (FD_ISSET(fileno(stdin), &readSet)) { // TODO: 处理输入 } else { // TODO: 处理超时 }
在上述代码中,我们使用了select函数来等待从标准输入中的数据。通过FD_ZERO和FD_SET将需要等待的文件描述符(fileno(stdin))设置到readSet中,再将readSet作为select函数的参数,timeout设置为5秒时间。如果在5秒之内没有数据到来,则select函数会超时并返回。如果在5秒之内有数据到来,则FD_ISSET会返回真。
六、总结
本文从select函数的返回值格式及其含义、readfds的使用方法、writefds的使用方法、exceptfds的使用方法以及超时参数timeout的使用这五个方面对select函数的返回值做了深入的探索。以后在使用select函数时,希望读者们能够更加深入地理解其返回值,从而更好地应用它。