在須要長鏈接的網絡通訊程序中,常常須要心跳檢測機制,來實現檢測對方是否在線或者維持網絡鏈接的須要。這一機制是在應用層實現的,對應的,在TCP協議中,也有相似的機制,就是TCP保活機制。html
設想這種狀況,TCP鏈接創建後,在一段時間範圍內雙發沒有互相發送任何數據。思考如下兩個問題:後端
基於上面兩點考慮,須要保活機制。bash
保活機制是由一個保活計時器實現的。當計時器被激發,鏈接一段將發送一個保活探測報文,另外一端接收報文的同時會發送一個ACK
做爲響應。微信
具體實現上有如下幾個相關的配置:網絡
查看Linux系統中TCP保活機制對應的系統配置以下(不一樣系統實現可能不一樣):socket
sl@Li:/proc/sys/net/ipv4$ cat tcp_keepalive_time
7200
sl@Li:/proc/sys/net/ipv4$ cat tcp_keepalive_intvl
75
sl@Li:/proc/sys/net/ipv4$ cat tcp_keepalive_probes
9
複製代碼
鏈接中啓動保活功能的一端,在保活時間內鏈接處於非活動狀態,則向對方發送一個保活探測報文,若是收到響應,則重置保活計時器,若是沒有收到響應報文,則通過一個保活時間間隔後再次向對方發送一個保活探測報文,若是尚未收到響應報文,則繼續,直到發送次數到達保活探測數,此時,對方主機將被確認爲不可到達,鏈接被中斷。tcp
TCP保活功能工做過程當中,開啓該功能的一端會發現對方處於如下四種狀態之一:分佈式
RST
,請求端將會斷開鏈接。理解了上面的實現,就能夠發現其存在如下兩點主要弊端:區塊鏈
因此,保活機制是存在爭議的,主要爭議之處在因而否應在TCP協議層實現,有兩種主要觀點:其一,保活機制沒必要在TCP協議中提供,而應該有應用層實現;其二,認爲大多數應用都須要保活機制,應該在TCP協議層實現。ui
保活功能在默認狀況下是關閉的。沒有通過應用層的請求,Linux系統不會提供保活功能。
/* server */
#include<sys/epoll.h>
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<netinet/tcp.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
#include<pthread.h>
#include <sys/time.h>
#define MAX_EVENTS 1024
#define LISTEN_PORT 33333
#define MAX_BUF 65536
struct echo_data;
int setnonblocking(int sockfd);
int events_handle(int epfd, struct epoll_event ev);
void run();
// 應用TCP保活機制的相關代碼
int set_keepalive(int sockfd, int keepalive_time, int keepalive_intvl, int keepalive_probes) {
int optval;
socklen_t optlen = sizeof(optval);
optval = 1;
if (-1 == setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen)) {
perror("setsockopt failure.");
return -1;
}
optval = keepalive_probes;
if (-1 == setsockopt(sockfd, SOL_TCP, TCP_KEEPCNT, &optval, optlen)) {
perror("setsockopt failure.");
return -1;
}
optval = keepalive_intvl;
if (-1 == setsockopt(sockfd, SOL_TCP, TCP_KEEPINTVL, &optval, optlen)) {
perror("setsockopt failure.");
return -1;
}
optval = keepalive_time;
if (-1 == setsockopt(sockfd, SOL_TCP, TCP_KEEPIDLE, &optval, optlen)) {
perror("setsockopt failure.");
return -1;
}
}
int main(int _argc, char* _argv[]) {
run();
return 0;
}
void run() {
int epfd = epoll_create1(0);
if (-1 == epfd) {
perror("epoll_create1 failure.");
exit(EXIT_FAILURE);
}
char str[INET_ADDRSTRLEN];
struct sockaddr_in seraddr, cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(LISTEN_PORT);
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (-1 == bind(listen_sock, (struct sockaddr*)&seraddr, sizeof(seraddr))) {
perror("bind server addr failure.");
exit(EXIT_FAILURE);
}
listen(listen_sock, 5);
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev)) {
perror("epoll_ctl add listen_sock failure.");
exit(EXIT_FAILURE);
}
int nfds = 0;
while (1) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (-1 == nfds) {
perror("epoll_wait failure.");
exit(EXIT_FAILURE);
}
for ( int n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
int conn_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &cliaddr_len);
if (-1 == conn_sock) {
perror("accept failure.");
exit(EXIT_FAILURE);
}
printf("accept from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
set_keepalive(conn_sock, 120, 20, 3);
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = conn_sock;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock, &ev)) {
perror("epoll_ctl add conn_sock failure.");
exit(EXIT_FAILURE);
}
} else {
events_handle(epfd, events[n]);
}
}
}
close(listen_sock);
close(epfd);
}
int setnonblocking(int sockfd){
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
return -1;
}
return 0;
}
struct echo_data {
char* data;
int fd;
};
int events_handle(int epfd, struct epoll_event ev) {
printf("events_handle, ev.events = %d\n", ev.events);
int fd = ev.data.fd;
if (ev.events & EPOLLIN) {
char* buf = (char*)malloc(MAX_BUF);
bzero(buf, MAX_BUF);
int count = 0;
int n = 0;
while (1) {
n = read(fd, (buf + count), 1024);
printf("step in edge_trigger, read bytes:%d\n", n);
if (n > 0) {
count += n;
} else if (0 == n) {
break;
} else if (n < 0 && EAGAIN == errno) {
perror("errno == EAGAIN, break.");
break;
} else {
perror("read failure.");
if (ETIMEDOUT == errno) {
if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev)) {
perror("epoll_ctl del fd failure.");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
exit(EXIT_FAILURE);
}
}
if (0 == count) {
if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev)) {
perror("epoll_ctl del fd failure.");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
printf("recv from client: %s\n", buf);
struct echo_data* ed = (struct echo_data*)malloc(sizeof(struct echo_data));
ed->data = buf;
ed->fd = fd;
ev.data.ptr = ed;
ev.events = EPOLLOUT | EPOLLET;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev)) {
perror("epoll_ctl modify fd failure.");
exit(EXIT_FAILURE);
}
return 0;
} else if (ev.events & EPOLLOUT) {
struct echo_data* data = (struct echo_data*)ev.data.ptr;
printf("write data to client: %s", data->data);
int ret = 0;
int send_pos = 0;
const int total = strlen(data->data);
char* send_buf = data->data;
while(1) {
ret = write(data->fd, (send_buf + send_pos), total - send_pos);
if (ret < 0) {
if (EAGAIN == errno) {
sched_yield();
continue;
}
perror("write failure.");
exit(EXIT_FAILURE);
}
send_pos += ret;
if (total == send_pos) {
break;
}
}
ev.data.fd = data->fd;
ev.events = EPOLLIN | EPOLLET;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_MOD, data->fd, &ev)) {
perror("epoll_ctl modify fd failure.");
exit(EXIT_FAILURE);
}
free(data->data);
free(data);
}
return 0;
}
複製代碼
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include <time.h>
#include <sys/time.h>
#include<stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#define SERVER_PORT 33333
#define MAXLEN 65535
void client_handle(int sock);
int main(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
printf("input args %d: %s\n", i, argv[i]);
}
struct sockaddr_in seraddr;
int server_port = SERVER_PORT;
if (2 == argc) {
server_port = atoi(argv[1]);
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr);
seraddr.sin_port = htons(server_port);
connect(sock, (struct sockaddr *)&seraddr, sizeof(seraddr));
client_handle(sock);
return 0;
}
void client_handle(int sock) {
char sendbuf[MAXLEN], recvbuf[MAXLEN];
bzero(sendbuf, MAXLEN);
bzero(recvbuf, MAXLEN);
int n = 0;
while (1) {
if (NULL == fgets(sendbuf, MAXLEN, stdin)) {
break;
}
// 按`#`號退出
if ('#' == sendbuf[0]) {
break;
}
struct timeval start, end;
gettimeofday(&start, NULL);
write(sock, sendbuf, strlen(sendbuf));
n = read(sock, recvbuf, MAXLEN);
if (0 == n) {
break;
}
write(STDOUT_FILENO, recvbuf, n);
gettimeofday(&end, NULL);
printf("time diff=%ld microseconds\n", ((end.tv_sec * 1000000 + end.tv_usec)- (start.tv_sec * 1000000 + start.tv_usec)));
}
close(sock);
}
複製代碼
只要鏈接後,不輸入數據,鏈接就沒有數據發送,經過抓包能夠發現每120秒就會有保活探測報文發出:
RST
)。
參考文檔:Requirements for Internet Hosts -- Communication Layers
關注微信公衆號,推送計算機網絡、後端開發、Linux、分佈式、區塊鏈、Rust等技術文章
![]()