高级I/O函数

套接字超时

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


猜你喜欢

转载自blog.csdn.net/amoscykl/article/details/80399073