在套接字上設置超時的方法有以下三種:
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;
}
}
}
}
}