網絡編程第十四章:套接字超時

在套接字上設置超時的方法有以下三種:
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;
				}
			}
		}
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章