网络编程第十四章:套接字超时

在套接字上设置超时的方法有以下三种:
1 调用alarm,产生sigalarm信号,
2 在select阻塞等待IO
3 使用SO_RCVTIMEO和SO_RNDTIMEO选项。这个方法的问题在于并非所有实现都支持这个两个选项。

alarm:
#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_timeo);//返回的是信号处理函数
	if(alarm(nsec)!=0)
		err_msg("alarm已经被设置");
	
	if((n= connect(sockfd,saptr,salen))<0){
		close(sockfd);
		if(errno==EINTR){
			errno = ETIMEDOUT;
		}
	}
	alarm(0);
	Signal(SIGALRM,sigfunc);
	return n;
}
static void connect_alarm(int signo){
	return ;
}//这个信号处理函数只是简单的return了一下

//在多线程程序中正确使用信号异常困难,建议在未线程化或者单线程中使用这个技术。
使用sigalrm为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  sendline[MAXLINE],recvline[MAXLINE];
	signal(SIGALRM,sig_alrm);
	
	while(fgets(sendline,MAXLINE,fp)!=NULL){
		sendto(sockfd,sendline,strlen(sendline),0,pseraddr,servlen);
		alarm(5);
		if((n=recvfrom(sockfd,recvline,MAXLINE,0,NULL,NULL))<0){
			if(errno==EINTR) fprintf(stderr,"*socket timeout\n");
			else err_sys("recvform error");			
		}else{
			alarm(0);
			recvline[n]=0;
			fputs(recvline,stdout);
		}
	}
}
static void sig_alrm(int signo){
	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;
	tc.tc_usec =0;
	
	return (select(fd+1,&rset,NULL,NULL,&tv));//出错的时候返回-1,超时返回0
}//本函数不支持读操作,只是等待描述符变为可读。

等待描述符变为可写的函数:

#include"unp.h"
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);
		if(readable_timeo(sockfd,5)==0){//只有当描述符可以读的时候才调用recvfrom
			fprintf(stderr,"*socket timeout");
		}else{
			n = recvfrom(sockfd,recvline,MAXLINE,0,NULL,NULL);
			recvline[n]=0;
			fputs(recvline,stdout);
		}
	}
}
使用套接字选项SO_ERCVTIMO为recvfrom设置超时

一旦设置超时,该套接字选项讲应用于该套接字所有的读操作. 不能为connect设置超时.

#include "unp.h"
void dg_cli(FIEL*fp,const SA*pservaddr,socklen_t servlen){
	int n;
	char sendline[MAXLINE],recvline[MAXLINE];
	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");
				continue;
			}else err_sys("recvfrom error");
		}
		recvline[n]=0;
		fputs(recvline,stdout);
	}
}
recv和send函数:
#include"unp.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值来确定:目的主机是否在本地网络上,阻塞IO.发送带外数据,查看已经读取的数据,返回读操作.

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);
其中
struct iovec{
	void *iov_base;
	size_t iov_len; 
}

这两个函数可以用于任何描述符,而不是仅限于套接字,writev是一个原子操作.

recvmsg和sendmsg函数

这是最通用的IO函数,

#include <sys/socket.h>
ssize_t recvmsg(int sockfd,struct msghdr *msg,int flags)
ssize_t sendmsg(int sockfd,struct msghdr *msg,int flags)

大部分参数在msgdr结构

struct msghdr{
	void *msg_name;//指向套接字地址结构
	socklen_t msg_namelen;//长度
	struct *msg_iov;
	int msg_iovlen;//输出输入缓冲区数组,
	void *msg_control;
	socklen_t msg_controllen;//辅助数据
	int msg_flags;
}

只有recvmsg使用msgf_lags,flags值被复制到内核驱动接受处理过程,内核还更新msg_flags的值
sendmsg忽略msg_flags成员,直接只用值参数flags;

辅助数据

辅助数据由sendmsg和recvmsg的msghdr的两个成员发送和接受.
辅助数据有一个或者多个辅助数据对象构成

struct cmsghdr{
	socklen_t cmsg_len;
	int cmsg_level;
	int cmsg_type;
}
排队的数据量:详见313
套接字和标准IO.

除了unix IO 方法之外,还可以使用标准IO函数库,标准IO函数库可用于套接字,
1 通过调用fdopen,可以从任意一个描述符建立一个标准IO流,调用fileno可以获取一个标准IO流的描述符
2 标准IO流也可以是全双工的,只要以R+类型打开,但是必须输出之后fflush,fseek,fsetpos,才能接着调用一个输入函数.类似的,调用一个输入之后必须插入一个fseek,fsetpos才能调用一个输出,除非遇到一个eof;但fseek和fsetpos应用到套接字上会出错.
3 解决2的最好方法是给一个给定描述符打开两个标准IO.读和写

#include"unp.h"
void str_echo(int sockfd){
	char line[MAXLINE];
	FILE *fpin,*fput;
	fpin = fdopen(sockfd,"r");
	fput = fdopen(sockfd,"w");
	while(fgets(line,MAXLINE,fpin)!=NULL){
		fputs(line,fpout);
	}
}

P315缓冲问题需要细读.

高级轮询技术:

其他为套接字操作设计时间限制的其他方法,但是并不是所有实现都支持.

/dev/poll接口提供了可扩展的轮询大量描述符的方法,轮询进程预先设置好查询描述符的列表,然后进入一个循环等待事件发生,每次循环回来不再次设置该列表.

打开/dev/poll,初始化一个结构,write向/dev/poll设备上写这个结构数组把它传递给内核,然后用ioct1阻塞自身等待事件发生.
传递给ioct1的结构:

struct dvpoll{
	struct pollfd*dpfds;//ioct1返回的时候存放一个pollfd数组,
	int dp_nfds;//缓冲区大小.
	int dp_timeout;
}

例子:

#include	"unp.h"
#include	<sys/devpoll.h>

void
str_cli(FILE *fp, int sockfd)
{
	int		stdineof;
	char		buf[MAXLINE];
	int		n;
	int		wfd;
	struct pollfd	pollfd[2];
	struct dvpoll	dopoll;
	int		i;
	int		result;

	wfd = Open("/dev/poll", O_RDWR, 0);
	//填写好pollfd数组之后,将它传递给内核
	pollfd[0].fd = fileno(fp);
	pollfd[0].events = POLLIN;
	pollfd[0].revents = 0;
	pollfd[1].fd = sockfd;
	pollfd[1].events = POLLIN;
	pollfd[1].revents = 0;
	Write(wfd, pollfd, sizeof(struct pollfd) * 2);

	stdineof = 0;
	
	for ( ; ; ) {
		/* block until /dev/poll says something is ready */
		dopoll.dp_timeout = -1;
		dopoll.dp_nfds = 2;
		dopoll.dp_fds = pollfd;
		result = Ioctl(wfd, DP_POLL, &dopoll);//阻塞到ioct1调用上,

		/* loop through ready file descriptors */
		for (i = 0; i < result; i++) {
			if (dopoll.dp_fds[i].fd == sockfd) {
				/* socket is readable */
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
					if (stdineof == 1)
						return;		/* normal termination */
					else
						err_quit("str_cli: server terminated prematurely");
				}

				Write(fileno(stdout), buf, n);
			} else {
				/* input is readable */
				if ( (n = Read(fileno(fp), buf, MAXLINE)) == 0) {
					stdineof = 1;
					Shutdown(sockfd, SHUT_WR);	/* send FIN */
					continue;
				}

				Writen(sockfd, buf, n);
			}
		}
	}
}
kqueue借口

本接口允许进程向内核注册描述所关注的kqueue事件的事件过滤器.

#include<sys/types.h>
#include<sys/event.h>
#include<sys/time.h>

int kqueue(void);//返回一个kqueue描述符,用于后续kevent调用中
int kevent(
	int kq,
	const struct kevent*changlist,
	int nchangs;//对所关注事件做出的更改
	struct kevent *evnelist,
	int nevents;//条件所触发的事件数组作为函数返回值返回,
	const struct timespec *timeout;//超时设置.
);
#include	"unp.h"

void
str_cli(FILE *fp, int sockfd)
{
	int				kq, i, n, nev, stdineof = 0, isfile;
	char			buf[MAXLINE];
	struct kevent	kev[2];
	struct timespec	ts;
	struct stat		st;

	isfile = ((fstat(fileno(fp), &st) == 0) &&
			  (st.st_mode & S_IFMT) == S_IFREG);

	EV_SET(&kev[0], fileno(fp), EVFILT_READ, EV_ADD, 0, 0, NULL);
	EV_SET(&kev[1], sockfd, EVFILT_READ, EV_ADD, 0, 0, NULL);

	kq = Kqueue();
	ts.tv_sec = ts.tv_nsec = 0;
	Kevent(kq, kev, 2, NULL, 0, &ts);

	for ( ; ; ) {
		nev = Kevent(kq, NULL, 0, kev, 2, NULL);

		for (i = 0; i < nev; i++) {
			if (kev[i].ident == sockfd) {	/* socket is readable */
				if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
					if (stdineof == 1)
						return;		/* normal termination */
					else
						err_quit("str_cli: server terminated prematurely");
				}

				Write(fileno(stdout), buf, n);
			}

			if (kev[i].ident == fileno(fp)) {  /* input is readable */
				n = Read(fileno(fp), buf, MAXLINE);
				if (n > 0)
					Writen(sockfd, buf, n);

				if (n == 0 || (isfile && n == kev[i].data)) {
					stdineof = 1;
					Shutdown(sockfd, SHUT_WR);	/* send FIN */
					kev[i].flags = EV_DELETE;
					Kevent(kq, &kev[i], 1, NULL, 0, &ts);	/* remove kevent */
					continue;
				}
			}
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章