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