企业财富库read_timeout,write_timeout,accept_timeout,connection_timeout企业财富库:select实现[超时]检测:read_timeout;write_timeout;connect_timeout;accept_timeout
https://blog.csdn.net/weixin_36750623/article/details/83307973
1.实现read超时检测:read_timeout
/** read_timeout-读超时检测函数,不含读操作 (即:判断[从fd套接字]中读数据,是否超时,不真正的读走数据) @fd:文件描述符 @wait_seconds:等待超时秒数,如果为0表示不检测超时 成功(未超时):返回0 失败:返回-1 超时:返回-1并且errno=ETIME_OUT */ int read_timeout(int fd, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set read_fdset; struct timeval timeout; FD_ZERO(&read_fdset); FD_SET(fd, &read_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; //select返回值三态 //1 若timeout时间到(超时),没有检测到读事件 ret返回=0 //2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理) //2-1 若返回-1,select出错 //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数 do { ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) ret = 0; } return ret; }
2.实现write超时检测:write_timeout
/** write_timeout-写超时检测函数,不含写操作 (即:判断[向fd套接字]中写数据,是否超时,不真正的写入数据) @fd:文件描述符 @wait_seconds:等待超时秒数,如果为0表示不检测超时 成功(未超时):返回0 失败:返回-1 超时:返回-1并且errno=ETIME_OUT */ int write_timeout(int fd, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set write_fdset; struct timeval timeout; FD_ZERO(&write_fdset); FD_SET(fd, &write_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) ret = 0; } return ret; }
3.实现connect超时检测:connect_timeout
1.在建立套接字(fd)以后默认是阻塞的(如果客户端连接服务器发生异常,则默认阻塞的情况下,返回时间是1.5RTT,大约100秒!–软件质量低下)
2.先把套接字通过fcntl变为非阻塞模型,再调用connect函数
[1]如果网络顺利,直接建立链接
[2]如果网络不好,则根据返回值判断:如果connect的返回值-1&&errno==EINPROGRESS,则表示客户端和服务器正在建立连接,需要等待一段时间才能建立链接(可以利用select监控该套接字是否可写来设定等待时间),进一步对select返回的结果判断是否可写。
[3]尽管select返回了套接字的可写状态,但不一定表示就是正确建立链接,(前面已经知道),导致select监控的套接字可读可写有两种情况
case1:真正的可读可写,即表示建立了连接
/** * activate_noblock - 设置I/O为非阻塞模式 * @fd: 文件描符符 */ int activate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd,F_GETFL); if(-1 == flags) { ret =flags; perror("fcntl"); return ret; } flags |= O_NONBLOCK; ret = fcntl(fd,F_SETFL,flags); if(ret == -1) { perror("fcntl(fd,F_SETFL,flags)"); return ret; } return ret; } /** * deactivate_nonblock - 设置I/O为阻塞模式 * @fd: 文件描符符 */ int deactivate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd,F_GETFL); if(-1 == flags) { ret =flags; perror("fcntl"); return ret; } flags &= ~O_NONBLOCK; ret = fcntl(fd,F_SETFL,flags); if(ret == -1) { perror("fcntl(fd,F_SETFL,flags)"); return ret; } return ret; } /** connect_timeout @fd:套接字 @addr:要连接的对方地址 @wait_seconds:等待超时秒数,如果为0表示正常模式 成功(未超时):返回0 失败:返回-1 超时:返回-1并且errno=ETIMEOUT */ static int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds) { int ret = 0; socklen_t addrlen = sizeof(struct sockaddr_in); fd_set connect_fdset; struct timeval timeout; int err; socklen_t socklen = sizeof(err); int sockoptret; if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的 { activate_nonblock(fd); } /*非阻塞 --成功:立马建立连接 --失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接 */ ret = connect(fd,(struct sockaddr*)addr,addrlen); if(ret < 0 && errno == EINPROGRESS) { FD_ZERO(&connect_fdset); FD_SET(fd,&connect_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { // 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中 ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接 }while(ret < 0 && errno == EINTR); if(ret == 0)//超时 { ret = -1; errno = ETIMEDOUT; } else if (ret < 0)//select出错 { return -1; } else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态 { /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/ /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */ sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态 if(sockoptret == -1)//getsockopt调用失败 { return -1; } if(err == 0)//若无错误发生,getsockopt()返回0,表示真正可写入/准备好 ret = 0; else{//表示套接字产生错误 errno = err; ret = -1; } } } if (wait_seconds > 0) { deactivate_nonblock(fd); } return ret; }
4.实现accept超时检测:accept_timeout
/** * accept_timeout - 带超时的accept * @fd: 套接字 * @addr: 输出参数,返回对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT */ int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds) { int ret; if (wait_seconds > 0) { fd_set accept_fdset; struct timeval timeout; FD_ZERO(&accept_fdset); FD_SET(fd, &accept_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == -1) return -1; else if (ret == 0) { errno = ETIMEDOUT; return -1; } } socklen_t addrlen = sizeof(struct sockaddr_in); if(addr!=NULL) ret=accept(fd,(struct sockaddr*)addr,&addrlen); else ret=accept(fd,NULL,NULL); if(ret==-1){ perror("accept"); } return ret; }
综合代码:
sockAPI.h
#ifndef _SOCKAPI_H_ #define _SOCKAPI_H_ #include <sys/types.h> /* See NOTES */ #include <sys/socket.h> #include <netinet/in.h> #include <netinet/ip.h> /* superset of previous */ #include <arpa/inet.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <fcntl.h> ssize_t readn(int fd,void *buf,size_t cnt); ssize_t writen(int fd,const void *buf,size_t cnt); int deactivate_nonblock(int fd); int activate_nonblock(int fd); int read_timeout(int fd, unsigned int wait_seconds); int write_timeout(int fd, unsigned int wait_seconds); int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds); int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds); #endif
sockAPI.c
#include "sockAPI.h" //@ssize_t:返回读的长度 若ssize_t<count 读失败 //@buf:接受数据内存首地址 //@count:接受数据长度 ssize_t readn(int fd,void *buf,size_t cnt) { size_t nleft = cnt;//定义剩余没有读取的个数 ssize_t nread = 0;//读取的个数 char * bufp = (char *)buf;//将参数接过来 while(nleft > 0)//当剩余需要读取的个数>0 { if((nread = read(fd,bufp,nleft)) < 0)//成功读取的个数小于0,则判断出错的原因 { //如果errno被设置为EINTR为被信号中断,如果是被信号中断继续, //不是信号中断则退出。 if(errno == EINTR) { continue; } perror("write"); exit(-1); } else if(nread == 0)//若对方已关闭 { return cnt - nleft; } bufp += nread;//将 字符串指针向后移动已经成功读取个数的大小。 nleft -= nread;//需要读取的个数=需要读取的个数-以及成功读取的个数 } return cnt; } //@ssize_t:返回写的长度 -1失败 //@buf:待写数据首地址 //@count:待写长度 ssize_t writen(int fd,const void *buf,size_t cnt) { size_t nleft = cnt;//需要写入的个数 ssize_t nwritten = 0;//已经成功写入的个数 char * bufp = (char *)buf;//接参数 while(nleft > 0)//如果需要写入的个数>0 { //如果写入成功的个数<0 判断是否是被信号打断 if((nwritten = write(fd,bufp,nleft)) < 0) { if(errno == EINTR)//信号打断,则继续 { continue; } perror("write"); exit(-1); } //需要写入的数据个数>0 //如果成功写入的个数为0 则继续 else if(nwritten == 0) { continue; } bufp += nwritten;//将bufp指针向后移动已经 nleft -= nwritten;//剩余个数 } return cnt; } /** read_timeout-读超时检测函数,不含读操作 (即:判断[从fd套接字]中读数据,是否超时,不真正的读走数据) @fd:文件描述符 @wait_seconds:等待超时秒数,如果为0表示不检测超时 成功(未超时):返回0 失败:返回-1 超时:返回-1并且errno=ETIME_OUT */ int read_timeout(int fd, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set read_fdset; struct timeval timeout; FD_ZERO(&read_fdset); FD_SET(fd, &read_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; //select返回值三态 //1 若timeout时间到(超时),没有检测到读事件 ret返回=0 //2 若ret返回<0 && errno == EINTR 说明select的过程中被别的信号中断(可中断睡眠原理) //2-1 若返回-1,select出错 //3 若ret返回值>0 表示有read事件发生,返回事件发生的个数 do { ret = select(fd + 1, &read_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) ret = 0; } return ret; } /** write_timeout-写超时检测函数,不含写操作 (即:判断[向fd套接字]中写数据,是否超时,不真正的写入数据) @fd:文件描述符 @wait_seconds:等待超时秒数,如果为0表示不检测超时 成功(未超时):返回0 失败:返回-1 超时:返回-1并且errno=ETIME_OUT */ int write_timeout(int fd, unsigned int wait_seconds) { int ret = 0; if (wait_seconds > 0) { fd_set write_fdset; struct timeval timeout; FD_ZERO(&write_fdset); FD_SET(fd, &write_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, NULL, &write_fdset, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == 0) { ret = -1; errno = ETIMEDOUT; } else if (ret == 1) ret = 0; } return ret; } /** * activate_noblock - 设置I/O为非阻塞模式 * @fd: 文件描符符 */ int activate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd,F_GETFL); if(-1 == flags) { ret =flags; perror("fcntl"); return ret; } flags |= O_NONBLOCK; ret = fcntl(fd,F_SETFL,flags); if(ret == -1) { perror("fcntl(fd,F_SETFL,flags)"); return ret; } return ret; } /** * deactivate_nonblock - 设置I/O为阻塞模式 * @fd: 文件描符符 */ int deactivate_nonblock(int fd) { int ret = 0; int flags = fcntl(fd,F_GETFL); if(-1 == flags) { ret =flags; perror("fcntl"); return ret; } flags &= ~O_NONBLOCK; ret = fcntl(fd,F_SETFL,flags); if(ret == -1) { perror("fcntl(fd,F_SETFL,flags)"); return ret; } return ret; } /** connect_timeout @fd:套接字 @addr:要连接的对方地址 @wait_seconds:等待超时秒数,如果为0表示正常模式 成功(未超时):返回0 失败:返回-1 超时:返回-1并且errno=ETIMEOUT */ int connect_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds) { int ret = 0; socklen_t addrlen = sizeof(struct sockaddr_in); fd_set connect_fdset; struct timeval timeout; int err; socklen_t socklen = sizeof(err); int sockoptret; if(wait_seconds > 0)//设置成非阻塞--因为文件描述符默认是阻塞的 { activate_nonblock(fd); } /*非阻塞 --成功:立马建立连接 --失败:ret < 0 && errno == EINPROGRESS,表示没有获取到链接 */ ret = connect(fd,(struct sockaddr*)addr,addrlen); if(ret < 0 && errno == EINPROGRESS) { FD_ZERO(&connect_fdset); FD_SET(fd,&connect_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { // 一但连接建立,则套接字就可写 所以connect_fdset放在了写集合中 ret = select(fd + 1,NULL,&connect_fdset,NULL,&timeout);//在规定时间内监控链接 }while(ret < 0 && errno == EINTR); if(ret == 0)//超时 { ret = -1; errno = ETIMEDOUT; } else if (ret < 0)//select出错 { return -1; } else if( ret == 1)//有两种情况会导致文件描述符变为可写入的状态/准备好的状态 { /* ret返回为1(表示套接字可写),可能有两种情况,一种是连接建立成功,一种是套接字产生错误,*/ /* 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 */ sockoptret = getsockopt(fd, SOL_SOCKET, SO_ERROR, &err, &socklen);//获取套接字的状态 if(sockoptret == -1)//getsockopt(可以修改十一种状态,诸如TIME_WAIT的时间等)调用失败 { return -1; } if(err == 0)//真正可写入/准备好 ret = 0; else{//-1表示套接字产生错误 errno = err; ret = -1; } } } if (wait_seconds > 0) { deactivate_nonblock(fd); } return ret; } /** * accept_timeout - 带超时的accept * @fd: 套接字 * @addr: 输出参数,返回对方地址 * @wait_seconds: 等待超时秒数,如果为0表示正常模式 * 成功(未超时)返回已连接套接字,超时返回-1并且errno = ETIMEDOUT */ int accept_timeout(int fd, struct sockaddr_in *addr, unsigned int wait_seconds) { int ret; if (wait_seconds > 0) { fd_set accept_fdset; struct timeval timeout; FD_ZERO(&accept_fdset); FD_SET(fd, &accept_fdset); timeout.tv_sec = wait_seconds; timeout.tv_usec = 0; do { ret = select(fd + 1, &accept_fdset, NULL, NULL, &timeout); } while (ret < 0 && errno == EINTR); if (ret == -1) return -1; else if (ret == 0) { errno = ETIMEDOUT; return -1; } } socklen_t addrlen = sizeof(struct sockaddr_in); if(addr!=NULL) ret=accept(fd,(struct sockaddr*)addr,&addrlen); else ret=accept(fd,NULL,NULL); if(ret==-1){ perror("accept"); } return ret; }
主函数: