1. 套接口超时
在涉及套接口的I/O操作上设置超时的方法有3个:
- 调用alarm,它在指定超时期满时产生SIGALRM信号;【适用于任何描述字】
- 在select中阻塞等待I/O(select有内置的时间限制),以此代替直接阻塞在read或write调用上;
- 使用SO_RCVTIMEO和SO_SNDTIMEO套接口选项,但并非所有实现都支持这两个套接口选项。【仅适用于套接口描述字】
1.2 使用SIGALRM为connect设置超时
- 它以由调用者指定的超时上限调用connect。前3个参数用于调用connect,第4个参数是等待的秒数。
static void connect_alarm(int);
int connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
Sigfunc *sigfunc;
int n;
// 为SIGALRM建立一个信号处理函数。现有信号处理函数得以保存,以便在本函数结束时恢复它
sigfunc = Signal(SIGALRM, connect_alarm);
// alarm的返回值是这个报警时钟的当前剩余秒数
if (alarm(nsec) != 0)
err_msg("connect_timeo: alarm was already set");
// 如果本调用被中断,即返回EINTR错误,就设置errno = ETIMEDOUT
if ( (n = connect(sockfd, saptr, salen)) < 0)
{
close(sockfd); // 关闭套接口,以防三路握手继续进行
if (errno == EINTR)
errno = ETIMEDOUT;
}
alarm(0); // turn off the alarm
Signal(SIGALRM, sigfunc); //恢复原来的信号处理函数
return(n);
}
static void connect_alarm(int signo)
{
// return语句将中断进程主控制流中那个未决的connect调用,使得它返回一个EINTR错误
return; // just interrupt the connect()
}
1.3 使用SIGALRM为recvfrom设置超时
- 本函数通过调用alarm使得一旦在5秒内收不到任何应答就中断recvfrom.
static void sig_alarm(int);
void dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
Signal(SIGALRM, sig_alrm);
while (Fgets(sendline, MAXLINE, fp) != NULL)
{
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
// 每次调用recvfrom前调用设置了5秒钟的alarm
alarm(5);
// 如果recvfrom被信号处理函数中断,则输出信息后继续执行。
// 如果读到一行来自服务器的文本,则关闭报警时钟并输出服务器的应答
if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0 NULL, NULL)) < 0)
{
if (error == EINTR)
fprintf(stderr, "socket timeout\n");
else
err_sys("recvfrom error");
}
else {
alarm(0);
recvline[n] = 0;
Fputs(recvline, stdout);
}
}
}
static void sig_alarm(int signo)
{
return;
}
1.4 使用select为recvfrom设置超时
- select等待该描述字变为可读,或发生超时。出错返回-1,超时返回0,否则返回的正值为已就绪描述字的数目。
- 使用Readable_timeo函数,所关注的描述字已变为可读后,才可以调用recvfrom,这保证了recvfrom函数不会阻塞。
int readable_timeo(int fd, int sec)
{
fd_set rset;
struct timeval tv;
FD_ZERO(&rset);
FD_SET(fd, &rset);
tv.tv_sec = sec;
tv.tv_usec = 0;
// select等待该描述字变为可读,或发生超时。
// 出错返回-1,超时返回0,否则返回的正值为已就绪描述字的数目
return (select(fd+1, &rset, NULL, NULL, &tv));
}
void dg_cli (FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE];
while (Fgets(sendline, MAXLINE, fp) != NULL)
{
Sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
// 使用Readable_timeo函数,所关注的描述字已变为可读后,才可以调用recvfrom,
// 这保证了recvfrom函数不会阻塞
if (Readable_timeo(sockfd, 5) == 0)
fprintf(stderr, "socket timeout\n", );
else
{
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0;
Fputs(recvline, stdout);
}
}
}
1.5 recvmsg和sendmsg函数
- 这两个函数是最通用的I/O函数
struct msghdr
{
void *msg_name; // 协议地址
// msg_namelen对于sendmsg是一个值参数,对于recvmsg是值-结果参数
socklen_t msg_namelen; // 协议地址大小
struct iovec *msg_iov; // 输入/输出缓冲区数组
int msg_iovlen; // msg_iov的元素
void *msg_control; // ancillary data
socklen_t msg_controllen; // length of ancillary data
int msg_flags; // flags returned by recvmsg()
};
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
1.6 套接字和标准I/O
标准I/O函数库可用于套接字,不过需要考虑以下几点:
- 通过调用fdopen,可以从任何一个描述符创建出一个标准I/O流。类似地,通过调用fileno,可以获取一个给定标准I/O流对应的描述符。但select只能用于描述符,因此不得不获取那个标准I/O流的描述符。
- 以r+类型打开标准I/O流,意味着进行读写,即其可以是全双工的。然而在标准I/O流上,必须在调用一个输出函数之后插入fflush、fseek、fsetpos或rewind调用才能接着调用一个输入函数。【同理,输入函数->fseek、fsetpos或rewind | EOF->输出函数 】
解决办法: 为一个套接字打开两个标准I/O流:一个用于读、一个用于写。
1.6.1 使用标准I/O的str_echo函数
void str_echo(int sockfd)
{
char line[MAXLINE];
FILE *fpin, *fpout;
fpin = Fdopen(sockfd, "r");
fpout = Fdopen(sockfd, "w");
while (Fgets(line, MAXLINE, fpin) != NULL)
Fputs(line, fpout);
}
标准I/O函数库执行以下三类缓冲:
- **完全缓冲:**①缓冲区满;②进程显式调用fflush;③进程调用exit终止自身等3种情况时,才发生I/O。【缓冲区大小8192=8KB】
- 行缓冲:
- 不缓冲: