TCP通信机制:三次握手、四次挥手、滑动窗口

news2024/11/17 0:45:43

在这里插入图片描述

欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起探讨和分享Linux C/C++/Python/Shell编程、机器人技术、机器学习、机器视觉、嵌入式AI相关领域的知识和技术。


TCP通信机制

    • 1. TCP三次握手
    • 2. TCP四次挥手
    • 3. TCP连接与数据传输过程
    • 4. TCP滑动窗口机制
    • 5. server服务端与client客户端编程实现


专栏:《Linux从小白到大神》《网络编程》


1. TCP三次握手

TCP是一种面向连接的安全的流式传输协议,TCP报文的格式如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6EaFNcZA-1673610188489)(Typora_picture_reference/1661688989208.png)]

  • 标志位URG、ACK、PSH、PST、SYN、FIN
    • SYN:请求建立连接
    • ACK:给对端应答
    • FIN:断开连接
  • 16位窗口大小:这里的窗口实际上就是滑动窗口(将在后面介绍),这个窗口大小只是记录了存放数据的缓冲区也就是窗口有多大,而不是实际存放数据的地方。
  • 32位序号:在请求建立连接时跟在SYN标志位后面的随机序号。
  • 32位确认序号:在应答时跟在ACK标志位后面的确认序号,它的值是对端在建立连接时跟随在对端发送的SYN后面的随机序号的值加上SYN携带的数据大小再加上1,如果对端SYN未携带数据,则直接在对端SYN后面随机序号的基础上加1。比如客户端发起连接并携带SYN 100(10),服务端收到后会回复ACK 111,表示连接请求已收到且10个字节的数据也已收到;如果客户端发起的连接为SYN 100,不携带数据,那么服务端回复为ACK 101。总之,ACK后面的确认序号值为对端SYN随机序号+携带数据大小+1。

TCP在建立连接的时候需要进行三次握手(TCP握手时一定有SYN标志,不带SYN标志的为建立连接后的正常数据传输)

  • 第一次握手:第一次握手应该由客服端发起,服务端被动等待连接。
    • 客户端:主动发起连接
      • 携带标志位SYN
      • 产生32位随机序号:比如100
        • 可以携带数据,也可以不携带数据,携带数据时要带上数据大小比如10字节 SYN 100(10),不携带数据为SYN 100。
    • 服务端:
      • 检测SYN的值是否为1。如果SYN值为0,则握手失败;如果SYN值为1,则握手成功,服务端回复,进行第二次握手。
  • 第二次握手:服务端回复并请求建立反向连接。
    • 服务端:服务端发送确认信号ACK并携带一个32位的确认序号,确认序号的值为客户端SYN随机序号的值+1,加的这个1实际上是SYN标志的大小1字节。同时,服务端向客户端发起一个反向的连接请求SYN,并携带一个32为随机序号。
      • 确认应答:ACK标志位+32位确认序号(值为客户端随机序号的值+1+携带的数据大小)
        • 客户端不带数据:ACK 101 —> 100+1
        • 客户端携带数据:ACK 111 —> 100+10+1
      • 发起一个连接请求:SYN+随机序号,同样可以携带数据也可以不携带数据
        • 不携带数据:SYN 200(0)
        • 携带数据:SYN 200(10)
    • 客户端:检测确认标志ACK是否为1,并校验确认序号是否正确。
      • 检测ACK:为1,对端收到并确认应答。
      • 校验:不携带数据时,确认序号为101,正好为自己发送的SYN100的值加1;携带数据时,确认序号为111,说明自己发送的10字节数据对端已收到。
  • 第三次握手:客户端向服务端发送确认数据包,完成反向连接的建立。
    • 客户端:确认应答,ACK+确认信号
      • 服务端未携带数据:ACK 201
      • 服务端携带数据:ACK 211
    • 服务端:
      • 检测ACK是否为1
      • 校验确认序号是否正确

至此,三次握手成功,双向连接均已建立,可以开始数据传输了。示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qyoMupBV-1673610188491)(Typora_picture_reference/1661692769922.png)]

2. TCP四次挥手

TCP断开连接时需要进行四次挥手:

  • 客户端与服务端哪一端主动断开连接都可以;
  • 挥手时需要一个标志位FIN,FIN后面也需要跟一个序号,序号的值为对端最后一次发送的ACK后面的确认序号;

四次挥手的过程如下:

  • 第一次挥手:某一端主动发起断开连接的请求。
    • 客户端:发送断开连接的请求
      • FIN + 序号(对端最后一个ACK后面的确认序号)
      • ACK + 序号(自己上一次ACK后面的确认序号)
  • 第二次挥手:另一端确认断开连接。
    • 服务端:
      • 检测FIN的值是否为1,如果不是1则挥手失败,即断开连接失败;
      • ACK + 序号(对端FIN后面的序号+收到数据的大小+1),告诉对端对方发送的数据自己收到了多少;
  • 第三次挥手:另一端请求断开反向连接(TCP是双向连接,经过前两次挥手只断开了单向连接,所以需要反向断开连接)。
    • 服务端:发送反向断开连接的请求。
      • FIN + 序号(客户端最后一次ACK所携带的确认序号);
      • ACK + 序号(自己上一次ACK后面的确认序号)
  • 第四次挥手:TCP双向连接断开。
    • 客户端:客户端对服务端发送的断开连接请求进行确认。
      • ACK + 序号(对端FIN后面的序号+收到数据的大小+1)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NCjG7Cab-1673610188493)(Typora_picture_reference/1661770821519.png)]

3. TCP连接与数据传输过程

首先看一个TCP连接与数据传输的示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wJtMxmBy-1673610188495)(Typora_picture_reference/1661771933088.png)]

在图中:

  • 1-3:三次握手阶段(握手阶段一定有SYN标志)
  • 4-6:数据传输阶段
  • 7-10:四次挥手阶段(挥手阶段一定有FIN标志)

图中的<mss 1460>表示最大数据长度,即告知对端给我发送数据的时候不要超过这个最大长度。

详细分析上述过程的完整图示如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NQ4IVlAA-1673610188510)(Typora_picture_reference/1661771971809.png)]

4. TCP滑动窗口机制

首先看滑动窗口的示意图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NNDeUFx7-1673610188525)(Typora_picture_reference/1661777743241.png)]

在图中,发送端速度快,接收端速度慢,一般来说谁先发送SYN谁就是客户端,因为客户端总是主动连接服务端,而服务端则被动等待客户端的连接。

在TCP中,滑动窗口实际上就是一块缓冲区(缓存)。在上图中,客户端与服务端进行数据传输的时候总是带有一个win 4096或win 6144等标志,这个win就代表滑动窗口的意思,而后面的数字则代表滑动窗口所表示的缓存区的大小。比如客户端发起的第一条握手请求,即fast sender所代表的1处,SYN, 0(0), win 4096, <mss 1460>

  • SYN表示请求建立连接
  • 0(0)表示随机序号为0,携带数据大小为0
  • win 4096 表示滑动窗口大小为4096字节,即4KB,可以参考图中右侧虚线框起来的部分
  • <mss 1460>表示允许对端一次发送的最大数据长度为1460字节

通过server端发送的 win 6144, <mss 1024> 可知,服务端滑动窗口大小为6KB,一次可以接受1KB数据。client端发送数据的速度是要快于server端接收数据的速度的,所以client端会发送多条数据,其中每条数据为1KB(由server端的mss指定最大接收长度),总共发送的数据不能超过6KB(server端的滑动窗口大小)。所以上图中client端总共发送了6条数据,每条数据长度为1KB(可见图中序号2-9数据传输),正好大小总共为6KB,等于server端窗口大小(实际上是server端缓冲区的大小)。

此时,server端的缓冲区已经满了,不能再接收数据了,只有当server端把数据读出的时候,缓冲区才能空出位置接受新数据。当server端读取了2K数据,那么server端的缓冲区将空余出2K字节,请见上图中的序号10处,server端向client端回复 ACK,6145, win 2048,表示server端收到了6144字节(6KB)数据,6145是由6144+1来的,win 2048表示server端现在缓冲区空余2K空间。当server端再次读出2K数据的时候,server再次向client发送一条数据 ACK,6145, win 4096,这里ACK和确认序号不变,win所代表的窗口大小发生了变化,变成了当前空余的缓冲区大小4KB,可见上图中序号11所代表的数据传输,此时在server发送数据10和11之间,client端并没有发送数据,但是server端要向client端告知自己的空闲缓冲区大小。

最后,12-18的过程实际上就是四次挥手的过程,client发送FIN断开连接,此时server还没有处理完缓冲区中的数据,所以每处理一批数据都会向client发送一条信息并告知缓冲区剩余大小,直到缓冲区中的数据全部处理完毕,server向client发送FIN断开连接。实际上滑动窗口就是缓冲区的大小,并且在发送数据过程中,并不是client发一条server就必须收一条,也可以发多条收多条,这是因为TCP是流式传输,一端发送的数据虽然没有立即被处理,但是已经存起来了,就存在了滑动窗口所表示的缓冲区中,当缓冲区满了,发送端就会临时阻塞,等待接收端缓冲区出现剩余空间。

上面所描述的过程都是所有数据传输都成功的前提下进行的,实际上,每条数据的发送都可能会失败,当发生传输失败的情况,TCP会进行重传,重传的示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XyllqAsg-1673610188527)(Typora_picture_reference/1661780184980.png)]

5. server服务端与client客户端编程实现

server.c

/************************************************************
  >File Name  : server.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年08月14日 星期日 19时53分24秒
************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <ctype.h>

int main(int argc, char* argv[])
{
	//创建用于监听的套接字
	int lfd = socket(AF_INET, SOCK_STREAM, 0);
	if(lfd == -1)
	{
		perror("socket err");
		exit(1);
	}	

	//bind
	struct sockaddr_in server_addr;
	//init
	memset(&server_addr, 0, sizeof(server_addr));	
	//bzero(&server_addr, sizeof(serve_addr));
	server_addr.sin_family = AF_INET; //IPv4
	server_addr.sin_port = htons(8765);
	server_addr.sin_addr.s_addr = htonl(INADDR_ANY); //使用本机任意IP

	int ret = bind(lfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("bind err");
		exit(1);
	}

	//设置监听
	ret = listen(lfd, 128);
	if(ret == -1)
	{
		perror("listen err");
		exit(1);
	}
	
	//等待并接受连接请求
	struct sockaddr_in client_addr;
	socklen_t client_len = sizeof(client_addr);
	int cfd = accept(lfd, (struct sockaddr*)&client_addr, &client_len);
	if(cfd == -1)
	{
		perror("accept err");
		exit(1);
	}
 	
	char ipbuf[64];
	printf("client ip: %s, port: %d\n",
			inet_ntop(AF_INET, &client_addr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
			ntohs(client_addr.sin_port));

	//通信
	while(1)
	{
		//接收数据
		char buf[1024] = {0};
		int len = read(cfd, buf, sizeof(buf));
		if(len < 0)
		{
			perror("read err");
			break;
		}
		else if(len == 0)
		{
			printf("client disconnect ...\n");
			break;
		}	
		else 
		{
			//读到数据
			printf("read buf : %s\n", buf);
			for(int i = 0; i < len; i++)
			{
				buf[i] = toupper(buf[i]);
			}
			printf("toupper read buf: %s\n", buf);
			//发送数据
			write(cfd, buf, strlen(buf) + 1);
		}
	}

	close(lfd);
	close(cfd);
	
	return 0;
}

client.c

/************************************************************
  >File Name  : client.c
  >Author     : Mindtechnist
  >Company    : Mindtechnist
  >Create Time: 2022年08月15日 星期一 11时13分29秒
 ************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <arpa/inet.h>
#include <fcntl.h>

int main(int argc, char* argv[])
{
	if(argc < 2)
	{
		printf("err:./exe port\n");
		return -1;
	}
	int port = atoi(argv[1]);

	//创建套接字
	int fd = socket(AF_INET, SOCK_STREAM, 0);
	if(fd == -1)
	{
		perror("socket err");
		exit(1);
	}

	//连接服务器
	struct sockaddr_in server_addr;
	memset(&server_addr, 0, sizeof(server_addr));
	server_addr.sin_family = AF_INET;
	inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr.s_addr);
	int ret = connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
	if(ret == -1)
	{
		perror("connect err");
		exit(1);
	}

	//通信
	while(1)
	{
		//接收键盘输入
		char buf[512];
		printf("please input string: \n");
		fgets(buf, sizeof(buf), stdin);
		//发送给服务器
		write(fd, buf, strlen(buf));

		//接收服务端数据
		int len = read(fd, buf, sizeof(buf));
		if(len < 0)
		{
			perror("read err");
			exit(1);
		}
		else if(len == 0)
		{
			printf("server close connect ...\n");
			break;
		}
		else
		{
			printf("read buf: %s, buflen: %d\n", buf, len);
		}
	}	

	close(fd);

	return 0;
}

在这里插入图片描述
在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/160578.html

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

相关文章

这套设备管理方案助你效率10倍提升

车间工厂等货物人员密集场所&#xff0c;对消防安全的要求很高。消防设备管理自然是生产制造型企业的核心之一。消防设施的有效管理&#xff0c;既要保证日常巡检工作的有效性&#xff0c;又要在设备出现故障后及时响应。在此基础上还要对整体管理情况进行数据分析&#xff0c;…

振弦采集模块的各种参数操作

振弦采集模块的各种参数操作 固件版本读取 点击指令区【 读取版本】 按钮&#xff0c;读取当前连接模块的固件版本信息&#xff0c;读取到的版本信息显示于按钮右侧。 VMTool 会根据读取到的版本不同对功能和界面做出调整&#xff0c;故此&#xff0c; 在使用 VMTool 时&#…

Workfine新手入门:给图片加水印

哈喽&#xff0c;我是办公助手小W&#xff0c;又到了跟大家分享办公小技巧的时候啦&#xff01; 最近Workfine5.0最新版本上线后&#xff0c;一直有人问到底有啥新功能啊&#xff1f;与往期版本有何不同呢&#xff1f;小W亲自去体验了一番&#xff0c;最大的一个亮点就是新增了…

90、【树与二叉树】leetcode ——104. 二叉树的最大深度:层次遍历+DFS+子问题分解(C++版本)

解题思路 原题链接&#xff1a;104. 二叉树的最大深度 解题思路 1、迭代法&#xff1a;层次遍历BFS /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), rig…

Blender 物理属性 (二)布料和碰撞

文章目录添加与去除布料查看布料效果布料的预设属性(模拟不同的布料效果)布料与其他物体碰撞布料的自交叉问题布料钉固制作一个窗帘添加与去除布料 1 添加&#xff1a;物体模式选中一个物体&#xff0c;属性栏/物理属性&#xff08;蓝色圆圈&#xff09;&#xff0c;选择布料 …

程序员转项目经理,需要拒绝的3大习惯。

程序员转项目经理并不容易&#xff0c;需要我们转变思路&#xff0c;并拒绝我们日常3大习惯。 1、拒绝单打独干&#xff0c;转而关注与干系人的沟通协调。 做为技术人员&#xff0c;在确定自我任务后&#xff0c;独自敲代码就好&#xff0c;具有较强的单干性质。而作为项目经理…

前端项目代码统一规范-从eslint、stylelint、husky、lint-staged、commitlint和配合vscode插件一一道来

目前在多人项目开发时发现很多代码不规范的地方&#xff0c;因为每个项目都是多人维护&#xff0c;再加上各种历史代码的不同风格&#xff0c;这些情况很容易就造成了代码规范落地难&#xff0c;项目中出现大量低质量代码&#xff0c;代码格式难统一。所以采用 eslinthuskystyl…

【Vue】移动端项目流程

移动端项目 O 项目技术栈说明 脚手架&#xff1a; Vite 3 还有 vue-cli - 底层 webpack 脚本&#xff1a;typescript路由&#xff1a;vue-router4状态管理器&#xff1a; vuex4 还有 pinia 组件库&#xff1a;vant3.6.3组件API&#xff1a;选项式API 一、Vite 脚手架的使用 …

PostgreSQL复习记录(一):Win10成功安装postgresql14.6的过程记录

到官网下载页面选择合适的版本进行下载&#xff0c;我这里选择Windows版本&#xff0c;跳转到Download PostgreSQL后选择PostgreSQL Version 14.1 Windows x86-64的版本。 1&#xff0c;启动安装程序&#xff1a; 如果只是练习使用&#xff0c;安装时这里可以取消勾选Stack Bu…

戴尔电脑开机屏幕花屏无法启动解决方法

戴尔电脑开机屏幕花屏无法启动解决方法。有用户使用的戴尔电脑开机的时候出现了一些问题&#xff0c;电脑屏幕变成了满屏的马赛克花屏&#xff0c;不能正常启动到桌面页面上了。那么这个问题要如何去做出解决&#xff0c;一起看看操作的方法吧。 准备工作&#xff1a; 1、U盘一…

初步认识 Babel

Babel 官网&#xff1a;https://www.babeljs.cn/docs/1.AST 抽象语法树AST 抽象语法树&#xff0c;是 Babel 的核心在 JavaScript 的世界中&#xff0c;你可以认为抽象语法树&#xff08;AST&#xff09;是最底层下面会通过拆解一个普通函数&#xff0c;解释下什么是 AST 抽象语…

node服务从http升级到https(阿里云免费ssl)

升级原因1.各大搜索引擎中&#xff0c;https的网页的权重比一般的http的网页权重要高。2.从用户体验的角度&#xff0c;一个老是被浏览器提醒该网页不可信的网页&#xff0c;总不会让用户感到安心所以将网站从http升级为https是很有必要的用户配置首先介绍一下这次升级的网站使…

拓扑排序 (算法思想+图解+模板+练习题)

拓扑排序 有向无环图一定是拓扑序列,有向有环图一定不是拓扑序列。 无向图没有拓扑序列。 首先我们先来解释一下什么是有向无环图&#xff1a; 有向就是我们两个结点之间的边是有方向的&#xff0c;无环的意思就是整个序列中没有几个结点通过边形成一个圆环。 下图就是一个…

数据分析:通俗易懂假设检验

导读 大多数关于假设检验的教程都是从先验分布假设开始&#xff0c;列出一些定义和公式&#xff0c;然后直接应用它们来解决问题。然而&#xff0c;在本教程[1]中&#xff0c;我们将从第一原则中学习。这将是一个示例驱动的教程&#xff0c;我们从一个基本示例开始&#xff0c;…

Web(五、六)

JavascriptDOM* 功能&#xff1a;控制html文档的内容* 获取页面标签(元素)对象&#xff1a;Element* document.getElementById("id值"):通过元素的id获取元素对象* 操作Element对象&#xff1a;1. 修改属性值&#xff1a;明确获取的对象是哪一个&#xff1f;查看API文…

BI工具将数据分析拉下神坛

以前&#xff0c;数据分析是一件比较有门槛的事&#xff0c;它不仅要求数据分析师具备一定的数据分析思维和方法经验&#xff0c;还要求数据分析师们熟练使用各种复杂的数据分析工具&#xff0c;要求他们掌握Python、R、SQL等。但随着BI工具的发展&#xff0c;多维自助分析逐渐…

组织机构管理不得不了解的 RBAC 权限模型|身份云研究院

由于信息安全越来越被重视&#xff0c;企业的身份管理已经成为市场焦点&#xff0c;对于实施企业级安全策略和身份管理的需求随之迅速上升。而作为权限访问控制策略的 RBAC&#xff08;基于角色的访问控制&#xff09;模型也已被广泛使用到组织机构管理中&#xff0c;本文将带领…

LeetCode刷题模版:81 - 90

目录 简介81. 搜索旋转排序数组 II82. 删除排序链表中的重复元素 II83. 删除排序链表中的重复元素84. 柱状图中最大的矩形85. 最大矩形86. 分隔链表87. 扰乱字符串【未理解】88. 合并两个有序数组89. 格雷编码90. 子集 II结语简介 Hello! 非常感谢您阅读海轰的文章,倘若文中有…

vue组件中插槽slot的使用

目录 插槽 1、组件的三大核心&#xff1a;属性&#xff08;data、props&#xff09;、事件、插槽 2、插槽&#xff08;slot&#xff09;&#xff1a;将子组件和父组件进行组合&#xff0c;可以弥补视图的不足。使组件具有更好的扩展性 组件的封装方式&#xff1a;抽取共性、…

CMMI五大成熟度定义及过程管理类详解

一、成熟度级别CMMI组织的成熟度级别提供了描述其绩效特征的方式。经验表明&#xff0c;当组织每次过程改进工作所专注的过程域在数量上易管理时&#xff0c;组织能够做到最好&#xff1b;那些领域随着组织的改进&#xff0c;也需要不断成熟。成熟度级别是组织级过程改进的预定…