套接字超时
套接字I/O操作上设置超时的方法有以下3种:
1.调用alarm,在指定超时期满时产生SIGALRM信号。
2.在select种阻塞等待I/O,一次代替直接阻塞在read或write调用上。
3.使用较新的SO_RCVTIMEO和SO_SNDTIMEO套接字选项。
使用SIGALRM为connect设置超时
以由调用者指定的超时上限调用connect。第四个参数为等待的秒数。
#include "unp.h"
static void connect_alarm(int);
int connect_timeo(int sockfd, const SA *saptr, socklen_t salen, int nsec)
{
sigfunc *sigfunc;
int n;
sigfunc = signal(SIGALRM, connect_alarm); //设置信号处理函数
if (alarm(nsec) != 0) //alarm闹钟函数,当设置时间到,内核产生一个SIGALRM信号
err_msg("connect_timeo: alarm was already set");
if ( (n = connect(sockfd, saptr, salen)) < 0) {
close(sockfd);
if (errno == EINTR);
errno = ETINEDOUT; //如果connect中端,把errno值改为ETIMEOUT,并关闭套接字。
}
alarm(0); //turn off the alarm
signal(SIGALRM,sigfunc);
return (n);
}
static void connect_alarm(int signo)
{
return ; //just interrupt the connect()
}
使用SIGALRM为recvfrom设置超时
改变dg_cli函数,通过调用alarm使得一旦在5秒钟内收不到任何应答就中断recvfrom
#include "unp.h"
static void sig_alrm(int);
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendlien[MAXLINE], recvline[MAXLINE + 1];
signal(SIGALRM, sig_alrm);
while(fgets(sendline, MAXLINE, fp) != NULL) {
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
alarm(5);
if ( (n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL)) < 0) {
if (errno == EINTR) //如果recvfrom被中断
fprintf(stderr, "socket timeout \n");
else
err_sys("recvfrom error");
} else {
alarm(0);
recvline[n] = 0; //null terminate
fputs(recvline,stdout);
}
}
}
static void sig_alrm(int signo) //signo为信号信息。如SIGINT等
{
return ;
}
使用select为recvfrom设置超时
#include "unp.h"
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;
return(select(fd+1, &rset, NULL, NULL, &tv)); //select等待该描述符变为可读,或者发生超时。
//返回值大于0,则描述符可读
}
/* 调用readable_timeo设置超时的dg_cli函数 */
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
while(fgets(sendline,MAXLINE,fp) != NULL ) {
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
if(readable_timeo(sockfd, 5) == 0) {
fprintf(stderr,"socket timeout \n");
}
else { //直到变为可读才调用recvfrom
m = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0; //null terminate;
fputs(recvline,stdout);
}
}
}
使用SO_RCVTIMEO套接字选项为recvfrom设置超时
本选项一旦设置到某个描述符,其超时设置将应用于该描述符上的所有读操作。
示例:使用SO_RCVTIMEO套接字选项的另一个版本的dg_cli函数
#include "unp.h"
void dg_cli(FILE *fp, int sockfd, const SA *pservaddr, socklen_t servlen)
{
int n;
char sendline[MAXLINE], recvline[MAXLINE + 1];
struct timeval tv;
tv.tv_sec = 5;
tv.tv._usec = 0;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
while (fgets(sendline, MAXLINE, fp) != NULL) {
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
if (n < 0) {
if (errno == EWOULDBLOCK) {
fprintf(stderr,"socket timeout \n");
continue;
} else
err_sys("recvfrom error");
}
recvline[n] = 0; //null terminate
fputs(recvline,stdout);
}
recv和send函数
类似标准的read和write函数,不过需要一个flags参数
#include<sys/socket.h>
ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
flags参数:
readv和writev函数
允许单个系统调用读入到或写出自一个或多个缓冲区。这些操作分别称为分散读和集中写。来自读操作的输入数据被分散到多个应用缓冲区中,而来自多个应用缓冲区的输出数据则被集中提供给单个写操作。
#include<sys/uio.h>
ssize_t readv(int filedes, const struct iovec *iov, int iovcnt);
ssize_t writev(int filedes, const struct iovec *iov, int iovcnt);
//若成功则返回读入或写出的字节数
第二个参数是指向某个iovec结构数组的一个指针,其中iovec结构在头文件<sys/uio.h>中定义:
struct iovec {
void *iov_base; //starting address of buffer
size_t iov_len; //size of buffer
};
iovec结构数组中元素的数目存在限制。
readv和writev这两个函数可用于任何描述符,不仅限套接字。
recvmsg和sendmsg函数
这两个函数是最通用的I/O函数。所有read,readv,recv,recvfrom都可以替换成recvmsg调用。各种输出函数也可以替换成sendmsg调用。
#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr *msg, int flags);
这两个函数大部分参数都封装到一个msghdr结构中:
struct msghdr {
void *msg_name; //protocol address 协议地址
socklen_t msg_namelen; //地址长度
struct iovec *msgiov;
int msg_iovlen;
void *msg_control;
socklen_t msg_controllen;
int msg_flags;
}
mas_name和msg_namelen这两个成员用于套接字未连接的场合(如未连接UDP套接字);
msg_iov和msg_iovlen这两个成员指定输入或输出缓冲区数组。
下图展示了一个msghdr结构以及它指向的各种信息。图中假设进程即将对一个UDP套接字调用recvmsg:
对于这两个函数,区分两个标志变量,一个是传递值的flags参数,一个是所传递msghdr结构的msg_flags成员,传递的是引用,因此传递给函数的是该结构的地址。
只有recvmsg使用msg_flags成员。recvmsg被调用时,flags参数被复制到msg_flags成员,并由内核使用其值驱动接收处理过程。
flags参数值以及recvfrom可能返回的msg_flag成员值:
recvmsg返回的7个标志:
MSG_BCAST:它的返回条件是本数据报作为链路层广播收取或者其目的IP地址是一个广播地址。
MSG_MCAST:它的返回条件是本数据报作为链路层多播收取
MSG_TRUNC:本标志的返回条件是本数据报被截断
MSG_CTRUNC:本标志的返回条件是本数据报的辅助数据被截断
MSG_EOR:本标志的返回条件是返回数据结束的一个逻辑记录。
MSG_OOB:本标志绝不为TCP带外数据返回。
MSG_NOTIFICATION:本标志由SCTP接受者返回。