一、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;
}