套接字超时
套接字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接受者返回。