TCP如何建立长连接

news2024/9/22 17:27:05

文章目录

    • 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");
        else
            break;
        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 4096

int 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 4096

int 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.coloradmin.cn/o/1994055.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Docker最佳实践(三):安装mysql

大家好&#xff0c;欢迎各位工友。 本篇呢我们就来演示一下如何在Docker中部署MySQL容器&#xff0c;可以按照以下步骤进行&#xff1a; 1. 搜索镜像 首先搜索MySQL镜像&#xff0c;可以使用以下命令&#xff1a; docker search mysql2. 拉取镜像 根据需求选择MySQL或Maria…

Oracle|DM 常用|不常用 SQL大口袋

目录 一、前言 二、SQL写法 1、sql获取某一条数据中的前一条和后一条 2、实现like多个值的查询&#xff08;Oracle和dm支持&#xff0c;MySQL未试过&#xff09; 3、start with connect by prior 使用方法 4、用hextoraw解决select、update、delete语句执行慢 5、ORA-00…

叉车数字化安全管理平台,安全管控升级,打造智慧监管新模式

近年来&#xff0c;国家和各地政府相继出台了多项政策法规&#xff0c;从政策层面推行叉车智慧监管&#xff0c;加大叉车安全监管力度。同时鼓励各地结合实际&#xff0c;积极探索智慧叉车建设&#xff0c;实现作业人员资格认证、车辆状态认证、安全操作提醒、行驶轨迹监控等&a…

场外个股期权的投资技巧:把握机遇与风险平衡

【来源&#xff1a;期权圈&#xff0c;场外个股每日询价】 在金融投资的领域中&#xff0c;场外个股期权作为一种新兴且具有吸引力的投资工具&#xff0c;为投资者提供了独特的机会和挑战。掌握有效的投资技巧&#xff0c;对于在这个领域中取得成功至关重要。 首先&#xff0c…

C++ | Leetcode C++题解之第332题重新安排行程

题目&#xff1a; 题解&#xff1a; class Solution { public:unordered_map<string, priority_queue<string, vector<string>, std::greater<string>>> vec;vector<string> stk;void dfs(const string& curr) {while (vec.count(curr) &am…

How we design Kola

How we design Kola - ApiHugKola background, Kola a consumer driver tester frameworkhttps://apihug.com/zhCN-docs/kola/002_design_of_kola Kola background, Kola a consumer driver tester framework ​BDD 行为驱动开发(BDD)是一种软件团队工作方式,可以缩小业务人…

基于STM32F429移植UCOSIII

μC/OS-III&#xff08;Micro C OS Three&#xff09;是一个可升级的、可固化的、基于优先级的实时内核&#xff0c;它是Micrium公司出品的RTOS&#xff08;实时操作系统&#xff09;类实时操作系统的一个版本。以下是对μC/OS-III的详细描述&#xff1a; 1. 基本特性 多任务管…

智能制造与工业互联网有何关联?工业互联网如何推进智能制造?

随着信息技术的飞速发展&#xff0c;智能制造和工业互联网已经成为当今产业变革的核心。智能制造&#xff0c;以其深度融合的设计、生产、管理、营销和售后服务等环节&#xff0c;代表了先进制造模式的典范。而工业互联网则作为智能制造的神经中枢&#xff0c;通过连接机器、人…

【初阶数据结构题目】17.用栈实现队列

用栈实现队列 点击链接答题 思路&#xff1a; 定义两个栈&#xff1a;pushST&#xff08;入数据&#xff09;和popST&#xff08;出数据&#xff09; 假设我们要在队列里放123&#xff0c;出队列123 我们先在pushST里面放进去1 2 3 然后把pushST里面的数据拿到popST里面&#…

会务要闻|向绿提质:上市企业ESG评级提升

在全球市场对环境、社会和治理&#xff08;ESG&#xff09;指标的关注与日俱增的大背景下&#xff0c;中国正积极拥抱ESG理念&#xff0c;将其作为推动经济与社会全面绿色转型的催化剂&#xff0c;更被企业视为长期主义投资策略的基石。面对日益严格的国际ESG尽职调查要求&…

信息论在机器学习中的实际应用

目录 一、说明 二、什么是信息论&#xff1f; 2.1 信息论中的关键概念 2.2 熵与信息 2.3 相互信息 2.4 Kullback-Leibler 背离 三、信息论在机器学习中的应用 3.1 功能选择&#xff1a; 3.2 计算边际概率分布 3.3 决策树&#xff1a;Information 增益 3.4 评估具有 KL 背…

FastAPI+Vue3工程项目管理系统项目实战私教课 上课笔记20240808 课程和学习计划制定

学习目标 将Word和Excel做的东西放到数据库里面去工程类公司&#xff0c;甲方&#xff0c;劳务存到数据库存储的信息主要是人员的信息 基本信息&#xff1a; 人员信息&#xff0c;资料库&#xff0c;甲方的人出现在哪些项目上&#xff0c;考勤材料信息&#xff0c;进货记录&…

yolov5更换主干网络shufflent

目录 1.网络结构解析 1.1创建yolov5s_shufflent_v2_X0_5.yaml文件 2.对common.py末尾进行添加 3.修改yolo.py 1.网络结构解析 1.可以先看看shufflenet_v2的网络结构 import torch from torch import nn from torchvision import models from torchinfo import summaryclas…

利用vscode-icons-js在Vue3项目中实现文件图标展示

背景&#xff1a; 在开发文件管理系统或类似的项目时&#xff0c;我们常常需要根据文件类型展示对应的文件图标&#xff0c;这样可以提高用户体验。本文将介绍如何在Vue3项目中利用vscode-icons-js库&#xff0c;实现类似VSCode的文件图标展示效果。 先看效果&#xff1a; 一…

Flink任务提交流程和运行模式

任务提交流程 Flink 的提交流程随着部署模式、资源管理平台的不同&#xff0c;会有不同的变化。这里做进一步的抽象&#xff0c;形成一个大概高视角的任务执行流程图&#xff0c;如下&#xff1a; Flink按照集群和资源管理的划分运行模式有&#xff1a;Standalone、Flink On…

你是否陷入了惯性思维的陷阱?

如何突破惯性思维&#xff1f; 文 / 周妙錥&#xff08;MEOW EE CHEW&#xff09;13-02-2024 什么是惯性思维&#xff1f; 惯性思维&#xff08;Inertial Thinking&#xff09;是指我们在面对问题、决策或行为选择时&#xff0c;倾向于依赖过去的经验、习惯或想法&#xff0…

SubtitleEdit:一个基于.Net开发的开源字幕编辑器

现在是短视频的时代&#xff0c;对视频的字幕编辑需求非常多&#xff0c;今天介绍一个功能强大的开源视频字幕编辑器。 01 项目简介 Subtitle Edit 是基于.Net开发的开源项目&#xff0c;支持跨平台使用&#xff0c;包括Windows、Linux 和 macOS。这个软件不仅支持多种字幕格…

打开 Mac 触控板的三指拖移功能

对于支持力度触控的触控板&#xff0c;可以选择使用三指手势来拖移项目。 相应的设置名称会因你使用的 macOS 版本而有所不同&#xff1a; 选取苹果菜单  >“系统设置”&#xff08;或“系统偏好设置”&#xff09;。 点按“辅助功能”。 点按“指针控制”&#xff08;…

cms框架cookice注入漏洞

目录 一、环境 二、开始分析 2.1代码审计&#xff08;未授权访问&#xff09; 一、环境 环境私聊获取 二、开始分析 2.1代码审计&#xff08;未授权访问&#xff09; 我们可以看到构造函数ip是通过X_FORWARDED_FOR来获取的&#xff0c;而这个刚好可以伪造&#xff0c;那我…

理解张量拼接(torch.cat)

拼接 维度顺序&#xff1a;对于 3D 张量&#xff0c;通常可以理解为 (深度, 行, 列) 或 (批次, 行, 列)。 选择一个dim进行拼接的时候其他两个维度大小要相等 对于三维张量&#xff0c;理解 torch.cat 的 dim 参数确实变得更加抽象&#xff0c;但原理是相同的。让我们通过一…