TCPIP编程之select函数详解
前述:
linux下的I/O复⽤模型⽬前很多都已经不⽤select函数了,⽽是⽤epoll,但是为什么还需要了解select编程呢,其实是从两个⽅⾯考虑的:⼀是为了通过select去理解epoll,⽽是也会⽤到select函数。
函数原型:
SELECT(2) Linux Programmer's Manual SELECT(2)
NAME
select, pselect, FD_CLR, FD_ISSET, FD_SET, FD_ZERO - synchronous I/O
multiplexing
SYNOPSIS
/* According to POSIX.1-2001 */
#include <sys/select.h>
/* According to earlier standards */
#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);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
相关的函数⽐较多,先来说说select函数的参数:
(1) timeout:超时时间
struct timeval {
long tv_sec; //秒
long tv_usec; //微妙
}
这个参数有三种可能
a) 永久等待下去,将该参数设置为空指针
b) 等待固定⼀段时间,指针不为空且设置的秒或者毫秒值不为0,如果超时,select函数会返回0
c) 根本不等待,指针不为空且设置的秒或者毫秒值均为0
(2) 中间的三个参数readfds、writefds、exceptfds指定我们要让内核检测读、写和异常条件的描述符,如果我们对某⼀个的条件不感兴趣,就可以把它设置空指针。
⽬前⽀持的异常条件只有两个(下⾯的异常条件可以暂时不⽤理解):
a) 某个套接字的外带数据的到达
b) 某个已置为分组模式的伪终端等信息
select使⽤描述符集,通常是⼀个整数数组,其中每个整数中的每⼀位对应⼀个描述符。所以fd_set类型本质是其实是⼀个数组类型。
举个例⼦,以下代码⽤于定义⼀个fd_set类型的变量,然后设置需要让select检测的描述符1、4、5:
fd_set rset;
FD_ZERO(&rset); //初始化set,位的值为0
FD_SET(1, &rest); //设置,位的值为1
FD_SET(4, &rest);
FD_SET(5, &rest);
(3) nfds参数是指定待检测的描述符的个数,它的值是待检测的最⼤描述符加1,举个例⼦,需要检测的描述符为1、4、5、11,那么nfds的值就为12。
select函数返回值:
返回:若有就绪描述符则为其数⽬,若超时则为0,若出错则为-1
我们使⽤FD_ISSET宏来检测fd_set数据类型中的描述符,描述符集内任何与为就绪对应的位返回均清成0。为此,每次重新调⽤select函数时,我们都得再次把所有的描述符集内所关⼼的位置为1.
描述符就绪条件:
这⾥要注意的是,即使select返回套接字可读,也需要使⽤getsockopt检查是否有错误发⽣,因为如图⽰,有待处理的错误也是返回可读就绪。
最后,可以通过⼀个⽀持connect超时的函数来理解下select函数,或者查阅《unix⽹络编程》·卷1·第6章:I/O复⽤:
#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
int connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)
{
int flags, n, error;
socklen_t len;
fd_set rset, wset;
struct timeval tval;
/* 调⽤fcntl把套接字设置为⾮阻塞 */
flags = fcntl(sockfd, F_GETFL, 0);
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
/* 发起⾮阻塞connect。期望的错误是EINPROGRESS,表⽰连接建⽴已经启动但是尚未完成 */
error = 0;
if ( (n = connect(sockfd, addr, addrlen)) < 0)
if (errno != EINPROGRESS)
return(-1);
/* 如果⾮阻塞connect返回0,那么连接已经建⽴。当服务器处于客户端所在主机时这种情况可能发⽣ */ if (n == 0)
goto done; /* connect completed immediately */
/* 调⽤select等待套接字变为可读或可写,如果select返回0,那么表⽰超时 */
FD_ZERO(&rset);
FD_SET(sockfd, &rset);
wset = rset;
tval.tv_sec = nsec;
tval.tv_usec = 0;
if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {
close(sockfd); /* timeout */
errno = ETIMEDOUT;
return(-1);
}
/* 检查可读或可写条件,调⽤getsockopt取得套接字的待处理错误,如果建⽴成功,该值将为0 */
if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {
len = sizeof(error);
if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)
return(-1); /* Solaris pending error */
} else {
perror("select error: sockfd not set");
exit(1);
网络编程之delphi}
/* 恢复套接字的⽂件状态标志并返回 */
done:
fcntl(sockfd, F_SETFL, flags); /* restore file status flags */
if (error) {
close(sockfd); /* just in case */
errno = error;
return(-1);
}
return(0);
return(0);
}
int main()
{
// socket
struct sockaddr_in servaddr;
short port = 9999;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("113.107.231.211");
servaddr.sin_port = htons(port);
int timeout = 3;
if (connect_nonb(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in), 2) < 0) { perror("connect fail: ");
return(-1);
}
printf("connect success!\n");
return 0;
}
参考:《unix⽹络编程》·卷1
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论