当前位置: 首页 > news >正文

TCP如何建立长连接

文章目录

    • 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关闭服务端进程,在客户端重连次数之内再重新打开即可

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • Jar工具完全指南:从入门到精通
  • C语言学习——函数
  • MySQL常用的日期和时间函数
  • oracle 比较两个值取小使用LEAST函数
  • 2024年湖北省建筑施工特种作业人员证书延期申请/年审
  • 精彩回顾 | ROS暑期“无人机自主追踪小车”训练营
  • 深度学习四大框架之争(Tensorflow、Pytorch、Keras和Paddle)
  • 鸿蒙笔记--Socket
  • 数据结构(邓俊辉)学习笔记】词典 02—— 散列函数
  • 用python实现求两个整数的最大公约数
  • vue 开发工具 Hbuilder 简介及应用
  • Python教程(十二):面向对象高级编程详解
  • haproxy的安装和服务信息
  • 管好“黄金数据”,浪潮海岳助力企业释放主数据潜能
  • LVS集群中的负载均衡技术
  • HomeBrew常规使用教程
  • Js基础知识(四) - js运行原理与机制
  • macOS 中 shell 创建文件夹及文件并 VS Code 打开
  • python_bomb----数据类型总结
  • Redash本地开发环境搭建
  • SpingCloudBus整合RabbitMQ
  • Theano - 导数
  • vue和cordova项目整合打包,并实现vue调用android的相机的demo
  • Vue组件定义
  • Web设计流程优化:网页效果图设计新思路
  • 理清楚Vue的结构
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 扫描识别控件Dynamic Web TWAIN v12.2发布,改进SSL证书
  • 深度学习入门:10门免费线上课程推荐
  • 一加3T解锁OEM、刷入TWRP、第三方ROM以及ROOT
  • 优化 Vue 项目编译文件大小
  • FaaS 的简单实践
  • Java性能优化之JVM GC(垃圾回收机制)
  • RDS-Mysql 物理备份恢复到本地数据库上
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • 分布式关系型数据库服务 DRDS 支持显示的 Prepare 及逻辑库锁功能等多项能力 ...
  • 关于Android全面屏虚拟导航栏的适配总结
  • 湖北分布式智能数据采集方法有哪些?
  • 说说我为什么看好Spring Cloud Alibaba
  • #1014 : Trie树
  • #ubuntu# #git# repository git config --global --add safe.directory
  • #职场发展#其他
  • $con= MySQL有关填空题_2015年计算机二级考试《MySQL》提高练习题(10)
  • (zt)最盛行的警世狂言(爆笑)
  • (补)B+树一些思想
  • (附源码)springboot“微印象”在线打印预约系统 毕业设计 061642
  • (免费分享)基于springboot,vue疗养中心管理系统
  • (十六)视图变换 正交投影 透视投影
  • (一)Kafka 安全之使用 SASL 进行身份验证 —— JAAS 配置、SASL 配置
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)EXC_BREAKPOINT僵尸错误
  • (最优化理论与方法)第二章最优化所需基础知识-第三节:重要凸集举例
  • *** 2003
  • .[hudsonL@cock.li].mkp勒索病毒数据怎么处理|数据解密恢复
  • .NET / MSBuild 扩展编译时什么时候用 BeforeTargets / AfterTargets 什么时候用 DependsOnTargets?