【技术实操】银河高级服务器操作系统实例分享,TCP长连接与短连接详细说明

1.服务器环境以及配置

|--------------|-------------------------------------------------------|
| 物理机/虚拟机/云/容器 | 物理机 |
| 处理器: | HUAWEI Kunpeng 920 |
| 具体操作系统版本 | Kylin-Server-10-SP1-Release-Build20-20210518- aarch64 |
| 内核版本 | kernel-4.19.90-23.8.v2101.ky10.aarch64 |

2.问题现象描述

对TCP长连接有疑问

1、如何从命令查看及确认哪些是长连接?

2、netstat命令查看结果中timers字段中第一列四种状态的具体含义

  1. keepalive #表示是keepalive的时间计时(希望可对照图 1-1具体是出现在哪个环节,从哪里开始计时?)
  2. on #表示是重发(retransmission)的时间计时(希望可对照图 1-1说明出现在哪个环节?)
  3. off #表示没有时间计时(希望可对照图 1-1说明出现在哪个环节?)
  4. timewait #表示等待(timewait)时间计时(希望可对照图 1-1说明出现在哪个环节?)

3、netstat命令查看结果中timers字段中第二列三个数据的具体含义状态的具体含义

例如:keepalive (576.47/1/1)

3.问题分析

3.1.长连接与短连接

长连接(long connnection),指在一个连接上可以连续发送多个数据包,在连接保持期间,如果没有数据包发送,需要双方发链路检测包。

短连接(short connnection),是相对于长连接而言的概念,指的是在数据传送过程中,只在需要发送数据时才去建立一个连接,数据发送完成后则断开此连接,即每次连接只完成一项业务的发送。

区分长连接和短连接主要看以下几点:

  • 连接持续时间:长连接通常持续较长时间,而短连接持续时间较短。通常情况下,长连接可以持续数分钟、数小时甚至更长时间,而短连接通常只持续几秒钟或几分钟。
  • 连接的目的:长连接通常用于需要保持持久连接的应用程序,例如在线聊天、实时通信、视频流传输等。短连接通常用于一次性数据传输,如HTTP/1.0请求和响应。
  • 连接的频率:长连接通常会在一段时间内持续传输数据,而短连接通常是一次性的数据传输。如果您看到多次数据传输在同一个连接上进行,那么可能是长连接。如果每个数据传输都伴随着新的连接建立和关闭,那么可能是短连接。
  • 连接状态:使用网络监控工具如netstat或ss,可以查看当前的TCP连接状态。长连接通常处于ESTABLISHED状态,而短连接可能会更快地进入CLOSED状态。
  • 应用程序协议:有些应用程序协议明确规定了连接的类型。例如,HTTP/1.0通常使用短连接,而HTTP/1.1支持持久连接。

3.2.如何从命令查看及确认哪些是长连接

3.2.1 使用tcpdump或者wireshark抓包查看

以HTTP1.1为例的长连接。

HTTP 1.1默认采用Keep-Alive连接,允许一个HTTP连接上的多个请求/响应交换,而不需要为每个请求重新建立一个TCP连接。短连接是指每次请求/响应完成后就关闭TCP连接,下次请求需要重新建立TCP连接。相比之下,Keep-Alive连接在一定时间内可以重复使用,提高了效率和性能。当然,如果需要,客户端和服务器都可以通过相应的配置或请求头来使用短连接。但是,随着Web应用的复杂度增加,使用Keep-Alive连接成为了一种更常见的做法。

从抓包可见HTTP1.1的Keep-Alive设置的timeout为60s。从654号包,开始计时,如果超时时间不更新的话,就会在60s之后断开连接,在654和2794之间没有timeout的更新,所以连接断开了。

3.2.2 使用netstat和ss查看

netstat -tonp,查看tcp状态和timer。

可以查看当前的TCP连接状态。长连接通常处于ESTABLISHED状态,而短连接可能会更快地进入CLOSED状态。长连接一般会使用心跳机制进行探测。

3.3 netstat命令查看结果中timers字段中第一列四种状态的具体含义

3.3.1 keepalive

keepalive:keepalive计时器通常在ESTABLISHED状态下使用。当TCP连接处于ESTABLISHED状态时,系统可以启动keepalive计时器来定期发送keepalive探测包,以检测连接的健康状态。

3.3.2 on

on:重发计时器。

建立连接时:当TCP连接刚刚建立时,RTO的计时器通常不会立即启动,因为还没有数据包被发送。连接建立后,RTO会在数据包被发送后开始计时。

数据包发送:RTO与每个已发送但未确认的数据包相关联。每当发送方发送一个数据包,RTO就会启动并开始计时,等待接收方的确认。

等待确认:RTO在等待接收方确认数据包的过程中运行。如果在RTO超时之前未收到确认,发送方将触发数据包的重传。

传输结束:RTO也可以在TCP连接关闭时出现。如果有未确认的数据包仍然存在于连接中,RTO会继续运行,以确保这些数据包得到确认或重传。

3.3.3 timewait

timewait:timewait计时器在TIME_WAIT状态下使用。在连接关闭后,连接的一方会进入TIME_WAIT状态,timewait计时器用于等待一段时间,以确保在此期间不会出现重复的连接请求。

3.3.4 off

off:没有时间计时,例如ESTABLISHED没有特殊的计时器操作,应用程序设置关闭了keepalive。如果State列为CLOSE_WAIT状态是,Timer列多为off (0.00/0/0),因为CLOSE_WAIT的是属于被动关闭那一方,这个是没有超时(timeout)设置的,所以也就不用计时了。CLOSE_WAIT除非你杀进程,CLOSE_WAIT是不会自动消失的。一个CLOSE_WAIT会维持至少2个小时的时间。当然不消失意味着占着资源呢,这里就是占着FD。

例如:应用服务端设置关闭KeepAlive

1、准备两台虚拟机

|-------------------------------------------|
| server 192.168.55.52 clinet 192.168.55.12 |

2、准备服务端程序

服务端监听1234端口。

|-----------------------------------------|
| ssh root@192.168.55.52 vim tcp_server.c |

|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <sys/epoll.h> #include <unistd.h> #define MAX_EVENTS 10 #define BUFFER_SIZE 1024 int main(int argc, char *argv\[\]) { if (argc != 2) { printf("请提供要监控的端口号作为参数\n"); return 1; } int port = atoi(argv1); // 创建套接字 int sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { perror("无法创建套接字"); return 1; } int keepAlive=0;//设置关闭keepAlive if (setsockopt(sockfd, SOL_SOCKET, SO_KEEPALIVE, &keepAlive, sizeof(keepAlive)) < 0) { perror("设置失败"); return 1; } // 绑定地址和端口 struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = htonl(INADDR_ANY); if (bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("无法绑定地址和端口"); return 1; } // 监听连接 if (listen(sockfd, 10) < 0) { perror("无法监听连接"); return 1; } printf("正在监听端口 %d ...\n", port); // 创建 epoll 实例 int epollfd = epoll_create1(0); if (epollfd < 0) { perror("无法创建 epoll 实例"); return 1; } // 添加 sockfd 到 epoll 实例中进行监听 struct epoll_event event; event.events = EPOLLIN; event.data.fd = sockfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, sockfd, &event) < 0) { perror("无法添加 sockfd 到 epoll 实例"); return 1; } // 准备接收连接的事件 struct epoll_event eventsMAX_EVENTS; while (1) { // 等待事件发生 int num_events = epoll_wait(epollfd, events, MAX_EVENTS, -1); if (num_events < 0) { perror("epoll_wait 出错"); return 1; } int i=0; // 处理所有事件 for (i = 0; i < num_events; i++) { if (eventsi.data.fd == sockfd) { // 有新连接请求 struct sockaddr_in client_addr; socklen_t client_len = sizeof(client_addr); int client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); if (client_sockfd < 0) { perror("无法接受连接"); return 1; } // 将新连接的套接字添加到 epoll 实例中进行监听 event.events = EPOLLIN; event.data.fd = client_sockfd; if (epoll_ctl(epollfd, EPOLL_CTL_ADD, client_sockfd, &event) < 0) { perror("无法添加新连接的套接字到 epoll 实例"); return 1; } // 获取客户端信息 char client_ipINET_ADDRSTRLEN; inet_ntop(AF_INET, &(client_addr.sin_addr), client_ip, INET_ADDRSTRLEN); int client_port = ntohs(client_addr.sin_port); printf("已连接客户端:%s:%d\n", client_ip, client_port); } else { // 有数据可读 int client_sockfd = eventsi.data.fd; char bufferBUFFER_SIZE; ssize_t recv_len = recv(client_sockfd, buffer, BUFFER_SIZE - 1, 0); if (recv_len > 0) { bufferrecv_len = '\0'; printf("接收到的字符串:%s\n", buffer); } else if (recv_len == 0) { // 客户 // 从 epoll 实例中移除套接字 if (epoll_ctl(epollfd, EPOLL_CTL_DEL, client_sockfd, NULL) < 0) { perror("无法从 epoll 实例中移除套接字"); return 1; } // 关闭套接字 close(client_sockfd); } else { perror("接收数据出错"); return 1; } } } } // 关闭套接字和 epoll 实例 close(sockfd); close(epollfd); return 0; } |

|-----------------------------------------------|
| # 编译 gcc tcp_server.c -o server ./server 1234 |

3、准备客户端程序

客户端绑定54321端口

|-----------------------------------------|
| ssh root@192.168.55.12 vim tcp_client.c |

|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #define PORT 54321 int main(int argc, char *argv\[\]) { int sock; // socket struct sockaddr_in server_addr; // server address char *server_ip; // server IP address int server_port; // server port // Check command line arguments if (argc != 3) { printf("Usage: %s <server_ip> <server_port>\n", argv0); return 1; } // Get server IP address and port server_ip = argv1; server_port = atoi(argv2); // Create socket sock = socket(AF_INET, SOCK_STREAM, 0); if (sock < 0) { perror("socket failed"); return 1; } // Set address and port of server memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(server_ip); server_addr.sin_port = htons(server_port); // Bind client socket to port 54321 struct sockaddr_in client_addr = { .sin_family = AF_INET, .sin_addr.s_addr = INADDR_ANY, .sin_port = htons(PORT) }; if (bind(sock, (struct sockaddr *)&client_addr, sizeof(client_addr)) < 0) { perror("bind failed"); return 2; } // Connect to server if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) { perror("connect failed"); return 1; } // Print success message printf("Connected to server %s:%d\n", server_ip, server_port); send(sock,"hello",6,0); while(1); // Close socket close(sock); return 0; } |

|----------------------------|
| gcc tcp_client.c -o client |

netstat -W -neoap查看服务端,后面的off表示关闭了timer,若服务端不关闭keepalive,使用netstat 查看到的timer应是keepalive。

3.4 netstat命令查看结果中timers字段第二列三个数据的具体含义状态的具体含义

第二列,(576.47/0/0) -> (a/b/c)

3.3.1 当第一列为 keepalive 计时器

|---|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| a | 定时器计时值的倒计时,单位秒;刚建立连接时为最大值。 如果系统没有特殊设置或者应用程序没有设置keepalive,起始值是/proc/sys/net/ipv4/tcp_keepalive_time默认值7200秒,倒计时直到值变为0。 |
| b | 没有用 |
| c | 已发送的keepalive探测器的数量(已经发送的探测(probe)包的次数)。默认为0;最大值和tcp_keepalive_probes有关系,/proc/sys/net/ipv4/tcp_keepalive_probes代表总共发送探测(probe)包的个数(默认为9个)。 当a值倒计时为0,而连接中间没有数据传输,这个值将会累加。 而/proc/sys/net/ipv4/tcp_keepalive_intvl表示在发送一个探测(probe)包后,如果多少秒内没有收到回复,则再发送一个探测(probe)包。这也代表了之前发送的探测(probe)包超时失效(默认为75秒)。当所有的探测(probe)包都发送完毕后,如果仍然没有收到回应,服务器会主动关闭该连接(长连接)。 因此,通常情况下,如果第二列的c为0,a的范围应在7200到0之间,其中7200是/proc/sys/net/ipv4/tcp_keepalive_time的值,例如keepalive (576.47/0/0) ;如果c不为0,但不可能大于/proc/sys/net/ipv4/tcp_keepalive_probes的值,那么a的范围应在75到0之间,其中75是tcp_keepalive_intvl的值,例如keepalive (73.06/0/2)。 |

3.3.2 当第一列为on计时器

|---|---------------------------|
| a | 重发(retransmission)倒计时,单位秒 |
| b | 已经产生的重发(retransmission)次数 |
| c | 没有使用 |

3.3.3 当第一列为 timewait 计时器

|---|--------------------------------------------------------------------------|
| a | TIMEWAIT状态倒计时值,单位秒,起始值为60(两倍MSL的时间值)。当时间倒计时为0,连接就会由TIMEWAIT状态就会变为CLOSE状态 |
| b | 没有使用 |
| c | 没有使用 |

相关推荐
Decay__1 小时前
05_MySQL 常用 SQL 大全 —— 诊断、管理、优化辅助 SQL 与三大系统库速查
运维·数据库·sql·mysql·架构
贞明电子3 小时前
工业三色报警灯怎么接入项目系统?开关量与RS485控制应用指南
运维·网络·微信公众平台·贞明电子
明天也要努力学习3 小时前
HTTP 协议的基本格式和 fiddler 的用法
网络协议·http·fiddler
HideF3 小时前
Linux
linux·服务器·网络
万联WANFLOW3 小时前
海外网络故障怎么排查?Cloudflare 三季度断网报告解读与 mtr 实战
运维·网络·故障分析
sbjdhjd3 小时前
网工进阶笔记:H3CSE 园区网全栈技术与协议汇总
网络·笔记·云计算·ssh·网络工程·vlan·aaa
Ruiery5 小时前
Linux 6.6内核内存管理深度解析(十三):slab/slub 分配器
linux·运维·服务器
taktok_ai6 小时前
用 cc-switch 连接 Taktok
网络
乌恩大侠6 小时前
从 AI-RAN 到 AI Agent:基于 NVIDIA Nemotron 构建 5G/6G 智能网络故障诊断系统
服务器·人工智能·5g·o-ru·ai-ran·电信大模型
Decay__7 小时前
04_MySQL DBA 日常运维手册 —— 会话管理、锁与阻塞、事务监控、慢查询与日志巡检
运维·数据库·sql·mysql·架构