一、超时检测

1.1 概念

1> 在网络通信中,有很多函数是阻塞函数,会导致进程的阻塞,例如:accept、recv、recvfrom、等等

2> 为了避免进程在阻塞函数处,无休止的等待,我们可以设置一个超时时间,当时间超时后,会从阻塞函数处立即返回,继续运行后续程序

1.2 自带超时检测的函数

1> select

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<myhead.h>

int main(int argc, const char *argv[])
{
//准备一个容器
fd_set readfds;
//清空容器
FD_ZERO(&readfds);
//将0号文件描述符放入集合中
FD_SET(0, &readfds);

int num; //要输入的变量

struct timeval tv = {5, 0};

while(1)
{
//将时间重置
tv.tv_sec = 5;
tv.tv_usec = 0;

//阻塞检测集合中是否有事件产生
int res = select(1, &readfds, NULL, NULL, &tv);
if(res == -1)
{
perror("select error");
return -1;
}else if(res == 0)
{
printf("time out\n");
return -1;
}

//判断是否触发了终端输入事件
if(FD_ISSET(0, &readfds))
{
scanf("%d", &num);
printf("num = %d\n", num);
}

}


return 0;
}

2> poll

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include<myhead.h>

int main(int argc, const char *argv[])
{

//定义一个等待容器数组
struct pollfd pfd;

//给结构体填充内容
pfd.fd = 0;
pfd.events = POLLIN;

int num;

while(1)
{
int res = poll(&pfd, 1, 5000);

if(res == -1)
{
perror("select error");
return -1;
}else if(res == 0)
{
printf("time out\n");
return -1;
}

//判断是否触发了终端输入事件
if(pfd.revents == POLLIN)
{
scanf("%d", &num);
printf("num = %d\n", num);
}

}


return 0;
}

1.3 不带超时参数的函数

1> setsockopt

1
2
3
4
5
 int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
功能:设置套接字属性
想要设置超时时间,在应用层,level:SOL_SOCKET, 属性名:SO_RCVTIMEO
值为一个struct timeval类型的变量
如果超时了,那么对应的函数会返回-1,但是错误码会被置位成 EAGAIN or EWOULDBLOCK,对于connect函数,错误码会置位为:EINPROGRESS
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#include<myhead.h>
#define SER_PORT 8888 //服务器端口号
#define SER_IP "192.168.122.118" //服务器客户端


int main(int argc, const char *argv[])
{
//1、创建用于连接的套接字
int sfd = socket(AF_INET, SOCK_STREAM, 0);
//参数1:通信域,表明使用的是ipv4协议
//参数2:通信方式,使用TCP通信
//参数3:0表示之前已经指定协议 IPPROTO_TCP

if(sfd == -1)
{
perror("socket error");
return -1;
}
printf("sfd = %d\n", sfd); //3



//对套接字设置接收超时时间
struct timeval tv;
tv.tv_sec = 5; //5秒
tv.tv_usec = 0;

//设置超时
if(setsockopt(sfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))==-1)
{
perror("setsockopt error");
return -1;
}




//将端口号快速重用函数
int reuse = 1;
if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1)
{
perror("setsockopt error");
return -1;
}
printf("端口号快速重用成功\n");



//2、给当前套接字绑定IP地址和端口号
//2.1填充要绑定的地址信息结构体
struct sockaddr_in sin;
sin.sin_family = AF_INET; //通信域
sin.sin_port = htons(SER_PORT); //端口号
sin.sin_addr.s_addr = inet_addr(SER_IP); //ip地址

//2.2 绑定
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1)
{
perror("bind error");
return -1;
}
printf("bind success %s %s %d\n", __FILE__, __func__, __LINE__);

//3、将套接字设置成监听状态
if(listen(sfd, 128) == -1)
{
perror("listen error");
return -1;
}
printf("listen success %s %s %d\n", __FILE__, __func__, __LINE__);

//4、阻塞等待客户端的链接请求
//4.1定义容器接收客户端的地址信息
struct sockaddr_in cin; //用于接收地址信息
socklen_t socklen = sizeof(cin); //用于接收地址信息的大小

int newfd = -1;
//4.2 接收客户端的链接
newfd = accept(sfd, (struct sockaddr*)&cin, &socklen);
if(newfd == -1)
{

//判断是否超时
if(errno == EAGAIN)
{
printf("timeout\n");
return -1;
}

perror("accept error");
return -1;
}
printf("[%s:%d]发来链接请求 %s %s %d\n", \
inet_ntoa(cin.sin_addr), ntohs(cin.sin_port),__FILE__, __func__, __LINE__);

//5、跟客户端进行消息通信
char buf[128] = "";
while(1)
{
//将数组清空
bzero(buf, sizeof(buf));

//读取客户端发来的消息
//int res = read(newfd, buf, sizeof(buf));
int res = recv(newfd, buf, sizeof(buf), 0);
if(res == 0)
{
printf("客户端已经下线\n");
break;
}
printf("[%s:%d] : %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), buf);

//给客户端发消息
strcat(buf, "*_*");

//write(newfd, buf, sizeof(buf));
send(newfd, buf, sizeof(buf), 0);
printf("发送成功\n");
}

//6、关闭套接字
close(newfd);
close(sfd);





return 0;
}

2> alarm

也可以作为超时时间检测,在某个阻塞函数执行之前,可以启动一个定时器,当定时器时间到位后,会产生一个SIGALRM信号,默认操作是关闭进程,也可以将该信号捕获后处理其他操作,例如 表示超时操作。案例参考斗地主模型。