赞
踩
一、alarm函数设置超时
它的主要功能是设置信号传送闹钟。
信号SIGALRM在经过seconds指定的秒数后传送给目前的进程,如果在定时未完成的时间内再次调用了alarm函数,则后一次定时器设置将覆盖前面的设置,当seconds设置为0时,定时器将被取消。
它返回上次定时器剩余时间,如果是第一次设置则返回0。
void sigHandlerForSigAlrm(int signo) { return ; } signal(SIGALRM, sigHandlerForSigAlrm); alarm(5); int ret = read(sockfd, buf, sizeof(buf)); if (ret == -1 && errno == EINTR) { // 阻塞并且达到了5s,超时,设置返回错误码 errno = ETIMEDOUT; } else if (ret >= 0) { // 正常返回(没有超时), 则将闹钟关闭 alarm(0); }
(二)套接字选项: SO_SNDTIMEO, SO_RCVTIMEO,调用setsockopt设置读/写超时时间
//示例: read超时
int seconds = 5;
if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &seconds, sizeof(seconds)) == -1)
err_exit("setsockopt error");
int ret = read(sockfd, buf, sizeof(buf));
if (ret == -1 && errno == EWOULDBLOCK)
{
// 超时,被时钟信号打断
errno = ETIMEDOUT;
}
SO_RCVTIMEO是接收超时,SO_SNDTIMEO是发送超时。这种方式也不经常使用,因为这种方案不可移植,并且有些套接字的实现不支持这种方式。
(三)使用select函数实现超时
select的函数提供了时间参数,可用来控制超时。data.h头文件是实现了相关的超时API。
/* * data.h * * Created on: 2020年2月21日 * */ #ifndef SRC_DEMO_DATA_H_ #define SRC_DEMO_DATA_H_ #include <stdio.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> #include <fcntl.h> /** 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 */ 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); } /* 1.在建立套接字(fd)以后默认是阻塞的(如果客户端连接服务器发生异常,则默认阻塞的情况下,返回时间是1.5RTT,大约100秒!–软件质量低下) 2.先把套接字通过fcntl变为非阻塞模型,再调用connect函数 [1]如果网络顺利,直接建立链接 [2]如果网络不好,则根据返回值判断:如果connect的返回值-1&&errno==EINPROGRESS,则表示客户端和服务器正在建立连接,需要等待一段时间才能建立链接(可以利用select监控该套接字是否可写来设定等待时间),进一步对select返回的结果判断是否可写。 [3]尽管select返回了套接字的可写状态,但不一定表示就是正确建立链接,(前面已经知道),导致select监控的套接字可读可写有两种情况 case1:真正的可读可写,即表示建立了连接 case2:建立套接字产生错误,会返回写失败信息,造成可写入的状态。 此时错误信息不会保存至errno变量中,因此,需要调用getsockopt来获取。 非阻塞 --成功:立马建立连接 --失败: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; } /** * 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; } #endif /* SRC_DEMO_DATA_H_ */
//服务器 #include <limits.h> // for OPEN_MAX #include <sys/types.h> #include <netinet/in.h> #include <sys/socket.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <poll.h> #include <unistd.h> #include "data.h" #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) #define MAXLINE 1024 #define SERV_PORT 5000 #define OPEN_MAX 1024 #define INFTIM -1 //writen函数 //说明:此函数解决了缓冲区数据发送溢出的处理; //@ssize_t:-1表示返回错误,0表示收到FIN信号,>0表示数据的长度 //@fd:文件描述符 //@buf:待写数据首地址 //@nByte:待写长度 ssize_t writen(int fd, void *buf, size_t nBytes) { size_t nleft = nBytes; char *buf_p = (char*)buf; int nwritten = 0; while(nleft > 0) { nwritten = write(fd, buf_p, nleft); if (nwritten<0 && errno==EINTR) nwritten = 0; else return -1; nleft -= nwritten; buf_p += nwritten; } return nBytes; } int main(int argc, char **argv) { int i, maxi, listenfd, connfd, sockfd; int nready; ssize_t n; char buf[MAXLINE]; socklen_t clilen; struct pollfd client[OPEN_MAX]; struct sockaddr_in cliaddr, servaddr; //1.创建套接字 if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) ERR_EXIT("socket error"); //调用上边的宏 memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(SERV_PORT); //2.设置套接字属性 int on = 1; if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) ERR_EXIT("setsockopt error"); //3.绑定 if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0) ERR_EXIT("bind error"); //4.监听 if (listen(listenfd, SOMAXCONN) < 0) //listen应在socket和bind之后,而在accept之前 ERR_EXIT("listen error"); client[0].fd = listenfd; client[0].events = POLLIN; for (i = 1; i < OPEN_MAX; ++i) client[i].fd = -1; // indicates available entry maxi = 0; // max index into client[] array while (true) { nready = poll(client, maxi + 1, INFTIM); if(nready < 0) { perror("poll error"); break; } else if(nready == 0) { continue; } if (client[0].revents & POLLIN) { // new client connection clilen = sizeof(cliaddr); connfd = accept_timeout(listenfd, &cliaddr, 5); if (connfd == -1 && errno == ETIMEDOUT) { printf("accept timeout\n"); continue; } else if (connfd == -1) { ERR_EXIT("accept error"); } for (i = 1; i < OPEN_MAX; ++i) { if (client[i].fd < 0) { client[i].fd = connfd; // save descriptor break; } } if (OPEN_MAX == i) { ERR_EXIT("too many clients"); } client[i].events = POLLIN; if (i > maxi) maxi = i; // max index in client[] array if (--nready <= 0) continue; // no more readable descriptors } for (i = 1; i <= maxi; ++i) { // check all clients for data if ( (sockfd = client[i].fd) < 0) continue; if (client[i].revents & (POLLIN | POLLERR)) { int ret = read_timeout(sockfd, 5); if (ret == 0) { if ( (n = read(sockfd, buf, MAXLINE)) >= 0) { if (errno == ECONNRESET) { // connection reset by client close(sockfd); client[i].fd = -1; } else if (0 == n) { // connection closed by client close(sockfd); client[i].fd = -1; } else { ret = write_timeout(sockfd, 5); if (ret == 0) writen(sockfd, buf, n); else if (ret == -1 && errno == ETIMEDOUT) { printf("write timeout\n"); continue; } } if (--nready <= 0) break; // no more readable descriptors } else { ERR_EXIT("read error"); } } else if (ret == -1 && errno == ETIMEDOUT) { printf("read timeout\n"); continue; } } } } return 0; }
//客户端 #include <stdio.h> #include <sys/types.h> #include <sys/socket.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <arpa/inet.h> #include <netinet/in.h> #include <string.h> #include <poll.h> #include "data.h" #define ERR_EXIT(m) \ do { \ perror(m); \ exit(EXIT_FAILURE); \ } while (0) #define MAXLINE 1024 #define SERV_PORT 5000 int main(void) { int sock; if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) // listenfd = socket(AF_INET, SOCK_STREAM, 0) ERR_EXIT("socket error"); struct sockaddr_in servaddr; memset(&servaddr, 0, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = connect_timeout(sock, &servaddr, 5); if (ret == -1 && errno == ETIMEDOUT) ERR_EXIT("connect timeout"); else if (ret == -1) ERR_EXIT("connect error"); struct sockaddr_in localaddr; char cli_ip[20]; socklen_t local_len = sizeof(localaddr); memset(&localaddr, 0, sizeof(localaddr)); if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 ) ERR_EXIT("getsockname error"); inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip)); printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port)); struct pollfd p_fds[2]; int fd_stdin = fileno(stdin); p_fds[0].fd = fd_stdin; p_fds[0].events = POLLIN; p_fds[0].revents = 0; p_fds[1].fd = sock; p_fds[1].events = POLLIN; p_fds[1].revents = 0; int nready; int maxfd; if (fd_stdin > sock) maxfd = fd_stdin; else maxfd = sock; char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while (true) { nready = poll(p_fds, 2, -1); if(nready < 0) ERR_EXIT("poll"); else if(nready == 0) { printf("timeout\n"); continue; } else { if (p_fds[1].revents & POLLIN) { int ret = read(sock, recvbuf, sizeof(recvbuf)); if (ret == -1) ERR_EXIT("read error"); else if (ret == 0 || errno == ECONNRESET) //服务器关闭 { p_fds[1].fd = -1; break; } fputs(recvbuf, stdout); memset(recvbuf, 0, sizeof(recvbuf)); } if (p_fds[0].revents & POLLIN) { if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL) break; write(sock, sendbuf, strlen(sendbuf)); memset(sendbuf, 0, sizeof(sendbuf)); } } } close(sock); return 0; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。