TCP如何建立长连接

news/2024/9/17 18:56:39/ 标签: tcp/ip, 网络, 服务器

文章目录

    • TCP建立长连接
      • 长连接和短连接
        • 长连接的优势
        • TCP KEEPALIVE
      • 心跳包
        • 心跳检测步骤
      • 断线重连
        • 断线重连函数实现
    • 实例
      • 服务端
      • 客户端
        • 程序功能
        • 演示效果

TCP建立长连接

长连接和短连接

  • 长连接是指不论TCP的客户端和服务器之间是否有数据传输,都保持建立的TCP连接;
  • 短连接则不同,一旦两者之间的数据传输完毕,则立即断开连接,下次需要传输数据时再重新创建连接
长连接的优势

由于TCP建立连接需要进行三次握手,每次建立连接都需要进行资源消耗,对于频繁请求资源的客户端而言,长连接可减少大量开销

TCP KEEPALIVE

TCP中默认包含一个keep-alive机制用于检测连接是否可用,TCP在链路空闲时定时(默认两小时)发送数据给对方,双方处理结果如下:

  • 主机可达,对方收到数据之后响应ACK应答,则连接正常
  • 主机可达,但程序退出,对方则发送RST应答,发送TCP撤销连接
  • 主机可达,但程序崩溃,对方发送FIN消息

对方主机不响应ACK、RST,继续发送消息直到超时(两小时),就撤销连接

虽然TCP已经有了keep-alive机制保证连接的可靠性,但是其仍有一定的局限性

  • keep-alive只能检测连接是否存活,但无法检测其是否可用。例如:socket连接虽然存在,但是无法对该连接进行读写操作,keep-alive机制是无法获知的
  • keep-alive的灵活性不够,其默认间隔为两小时,无法及时获知TCP连接情况
  • keep-alive无法检测到机器断电、网线拔出、防火墙等导致的断线问题

心跳包

心跳包就是探测性的数据包,因为它像心跳一样每隔固定时间发送,以此来通知服务器自己仍处于存活状态,以保持长连接。心跳包的内容基本没有要求,一般是很小的数据包,或者是仅包含包头的空包

如果不主动关闭socket,操作系统是不会将其关闭的,这样socket所在的进程如果没有挂掉,则socket所占用的资源将一直无法回收。不仅如此,防火墙会自动关闭一定时间没有进行数据交互的连接。因此,我们需要自己实现心跳包,用于长连接的保活、断线处理及资源回收等操作。

一般来说,心跳包的频率为30~40秒,如果对实时性要求较高,则可进一步减小时间间隔

心跳检测步骤
  • 客户端定时发送探测包给服务器,同时启动超时定时器
  • 服务器接收到检测包之后就回复一个数据包
  • 客户端如果接收到服务器的返信,则表示服务器正常,定时器结束
  • 如果客户端定时器超时仍没有接收到服务器返信,则表示服务器停止工作

断线重连

长连接断开原因
在长连接通信过程中,双方的所有通信都建立在1条长连接上(1次TCP连接),因此连接需要持续保持双方连接才可使得双方持续通信
长连接会存在断开的情况,而断开原因主要是:

  • 长连接所在进程被杀死 NAT超时
  • 网络状态发生变化
  • 其他不可抗因素(网络状态差、DHCP的租期等等 )

维持长连接的另一个方法是断线重连

如果服务器因为某种故障导致连接无法正常使用,客户端使用心跳检测发现了服务器端掉线,则客户端可进行断线重连操作

我们将之前写的Linux创建tcp连接流程中的程序的客户端进行修改,进而实现服务器断开连接之后,客户端会自动重连

断线重连函数实现
void reconnect()
{printf("------Reconnect ...-----\n");sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(6666);saddr.sin_addr.s_addr = inet_addr("127.0.0.1");int retry = 5;		//掉线后重连五次,期间重连成功直接退出while(retry > 0){confd = connect(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));if(confd < 0)perror("connect");elsebreak;sleep(3);retry--;} 
}

需要注意的是,在判断连接是否断开时,可以判断recv函数是否成功,而非send函数
原因在于,send函数在将数据发送出去就立即返回,不论传输是否成功;
而recv是等数据传输完成之后才会返回,因此使用recv的结果作为判断连接是否成功才更为准确

实例

服务端

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>#define MAXLINE 4096int main() {int sockfd, connfd;struct sockaddr_in servaddr;char buff[MAXLINE];int n;if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {printf("Create socket error: %s (errno: %d)\n", strerror(errno), errno);return 0;}int opt = 1;setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);servaddr.sin_port = htons(6666);if (bind(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1) {printf("Bind socket error: %s (errno: %d)\n", strerror(errno), errno);return 0;}if (listen(sockfd, 10) == -1) {printf("Listen socket error: %s (errno: %d)\n", strerror(errno), errno);return 0;}printf("====Waiting for client's request=======\n");while (1) {if ((connfd = accept(sockfd, (struct sockaddr *)NULL, NULL)) == -1) {printf("Accept socket error: %s (errno: %d)\n", strerror(errno), errno);continue;}while (1) {n = recv(connfd, buff, MAXLINE, 0);if (n <= 0) {if (n == 0) {printf("Client disconnected\n");} else {printf("Recv error: %s (errno: %d)\n", strerror(errno), errno);}break;}buff[n] = '\0';printf("Recv msg from client: %s\n", buff);if(!strcmp(buff, "keepalive"))continue;memset(buff, 0x00, sizeof(buff));printf("send msg to Client: ");fgets(buff, MAXLINE, stdin);if (send(connfd, buff, strlen(buff), 0) < 0) {printf("send msg error: %s(errno: %d)\n", strerror(errno), errno);break;}}close(connfd);}close(sockfd);return 0;
}

客户端

#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#define MAXLINE 4096int sockfd = 0;
int confd = 0;
int count = 0;
char ipAddr[24] = "";int reconnect() {close(sockfd);sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0) {perror("socket");return 0;}struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(6666);saddr.sin_addr.s_addr = inet_addr(ipAddr);int retry = 5;while (retry > 0) {confd = connect(sockfd, (struct sockaddr*)&saddr, sizeof(saddr));if (confd < 0) {perror("connect");} else {return 1;}sleep(3);printf("------Reconnect ...-----\n");retry--;}return 0;
}void printMes(int signo) {char heartData[12] = "keepalive";printf("Get a SIGALRM, %d counts!\n", ++count);if (send(sockfd, heartData, strlen(heartData), 0) < 0) {printf("send msg error: %s(errno :%d)\n", strerror(errno), errno);if (0 == reconnect()) {printf("The reconnect failed!!!\n");return;}}
}void initTimer() {struct itimerval tick;signal(SIGALRM, printMes);memset(&tick, 0, sizeof(tick));tick.it_value.tv_sec = 10;tick.it_value.tv_usec = 0;tick.it_interval.tv_sec = 10;tick.it_interval.tv_usec = 0;if (setitimer(ITIMER_REAL, &tick, NULL) < 0) {printf("Set timer failed!\n");}
}int main(int argc, char** argv) {int n;char recvline[4096], sendline[4096];struct sockaddr_in servaddr;if (argc != 2) {printf("usage: ./client <ipaddress>\n");return 0;}memcpy(ipAddr, argv[1], sizeof(ipAddr));if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {printf("create socket error: %s (errno :%d)\n", strerror(errno), errno);return 0;}memset(&servaddr, 0, sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(6666);if (inet_pton(AF_INET, ipAddr, &servaddr.sin_addr) <= 0) {printf("inet_pton error for %s\n", argv[1]);return 0;}if ((confd = connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr))) < 0) {printf("connect socket error: %s(errno :%d)\n", strerror(errno), errno);return 0;}initTimer();while (1) {memset(sendline, 0x00, sizeof(sendline));printf("send msg to server:\n");fgets(sendline, 4096, stdin);if (strstr(sendline, "exit")) {printf("The socket is free....\n");break;}if (send(sockfd, sendline, strlen(sendline), 0) < 0) {printf("send msg error: %s(errno :%d)\n", strerror(errno), errno);if (0 == reconnect()) {return 0;}}n = recv(sockfd, recvline, MAXLINE, 0);if (n < 0) {printf("recv error: %s(errno :%d)\n", strerror(errno), errno);if (0 == reconnect()) {return 0;}} else if (n == 0) {printf("server closed connection\n");if (0 == reconnect()) {return 0;}} else {recvline[n] = '\0';printf("recv msg from server: %s\n", recvline);}}close(sockfd);return 0;
}
程序功能

先启动服务端,等待客户端进行连接。如果客户端发起连接后,每隔10s发送一个心跳包

  • 如果服务端接受到心跳包,则不输入内容,直接等待下一次客户端发送的信息;
  • 如果客户端接受到字符串,则等待服务端输入字符串进行回应;
  • 如果客户端在发送字符串后,没有收到回应信息,则重新发起连接请求;
演示效果

程序手动在服务端执行过程中,输入ctrl+C关闭服务端进程,在客户端重连次数之内再重新打开即可

  • 客户端
    在这里插入图片描述
  • 服务端
    在这里插入图片描述

http://www.ppmy.cn/news/1507255.html

相关文章

CSS优先级,没你想的那么简单!全面介绍影响CSS优先级的各类因素

简介 CSS的中文名称叫做“层叠样式表”&#xff0c;其中的层叠就是指根据各类优先级规则来处理冲突的样式。层叠是CSS的一个重要特性&#xff0c;优先级也是CSS学习中一项非常重要的内容。 提到CSS优先级&#xff0c;我们首先会想到各类的选择器&#xff0c;例如ID选择器&…

学习记录——day28 信号量集

目录 一、信号量集 1、信号量集的API函数接口 二、 将信号量集函数再次封装 1、sem.h 2、sem.c 三、使用信号量集完成共享内存的进程同步 1、发送端 2、接收端 一、信号量集 信号量集&#xff0c;其实就是无名信号量的集合&#xff0c;主要用于完整多个进程间的同步问题.…

127. Go反射基本原理

文章目录 反射基础 - go 的 interface 是怎么存储的&#xff1f;iface 和 eface 的结构体定义&#xff08;runtime/iface.go&#xff09;&#xff1a;_type 是什么&#xff1f;itab 是什么&#xff1f; 反射对象 - reflect.Type 和 reflect.Value反射三大定律Elem 方法reflect.…

【数据结构】三、栈和队列:6.链队列、双端队列、队列的应用(树的层次遍历、广度优先BFS、先来先服务FCFS)

文章目录 2.链队列2.1初始化&#xff08;带头结点&#xff09;不带头结点 2.2入队&#xff08;带头结点&#xff09;2.3出队&#xff08;带头结点&#xff09;❗2.4链队列c实例 3.双端队列考点:输出序列合法性栈双端队列 队列的应用1.树的层次遍历2.图的广度优先遍历3.操作系统…

【Kubernetes】Service 概念与实战

Service 概念与实战 1.通过 Service 向外部暴露 Pod2.Service 的多端口设置3.集群内部的 DNS 服务4.无头 Service 在 Kubernetes 中部署的应用可能对应一个或者多个 Pod&#xff0c;而每个 Pod 又具有独立的 IP 地址。Service&#xff08;服务&#xff09;能够为一组功能相同的…

大数据-72 Kafka 高级特性 稳定性-事务 (概念多枯燥) 定义、概览、组、协调器、流程、中止、失败

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 目前已经更新到了&#xff1a; Hadoop&#xff08;已更完&#xff09;HDFS&#xff08;已更完&#xff09;MapReduce&#xff08;已更完&am…

Linux中安装MYSQL数据库

文章目录 一、MYSQL数据库介绍1.1、MySQL数据库的基本概述1.2、MySQL数据库的主要特性1.3、MySQL数据库的技术架构与组件1.4、MySQL数据库的应用与扩展性1.5、MySQL数据库的许可模式与开源生态 二、MySQL Workbench和phpMyAdmin介绍2.1、MySQL Workbench介绍2.2、phpMyAdmin介绍…

【学习笔记】Day 9

一、进度概述 1、inversionnet_train 试运行——成功 二、详情 1、inversionnet_train 试运行 在经历了昨天的事故后&#xff0c;今天最终成功运行了 inversionnet_train&#xff0c;运行结果如下&#xff1a; 经观察&#xff0c;最开始 loss 值大概为 0.5 左右 随着训练量的增…

使用Selenium调试Edge浏览器的常见问题与解决方案

背景介绍 在当今互联网时代&#xff0c;网页爬虫已经成为数据获取的重要手段。而Selenium作为一款功能强大的自动化测试工具&#xff0c;被广泛应用于网页爬取任务中。虽然Chrome浏览器是Selenium用户的常见选择&#xff0c;但在某些工作环境中&#xff0c;我们可能需要使用Ed…

Ubuntu24.04设置国内镜像软件源

参考文章&#xff1a; Ubuntu24.04更换源地址&#xff08;新版源更换方式&#xff09; - 陌路寒暄 一、禁用原来的软件源 Ubuntu24.04 的源地址配置文件发生改变&#xff0c;不再使用以前的 sources.list 文件&#xff0c;升级 24.04 之后&#xff0c;该文件内容变成了一行注…

牛客-热身小游戏

题目链接&#xff1a;热身小游戏 第一种写法&#xff1a;线段树 介绍第二种写法&#xff1a;并查集 对于一些已经查询过的点&#xff0c;我们可以往后跳&#xff0c;进行路径压缩&#xff0c;他们的父亲为下一个点。 a数组记录[ l , r ] 之间的乘积&#xff0c;初始值为1。…

haproxy知识点整理

haproxy知识点整理 haproxy七层代理负载均衡什么是负载均衡为什么使用负载均衡 负载均衡类型四层负载均衡七层负载均衡四层和七层的区别 环境搭建:客户端(client)haproxy服务器两台服务器hapserver1hapserver2 简单的haproxy负载均衡 haproxy的基本配置信息global配置proxies配…

17. ADC开发

1. 概述 bes2700 支持2路ADC 2. 硬件连接 3. 软件开发 电压值计算:电压 = 参考电压/4096(2的12次方) * ADC值

linux中安装nginx方法

1、首先确保系统已经安装gcc&#xff0c;如没安装&#xff0c;请先自行安装 2、安装nginx 将openssl-1.1.1j.tar.gz、pcre-8.44.tar.gz、zlib-1.3.tar.gz、nginx-1.20.0.tar.gz解压到当前目录&#xff0c;命令如下&#xff1a; tar -zxvf openssl-1.1.1j.tar.gz tar -zxvf…

【RISC-V设计-08】- RISC-V处理器设计K0A之BMU

【RISC-V设计-08】- RISC-V处理器设计K0A之BMU 文章目录 【RISC-V设计-08】- RISC-V处理器设计K0A之BMU1.简介2.顶层设计3.端口说明4.总线时序4.1 总线写时序4.2 总线读时序 5.代码设计6.总结 1.简介 总线管理单元&#xff08;Bus Management Unit&#xff0c;简称 BMU&#x…

Linux安全与高级应用(四)深入探索MySQL数据库:安装、管理与安全实践

文章目录 标题&#xff1a;全面解析LAMP平台部署及应用第一部分&#xff1a;LAMP平台概述第二部分&#xff1a;准备工作第三部分&#xff1a;安装和配置PHP第四部分&#xff1a;配置Apache第五部分&#xff1a;测试LAMP平台第六部分&#xff1a;部署phpMyAdmin总结 &#x1f44…

【海贼王航海日志:前端技术探索】CSS你了解多少?(三)

目录 1 -> 浏览器调试工具——查看CSS属性 1.1 -> 打开浏览器 1.2 -> 标签页含义 1.3 -> elements标签页使用 2 -> 元素的显示模式 2.1 -> 块级元素 2.2 -> 行内元素/内联元素 2.3 -> 改变显示模式 3 -> 盒模型 3.1 -> 边框 3.2 ->…

MySql-索引事务

在面试中&#xff0c;对于mysql相关的面试题常看的两部分也是我们学习时需要重点了解的内容&#xff1a;索引与事务。 目录 索引 B树 B树结构 B树创建 事务 重点&#xff1a;事务的基本特性 一、原子性 二、一致性 三、持久性 四、隔离性 索引 索引的核心内容&#…

白骑士的Matlab教学进阶篇 2.3 信号处理

系列目录 上一篇&#xff1a;白骑士的Matlab教学进阶篇 2.2 数值计算 信号处理在现代工程和科学领域中扮演着至关重要的角色。MATLAB作为一个强大的数学计算平台&#xff0c;提供了丰富的工具和函数来帮助研究人员和工程师处理各种信号问题。本文将深入介绍MATLAB中信号处理的…

C# 集合操作的艺术:深入解析数据分区策略与高效筛选技巧(Skip、SkipWhile、Take、TakeWhile)

文章目录 概述Skip 和 SkipWhile 方法Take 和 TakeWhile 方法综合应用示例总结 在C#中&#xff0c;LINQ&#xff08;语言集成查询&#xff09;提供了一种非常方便的方式来处理数据集合。本文将详细介绍四种数据分区方法&#xff1a;Skip、SkipWhile、Take、TakeWhile&#xff0…