Linux⽹络编程之⼼跳机制
最近学了⽹络编程的基础课程,在TCP⽹络连接中,了解到了⼼跳机制。
1、什么是⼼跳机制?
⼼跳机制:是定时发送⼀个⾃定义的结构体(⼼跳包),让对⽅知道⾃⼰还活着,以确保连接的有效性的机制。
⼼跳包就是客户端定时发送简单的信息给服务器端告诉它我还在⽽已。代码就是每隔⼏分钟发送⼀个固定信息给服务端,服务端收到后回复⼀个固定信息如果服务端⼏分钟内没有收到客户端信息则视客户端断开。
注意:
(1)发包⽅:可以是客户也可以是服务端,看哪边实现⽅便合理。⼀般是客户端。服务器也可以定时轮询发⼼跳下去
(2)为了保持长连接,携带的数据应该尽量的少。
2、在何种情况下使⽤⼼跳机制呢?
⽹络中的接收和发送数据都是使⽤操作系统中的SOCKET进⾏实现,在TCP⽹络通信中经常会出现客户端和服务器之间的套接字已经断开,此时发送数据和接收数据的时候就⼀定会有问题,就需要实时检测查询链接状态。常⽤的解决⽅法就是在程序中加⼊⼼跳机制。
在TCP的机制⾥⾯,本⾝是存在有⼼跳包的机制的,也就是TCP的选项。系统默认是设置的是2⼩时的⼼跳频率。但是它检查不到机器断电、⽹线拔出、防⽕墙这些断线。
⼼跳包主要也就是⽤于长连接的保活和断线处理。⼀般的应⽤下,判定时间在30-40秒⽐较不错。如果实在要求⾼,那就在6-9秒。
3、⼼跳包的发送,通常有两种技术
⽅法1:应⽤层⾃⼰实现的⼼跳包
由应⽤程序⾃⼰发送⼼跳包来检测连接是否正常,服务器每隔⼀定时间向客户端发送⼀个短⼩的数据包,然后启动⼀个线程,在线程中不断检测客户端的回应, 如果在⼀定时间内没有收到客户端的回应,即认为客户端已经掉线;同样,如果客户端在⼀定时间内没有收到服务器的⼼跳包,则认为连接不可⽤。
⽅法2:TCP的KeepAlive保活机制
在TCP的机制⾥⾯,本⾝是存在有⼼跳包的机制的,也就是TCP的选项. 不论是服务端还是客户端,⼀⽅开启KeepAlive功能后,就会⾃动在规定时间内向对⽅发送⼼跳包, ⽽另⼀⽅在收到⼼跳包后就会⾃动回复,以告诉对⽅我仍然在线。因为开启KeepAlive功能需要消耗额外的宽带和流量,所以TCP协议层默认并不开启默认的KeepAlive超时需要7,200,000 MilliSeconds, 即2⼩时,探测次数为5次。对于很多服务端应⽤程序来说,2⼩时的空闲时间太长。因此,我们需要⼿⼯开启KeepAlive功能并设置合理的KeepAlive参数
4、开启KeepAlive选项后会导致的三种情况:
1、对⽅接收⼀切正常:以期望的ACK响应,2⼩时后,TCP将发出另⼀个探测分节
2、对⽅已崩溃且已重新启动:以RST响应。套接⼝的待处理错误被置为ECONNRESET,套接⼝本⾝则被关闭。
3、对⽅⽆任何响应:套接⼝的待处理错误被置为ETIMEOUT,套接⼝本⾝则被关闭.
⼀个服务器通常会连接多个客户端,因此由⽤户在应⽤层⾃⼰实现⼼跳包,代码较多 且稍显复杂。⽤TCP/IP协议层为内置的KeepAlive功能来实现⼼跳功能则简单得多。⼼跳包在按流量计费的环境下增加了费⽤.但TCP得在连接闲置2⼩时后才发送⼀个保持存活探测段,所以通常的⽅法是将保持存活参
数改⼩,但这些参数按照内核去维护,⽽不是按照每个套接字维护,因此改动它们会影响所有开启该选项的套接字。
5、下⾯我们通过⼀个实例来展⽰⼼跳机制。
服务器:
1.经过socket、bind、listen、后⽤accept获取⼀个客户的连接请求,为了简单直观,这⾥服务器程序只接收⼀个connect请求,我们⽤clifd来获取唯⼀的⼀个连接。
2.为clifd修改KeepAlive的相关参数,并开启KeepAlive套接字选项,这⾥我们把间隔时间设为了5秒,闲置时间设置了5秒,探测次数设置为5次。
3.将clifd加⼊select监听的描述符号集
客户:很简单,只是连接上去,并停留在while死循环。
1 #include <iostream>
2 #include <sys/types.h>
3 #include <sys/socket.h>
4 #include <stdlib.h>
5 #include <strings.h>
6 #include <stdio.h>
7 #include <netinet/in.h>
8 #include <arpa/inet.h>
9 #include <errno.h>
10 #include <unistd.h>
11 using namespace std;
12
13 int main()
14 {
15 int skfd;
16 if ((skfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
17 perror("");
18 exit(-1);
19 }
20
21 struct sockaddr_in saddr;
linux在线编程22 bzero(&saddr, sizeof(saddr));
23 saddr.sin_family = AF_INET;
24 saddr.sin_port = htons(9999);
25 saddr.sin_addr.s_addr = inet_addr("115.29.109.198");
26
27 if (connect(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
28 perror("");
29 exit(-1);
30 }
31
32 cout << "连接成功" << endl;
33 while(1);
34 return 0;
35 }
36 服务器
37
38 #include <iostream>
39 #include <sys/types.h>
40 #include <sys/socket.h>
41 #include <stdlib.h>
42 #include <strings.h>
43 #include <stdio.h>
44 #include <netinet/in.h>
45 #include <arpa/inet.h>
46 #include <errno.h>
47 #include <unistd.h>
48 #include <sys/select.h>
49 #include <netinet/tcp.h>
50 using namespace std;
51
52 #define LISTENNUM 5
53
54 int main()
54 int main()
55 {
56 int skfd;
57 if ((skfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
58 perror("");
59 exit(-1);
60 }
61
62 struct sockaddr_in saddr;
63 bzero(&saddr, sizeof(saddr));
64 saddr.sin_family = AF_INET;
65 saddr.sin_port = htons(9999);
66 saddr.sin_addr.s_addr = inet_addr("115.29.109.198");
67
68 if (bind(skfd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0) {
69 perror("");
70 exit(-1);
71 }
72
73 if (listen(skfd, LISTENNUM) < 0) {
74 perror("");
75 exit(-1);
76 }
77
78 int clifd;
79 if ((clifd = accept(skfd, NULL, NULL)) < 0) {
80 perror("");
81 exit(-1);
82 }
83 cout << "有新连接" << endl;
84
85 //setsockopt
86 int tcp_keepalive_intvl = 5; //保活探测消息的发送频率。默认值为75s
87 int tcp_keepalive_probes = 5; //TCP发送保活探测消息以确定连接是否已断开的次数。默认值为9次
88 int tcp_keepalive_time = 5; //允许的持续空闲时间。默认值为7200s(2h)
89 int tcp_keepalive_on = 1;
90
91 if (setsockopt(clifd, SOL_TCP, TCP_KEEPINTVL,
92 &tcp_keepalive_intvl, sizeof(tcp_keepalive_intvl)) < 0) {
93 perror("");
94 exit(-1);
95 }
96
97 if (setsockopt(clifd, SOL_TCP, TCP_KEEPCNT,
98 &tcp_keepalive_probes, sizeof(tcp_keepalive_probes)) < 0) {
99 perror("");
100 exit(-1);
101 }
102
103 if (setsockopt(clifd, SOL_TCP, TCP_KEEPIDLE,
104 &tcp_keepalive_time, sizeof(tcp_keepalive_time)) < 0) {
105 perror("");
106 exit(-1);
107 }
108
109 if (setsockopt(clifd, SOL_SOCKET, SO_KEEPALIVE,
110 &tcp_keepalive_on, sizeof(tcp_keepalive_on))) {
111 perror("");
112 exit(-1);
113 }
114
115 char buf[1025];
116 int r;
117 int maxfd;
118 fd_set rset;
119 FD_ZERO(&rset);
119 FD_ZERO(&rset);
120 sleep(5);
121 while (1) {
122 FD_SET(clifd, &rset);
123 maxfd = clifd + 1;
124 if (select(maxfd, &rset, NULL, NULL, NULL) < 0) {
125 perror("");
126 exit(-1);
127 }
128
129 if (FD_ISSET(clifd, &rset)) {
130 r = read(clifd, buf, sizeof(buf));
131 if (r == 0) {
132 cout << "接收到FIN" << endl;
133 close(clifd);
134 break;
135 }
136 else if (r == -1) {
137 if (errno == EINTR) {
138 cout << "errno: EINTR" << endl;
139 continue;
140 }
141
142 if (errno == ECONNRESET) {
143 cout << "errno: ECONNRESET" << endl;
144 cout << "对端已崩溃且已重新启动" << endl;
145 close(clifd);
146 break;
147 }
148
149 if (errno == ETIMEDOUT) {
150 cout << "errno: ETIMEDOUT" << endl;
151 cout << "对端主机崩溃" << endl;
152 close(clifd);
153 break;
154 }
155
156 if (errno == EHOSTUNREACH) {
157 cout << "errno: EHOSTUNREACH" << endl;
158 cout << "对端主机不可达" << endl;
159 close(clifd);
160 break;
161 }
162 }
163 }
164 }
165
166 close(skfd);
167 return 0;
168 }
原⽂链接:blog.csdn/qq_37050329/article/details/80460357
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论