admin 管理员组

文章数量: 1184232

网络基本模型

协议 protocol

1.应用层

HTTP 80/HTTPS 443

协议 域名(SSL的加密方式 更加安全 端口)

FTP 21 文件传输协议

用于传输文件(QQ 微信 网盘等)

DNS 53 域名转换协议

将域名转换为IP地址。

首次访问网址,本地DNS服务器带着域名要出去一层层询问,便会很慢,当第二次访问时,已经访问的域名会被本地DNS服务器写入缓存,以便访问,同样存在老化机制。

DHCP 68

动态获取网络配置 链接到某一网络,插网线,将会为设备自动分配一个IP地址,好处是保证其不与别的IP重复。

Telnet 23 远程登陆协议

用于实现连接并控制远程计算机(如服务器),数据均已未加密方式传输,安全性低。

Smtp 25 简单邮件传输协议

专门用于送电子邮件,仅负责发送,不负责接收。工作时需指定邮件服务器的地址,通过服务器间的通信从发件方传递到收件方的邮件服务器,但其正常工作还需要其他协议进行接收。

SSH 22 安全外壳协议

为Telnet协议的安全替代方案,但在其基础上扩展了加密文件传输,端口转发等功能。

2.表示层

数据加解密

网络是开放的,通过一些方式获取到网络流通的数据(抓包)。

数据解压缩

数据量大,压缩后减少体积,音视频的编解码。

3.会话层

断点续传

从网盘上下载文件,断网后,再联网,从上次下载的地方继续下载,并非重新开始。

session会话管理

在浏览器里记住了用户名和密码,便会自动登录,打开新的网页,并不需要重新登录,访问网站即为建立起会话。

服务器验用户登录

4.传输层

传输层开始,将从应用程序转到操作系统中完成。

TCP 传输控制协议

提供一种“面向连接的”“可靠的”“基于字节流的”传输层通信协议,有流量控制和差错控制,使用TCP协议的应用比如邮件的接收和发送、文件传输、远程登陆。需要数据稳定和完整性比较高的场景多次使用TCP协议。

UDP 用户数据报协议

提供一种无连接的、高效率、低可靠性的数据传输服务,使用UDP的应用比如音视频聊天,在线游戏,工业物联网数据传输等。需要数据时效性比较高的场景多使用UDP协议。

线程

多线进行,解决“同时”的问题,如果是单核,将会一边做一点,来回切换。

端口

应用程序是静态的软件文件,存储在硬盘中,未运行时不占用系统资源。当应用程序被启动时,加载到内存中运行的动态实例。一个应用程序可同时运行多个进程(浏览器打开多个标签页),进程为操作系统进行资源分配的基本单位。端口是进程与外部网络或其他进程连接的门牌号,一个进程能同时占用多个端口,反之一个端口只能被一个进程占用。端口是对外通信的必要条件。若进程不联网(本地记事本),不需要占用端口;如果需要联网,必须需要一个端口用于接收/发送数据。

socket(套接字)

通信的基石,是支持TCP/IP协议的网络通信的基本操作单元,包含进行网络通信必须的五种信息:连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。

5.网络层

此层开始,进入硬件部分。

路由器

连接不同的网络(如家庭局域网与互联网),根据目标 IP 地址查询路由表,选择最优路径将数据包从一个网络转发到另一个网络,是实现跨网络通信的核心设备。

IP 因特网协议

每一台设备在网络中的唯一标识,类似于人的身份证号,不能重复,数据根据IP地址进入到设备。访问网站时,一台大型服务器存储数据,从网站上下载数据,通过IP访问这台服务器,并通过浏览器进行显示。

防火墙 

根据预设的安全规则(如允许 / 禁止特定 IP、端口的访问),对进出网络的数据包进行检查、过滤,阻止非法访问,保护内部网络的安全。

ARP 地址解析协议

网络通信中,数据包需通过 MAC 地址(网卡的物理地址)在局域网内传输,但设备间通常只知道对方的 IP 地址。ARP 协议通过广播请求,查询目标 IP 对应的 MAC 地址,并缓存到 ARP 表中,实现局域网内的通信。

ARP代理

当发送端广播ARP请求时,本地网络上不会有主机回应(因为IP地址是外网的),此时路由器将会回应该请求,则发送源误认为路由器就是目的主机,会将报文全部转发给它,再由路由器转发报文到外网,则该路由器就被称为ARP代理。

免费ARP

在主机开机配置时,会发送一个目的IP地址为自己IP地址的ARP请求报文,该报文称为免费ARP其作用如下:
1.让主机确认本地网络上是否有与自己IP地址相同的主机,若有,则会返回一个错误报文
2.告诉整个广播域,目前某个IP所对应的MAC地址是什么,这一行为就像是在发宣传单,而宣传单是不需要回应的。若接收主机ARP缓存中本身就有发送源主机的IP-MAC对,则会更新,否则,会缓存发送源的IP-MAC对。之后建立通信的时候,将通过IP在缓存中寻找MAC对其进行通讯。缓存具有老化机制,存储的设备数据过多,将会将缓存中最早的(老化的)删除掉,以给新来的数据腾位置,当再想要通信的时候,再次请求即可。

RARP 反向地址解析协议

与 ARP 相反,适用于无固定 IP 的设备(如早期无盘工作站)。设备通过广播自己的 MAC 地址,向网络中的 RARP 服务器请求分配对应的 IP 地址,从而获取网络通信所需的 IP 标识。

6.数据链路层

交换机

连接局域网内的多台设备(如电脑、打印机),通过学习设备的 MAC 地址,构建 MAC 地址表。接收数据包后,根据目标 MAC 地址直接将数据转发到对应设备(而非广播),减少局域网内的无用数据传输,提升局域网通信效率。

网卡

是计算机、手机等设备连接网络的物理部件,具备两个核心功能:一是提供硬件接口(如网线接口、无线模块),实现设备与网络的物理连接;二是拥有唯一的 MAC 地址,作为设备在数据链路层的身份标识,确保数据能准确送达本机。

7.物理层

光纤,电缆等。

C/S与B/S架构对比

C/S

应用程序有自己固定的客户端,但使用哪种协议不固定,常用TCP/UDP。

B/S

客户端不固定,浏览网站时任意一个浏览器都可以,但协议固定,必须是HTTP/HTTPS。

基于UDP通信

加载库 下文所用的函数都在库(ws2_32.lib)

接消息和回消息最重要的部分。

socket作为通信的基石,需要向计算机进行申请。

绑定IP端口号,告诉计算机目前进程使用的端口。

静态库(.lib)

使用方法:

1.include头文件。

2.导入依赖库。

优点:

1.发布给用户的时候,只发布可执行文件即可。

缺点:

1.修改库代码,需要重新编译库文件和可执行文件。

2.当多个应用程序依赖同一个静态库并同时运行时,拷贝多份,浪费内存空间。

动态库(b.dll)链接库(b.lib)

1.include头文件

2.导入依赖库(实际上是链接库)

3.dll文件和exe文件放在同一路径下

优点:

1.只需要重新编译库文件。

2.当多个应用程序依赖同一个动态库并且同时运行的时候,内存中只存在一份动态库,节省内存。

缺点:

1.需要同时提供可执行文件和动态库。

动态库中的函数通过链接库的地址找到并被使用。

具体的使用过程为:

.exe文件将静态库拷贝进来,将动态库的链接库拷贝进来,当执行到某函数时,直接去静态库中找,去动态库的链接库中去寻找,再根据链接库的地址找到动态库的函数并使用。

输入参数:输入参数在小括号中赋值。

输出参数:返回值,输出参数

UDPServer建立

头文件准备,导入依赖库,包含Windows套接字编程

#include<iostream>
#include<WinSock2.h>//包含Windows套接字编程的头文件
//导入依赖库
#pragma comment(lib,"ws2_32.lib")

1.加载库

MAKEWORD(a,b)将两个字节组合成一个WORD类型。

WSADATA 结构体,接收WSAStarup返回库信息

WSAStarup 必须调用的初始化函数,用于向系统请求加载Winsock库。成功返回0,不成功返回非0错误码。

//1.加载库
WORD version = MAKEWORD(2, 2);//MAKEWORD(主版本号,次版本号),表示加载2.2版本的库。
WSADATA wsadata = {};//用来存储加载库的信息
int err = WSAStartup(version, &wsadata);//加载库
if (0!=err)
{
	cout << "加载库失败" << endl;
	return 1;
}
//判断库是否加载成功
if (2 == HIBYTE(wsadata.wVersion) && 2 == LOBYTE(wsadata.wVersion))//HIBYTE高字节,LOBYTE低字节
{
	cout << "加载库成功" << endl;
}
else
{
	cout << "加载的版本不符" << endl;
	WSACleanup();//卸载库
	return 1;
}

2.创建套接字

socket 创建套接字的入口,参数定义了套接字的网络层协议,传输层类型和具体协议;

如过套接字创建失败,则返回INVALID_SOCKET。

#define INVALID_SOCKET (SOCKET)-1
//2.创建套接字
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);//ip地址类型,AF_INET表示IPv4地址类型,SOCK_DGRAM表示数据报套接字,IPPROTO_UDP表示使用UDP协议
if (INVALID_SOCKET == sock)
{
	cout << "创建套接字失败" << WSAGetLastError()/*打印最后一次产生的错误码*/ << endl;
	WSACleanup();//卸载库
	return 1;
}
else
{
	cout << "创建套接字成功" << endl;
}

3.绑定IP和端口

sockaddr_in是IPv4专用的地址结构体,用于描述IP地址端口号

sockaddr_in addr;
addr.sin_family = AF_INET;                // ① 地址族:必须与socket函数的AF_INET一致(IPv4)
addr.sin_port = htons(12345);             // ② 端口号:需转换为网络字节序(大端)
addr.sin_addr.S_un.S_addr = ADDR_ANY;     // ③ IP地址:绑定所有可用网卡(0.0.0.0)

bind函数绑定套接字 关联本地端口

int bind(SOCKET s, const struct sockaddr* name, int namelen);

代码中的调用

err = bind(sock, (sockaddr*)&addr, sizeof(addr));

强制转换原因:sockaddr_insockaddr的IPv4专用扩展(sockaddr是通用地址结构体,长度固定为16字节;sockaddr_in长度为16字节,与sockaddr兼容),因此需要将sockaddr_in*转换为sockaddr*以匹配函数参数。

4.接收数据

recvfrom(套接字,接收缓冲区,缓冲区长度,标志位,发送方地址信息,发送方地址信息长度)
阻塞式函数,程序执行到这里会卡住,等待客户端发送数据
recvNum表示实际接收到的数据长度

char sendBuf[1024] = "";//接收数据的缓冲区
char recvBuf[1024] = "";//发送数据的缓冲区
int recvNum = 0;//接收数据的长度
int sendNum = 0;//发送数据的长度
sockaddr_in addrFrom = {};//用来存储发送方的地址信息
int size = sizeof(addrFrom);//地址信息的长度
while (true)
{
	//4.接收数据
	recvNum = recvfrom(sock, recvBuf/*准备好的接受数据的空间,为输出参数,收到的数据会被操作系统写入该空间*/
		, sizeof(recvBuf), 0/*以默认方式接受*/,
		(sockaddr*)&addrFrom/*数据从哪来的*/,
	&size);
	if (recvNum > 0)
	{
		//接收数据成功
		cout << "接收数据成功,接受数据:" << recvBuf << endl;
	}
	else
	{
		cout << "接收数据失败" << WSAGetLastError() << endl;
		break;
	}
	cin >> sendBuf;
		 

5.发送数据

	sendNum = sendto(sock,sendBuf/*要发送的数据*/,strlen(sendBuf)+1,0,(sockaddr*) & addrFrom,sizeof(addrFrom));
	if (SOCKET_ERROR == sendNum)
	{
		cout << "发送数据失败" << WSAGetLastError() << endl;
		break;
	}
	else
	{
		cout << "发送数据成功,发送数据长度为:" << sendNum << endl;
	}
}

UDPClient建立

加载库,创建套接字都与服务端相同

#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<iostream>
#include<Winsock2.h>

#pragma comment(lib,"Ws2_32.lib")

using namespace std;
int main()
{
	//1.加载库
	WORD version = MAKEWORD(2, 2);
	WSADATA data = {};
	int err = WSAStartup(version, &data);
	if (err != 0)
	{
		cout <<"加载库失败" << endl;
		return 1;
	}
	if (HIBYTE(data.wVersion) == 2 && LOBYTE(data.wVersion) == 2)
	{
		cout << "加载库成功" << endl;
	}
	else
	{
		cout << "库版本错误" << endl;
		WSACleanup();
		return 1;
	}

	//2.创建套接字
	SOCKET s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
	if (s == INVALID_SOCKET)
	{
		cout << "创建套接字失败" << WSAGetLastError() << endl;
		WSACleanup();
		return 1;
	}
	else
	{
		cout << "创建套接字成功" << endl;
	}


	int recvNum = 0;
	int sendNum = 0;
	char sendBuf[1024] = "";
	char recvBuf[1024] = "";
	sockaddr_in addrTo;
	addrTo.sin_family = AF_INET;
	addrTo.sin_port = htons(12345);

	// ip地址有两种类型:一种是字符串类型,一种是ulong类型(unsigned long)
	//从字符串类型转换到ulong类型:inet addr();
	//从ulong类型转换成字符串类型:inet_ntoa();


	//直接广播(把发送ip改成直接广播的ip)
	//addrTo.sin_addr.S_un.S_addr = inet_addr("192.168.1.142");



	//有线广播(需要申请广播权限)
	addrTo.sin_addr.S_un.S_addr = inet_addr("255.255.255.255");
	//申请广播权限
	bool val = true;
	setsockopt(s, SOL_SOCKET, SO_BROADCAST, (char*)&val, sizeof(val));



	while (true)
	{
		//输入要发送的数据
		cin >> sendBuf;
		//3.发送数据
		sendNum = sendto(s, sendBuf, strlen(sendBuf) + 1, 0, (sockaddr*)&addrTo, sizeof(addrTo));
		if (sendNum == SOCKET_ERROR)
		{
			cout << "发送失败" << WSAGetLastError() << endl;
			break;
		}
		//4.接收数据
		recvNum = recvfrom(s, recvBuf, sizeof(recvBuf), 0, nullptr, nullptr);
		if (recvNum)
		{
			cout << "服务端说" <<recvBuf<< endl;
		}
		else
		{
			cout << "接收错误" << recvBuf << endl;
			break;
		}


	
	}
	closesocket(s);
	WSACleanup();
	return 0;
}

为什么客户端不绑定IP也能用,发送消息时,系统发现没有IP,则系统为其随机分配,服务端被动接收,不能随机分配,客户端服务端的端口不能重复。

数据包在传输过程中的变化过程

在发送端,一层层加上标识,在接收端逐层校验目的(即目的端口号,目的IP,目的目的MADC),如果是继续向上传输,通过目的端口号,在表里找到目的端口对应的应用程序,把数据放在应用程序放在缓冲区里,再通过函数读取。

1.应用层(原数据)

用户数据。

2.传输层(UDP报文段)

UDP头(目的端口号,源端口号),用户数据。

3.网络层(IP数据报)

IP头(目的IP,源IP)UDP头,用户数据。

4.物理层(帧)

帧头(目的MADC,源MAC)IP头,UDP头,帧尾,CRC校验。

单播组播广播

单播unicast

两个设备互相发送接收数据。

广播broadcast

在广播范围内才能接收到数据,和哪台设备连无所谓。(广播)

组播multicast

给目标设备才能结收到数据,和在哪个范围内无所谓。(QQ群)

路由数据转发过程

源IP 10.0.0.1

目的IP 60.0.0.6

源MAC mac1---Router1---Router2.2---Router3.2---Router4

目的MAC Router1---Router2.1---Router3.1---Router4---mac2

整个过程中IP不变,MAC在一直变,记录了来时的路。

缓冲区

1. 应用程序的虚拟内存布局

打开应用程序后,操作系统会为其分配4G逻辑虚拟内存(并非物理内存,是“假的”逻辑地址空间),并划分为两个固定区域:

用户空间(0-2G):进程作为“操作系统的用户”,其代码中定义的变量(如sendBufrecvBuf等)均存储在此空间,仅进程自身可访问。

内核空间(3-4G):属于操作系统内核专属区域,用于运行内核代码(如进程管理、内存管理、网络协议栈等),进程无法直接访问。

2. Socket的内核缓冲区

当进程创建一个Socket(网络通信端点)时,操作系统会在内核空间为该Socket自动创建两个固定大小的缓冲区(默认各约64KB):

内核接收缓冲区:用于暂存网络中收到的数据,等待进程读取。

内核发送缓冲区:用于暂存进程待发送的数据,等待操作系统通过网络发送。

3. 数据传输的核心流程

Socket的通信本质是“用户空间与内核空间的数据拷贝”,关键步骤如下:

接收数据:
进程调用recvfrom(阻塞式函数)时,操作系统会将内核接收缓冲区中的数据拷贝到进程用户空间的recvBuf中。此时进程会阻塞等待,直到内核接收缓冲区有数据或发生错误。

发送数据:
进程将用户空间sendBuf中的数据拷贝到内核发送缓冲区,之后由操作系统负责将内核发送缓冲区的数据通过网络发送给目标进程。

4. 进程与端口的作用

PID(进程ID):操作系统通过PID唯一标识一个运行的进程,确保数据能正确交付给目标进程。

端口号:Socket通过端口号(如TCP/UDP端口)标识进程内的具体通信端点,配合IP地址实现“进程-进程”的网络通信。

IP地址分类

子网掩码

默认子网掩码

非默认子网掩码

用IPv4与子网掩码按位与&操作,得到主机的网络地址,也叫对外的IP地址。

网关

交换机:是局域网内的“数据分发器”(工作在数据链路层),通过MAC地址转发同一网络内的 data(如企业电脑、家庭智能设备间的通信),优化内部通信效率。

网关:是不同网络间的“翻译官”(工作在网络层及以上),负责跨网络的协议转换与连接(如光猫连运营商光纤和家庭Wi-Fi、企业连内网与云端),解决“不同网络无法对话”的问题。

交换机管“内网怎么传”,网关管“外网怎么连”

判断是否在同一内网中,使用IP和子网掩码按位与,除主机号都一样的就是同一内网的。

广播地址

用于向网络中的所有的设备进行广播。据有正常的网络号部分,而主机号部分全为1(二进制,实际上为255)的IP地址称之为广播地址。主机号全为0(二进制)的为网关地址。

有限广播地址:32位全为1的IP地址,用于本网广播。本网广播即为只能在内网进行广播。

阻塞和非阻塞

阻塞

等待的事情如果没有发生就在这一直等着,直到事情发生,等待事情的发生,第一时间就知道,阻塞过程不占用CPU。

发送的阻塞:发送缓冲区剩余空间不足够大,等待空间足够大再往发送缓冲区中拷贝数据。

非阻塞

等待的事情如果没有发生,可以去干别的事情,隔一段时间回来看看等待的事情发生了没有,等待的事情发生的时候不能第一时间知道,等待的过程中占用CPU,不推荐使用。

发送的非阻塞:发送缓冲区剩余空间不足够大,有多大空间拷贝多少数据,应用程序自己处理。

转换为非阻塞

	u_long iMode = 10;
	ioctlsocket(sock, FIONBIO, &iMode);
	转换为非阻塞

UDP协议特点

1.面向非连接,接收数据的时候,可以接收任意客户端发来的数据,可以一对一,也可以一对多,

2.通讯方式:数据报文的通讯方式,数据包不可拆分,

3.传输效率高(与TCP对比)

4.会产生丢包,还会出现乱序,没有校验检查。

IPv4报文格式

传输数据必须为4的倍数字节,可变部分使用TVL格式。

首部

版本(Version, 4比特)

该字段是IP协议的版本号标识。对于IPv4,该字段的十进制值始终为4。这个字段是IP协议能够与上层和下层协议正确交互的基础,确保了数据报文能够被网络中的所有设备正确识别和处理。

首部长度(IHL, 4比特)

该字段以32位字(4字节)为单位,用于指明IP首部的总长度。由于此字段只有4比特,其最大值为15,即IPv4首部的最大长度为15×4=60字节。如果没有可选字段,首部的最小长度为20字节,此时首部长度字段的值为5 。这个字段的存在是IPv4报文首部长度可变的直接体现。

总长度(Total Length, 16比特)

该字段表示整个IP数据报文的长度,包括首部和数据部分,以字节为单位。由于其占16比特,故理论上一个IP数据报文的最大长度为216−1=65535字节 。

分片控制字段群

这三个字段协同工作,共同实现了IPv4的分片(Fragmentation)与重组(Reassembly)机制。

  • 标识(Identification, 16比特):当一个数据报文被分片时,所有分片都携带着相同的标识值。这个值如同一个家庭标签,使得目的主机能够正确识别并收集所有属于同一原始数据报的分片,以便进行重组 。   

  • 标志(Flags, 3比特):该字段用于控制分片行为。其中,第一位是保留位,必须为0。第二位是DF(Don't Fragment)位,如果该位为1,则表示禁止对该报文进行分片;如果报文长度超过下一跳链路的最大传输单元(MTU),路由器将直接丢弃该报文并向源主机返回错误报告。第三位是MF(More Fragments)位,如果该位为1,则表示该分片后面还有其他分片;最后一个分片的MF位被设置为0,标志着分片序列的结束 。  

  • 片偏移(Fragment Offset, 13比特):该字段以8字节(64比特)为单位,指出当前分片的数据部分在原始数据报数据部分中的相对起始位置。例如,第一个分片的片偏移值永远为0。通过片偏移字段,目的主机可以确定每个分片在原始数据报中的正确位置,并按序将它们拼接起来 。

生存时间(TTL, 8比特)

该字段用于限制数据报文在网络中的最大“寿命”,以防止因路由环路而无限循环。每当数据报文经过一个路由器,该字段的值就减去1。当TTL值变为0时,路由器会丢弃该数据报,并向源主机发送一个ICMP超时差错报告 。这一看似简单的设计,实际上是网络诊断工具(如  Traceroute)的核心工作原理。

协议(Protocol, 8比特)

该字段标识了数据报文数据部分所封装的上层协议类型。例如,TCP的协议号为6,UDP为17 。目的主机正是通过该字段,将收到的数据报文正确交付给对应的上层协议进行处理。  

首部检验和(Header Checksum, 16比特)

该字段仅用于校验IP报文首部在传输过程中是否被意外破坏。特别值得注意的是,数据报文每经过一个设备(例如路由器),该设备都需要重新计算并更新该检验和,这是因为生存时间字段在每一跳都会发生变化 。这种每跳重新计算的机制确保了报文头的完整性,但也带来了额外的计算开销。  

源地址与目的地址(各32比特)

这两个字段是IPv4数据报文的“发件人”和“收件人”地址。每个地址由32比特组成,共4个字节。源地址标识报文的发送方,目的地址标识报文的接收方。它们是IP协议实现端到端通信的基石,指导路由器在整个网络中进行转发决策 。  

可选字段(Options, 长度可变)

可选字段为IPv4协议提供了额外的功能扩展能力,主要用于网络排错、测量和安全等目的,但在实际网络中并不常用,因为它们会增加中间路由器的工作负担和处理延迟。一些关键的选项包括:

  • 记录路由(Record Route):当该选项被设置时,数据报文每经过一个路由器,该路由器都会在报文中记录下自己的IP地址。这使得报文到达目的地后,目的地主机可以查看其所经过的完整路径,主要用于网络管理员进行路径追踪和调试 。  

  • 源路由(Source Routing):该功能允许源主机指定数据报文在网络中必须遵循的路由路径,而不是完全依赖于路由器的动态路由协议。源路由又分为两种类型:

    • 严格源路由(Strict Source Routing):报文必须严格按照指定的路由器序列前进,不能有任何偏离 。  

    • 宽松源路由(Loose Source Routing):报文必须经过指定的路由器,但中间可以自由选择路径 。  

TCP协议头

源端口目的端口

从哪个应用程序出发,到哪个应用程序去。

序号

保证有序,先发出对端不一定先接受,但一定先处理。

确认号

告诉发送端,发过来的包成功收到。

标志位

URG(紧急指针标志):当URG标志位为1时,表示**紧急指针(Urgent Pointer)**字段是有效的。紧急指针指示了TCP数据流中紧急数据的末尾。这允许发送方将紧急数据插入到普通数据流中,并告知接收方立即处理这些数据,而无需等待普通数据被完全处理。

ACK(确认标志):ACK标志位用于指示**确认字段(Acknowledgment Number)**是否有效。在TCP连接建立后,ACK标志通常为1,表示接收方已成功收到数据,并通过确认字段告知发送方已收到的数据序列号。

PSH(推送标志):PSH标志位为1时,指示接收方应**立即将应用程序数据推送(Push)**到应用程序层,而无需等待缓冲区填满。这对于需要低延迟的应用(如交互式Telnet会话)非常重要,可以减少数据传输的延迟。

RST(重置标志):RST标志位为1时,表示TCP连接需要立即重置。这通常发生在连接出现严重错误(如端口不可达、非法请求)时,强制终止当前连接,并可能需要重新建立连接。

SYN(同步标志):SYN标志位为1时,用于在TCP连接的三次握手(Three-way Handshake)的第一个步骤,表示同步序列号。发送方发送带有SYN标志的数据段,请求与接收方建立连接,并协商初始序列号。

FIN(结束标志):FIN标志位为1时,表示发送方已发送完所有数据,并请求终止TCP连接。这是一个优雅关闭连接的标志,通常是TCP四次握手(Four-way Handshake)过程的一部分。

可变部分仍旧是TVL格式,

TLV (Type-Length-Value)

在计算机科学和数据通信领域,TLV 是一种数据编码方案。

定义: TLV 是一种将数据元素进行编码的方式,主要用于结构化和传输数据,尤其是在二进制协议中。

Type (类型/标识符): 标识后面数据的类型。

Length (长度): 指定“值”字段的大小(通常是字节数)。

Value (值): 实际的数据内容。

TCP核心

可靠性 (Reliability)
  1. 这是 TCP 最显著的特点。它通过多种机制来保证数据不丢失、不重复、不乱序。

    • 序号 (Sequence Numbers):TCP 将传输的数据视为一个无结构的字节流,并为每个字节分配一个序号。接收端根据序号来检测是否有数据丢失或重复。

    • 确认应答 (Acknowledgments, ACK):每当接收端成功接收到数据后,会发送一个确认应答(ACK)给发送端,告知已收到哪些数据。

    • 超时重传 (Timeout Retransmission):发送端在发送数据后会启动一个计时器。如果在计时器超时之前没有收到对方的 ACK,就认为数据包丢失,并会重新发送。

    • 校验和 (Checksum):TCP 头部和数据部分都会计算一个校验和,接收端在收到数据后会重新计算校验和,如果与收到的校验和不符,则认为数据在传输过程中损坏,会丢弃该数据包。

    • 选择确认 (Selective Acknowledgement, SACK):当接收端收到乱序的数据包时,可以使用 SACK 来精确地告知发送端哪些数据包已经收到,哪些数据包丢失,从而提高重传效率,避免不必要的重复发送。

面向连接 (Connection-Oriented)

在数据传输之前,TCP 必须在发送端和接收端之间建立一个逻辑连接。这个过程就是著名的 三次握手 (Three-way Handshake)。同样,在数据传输结束后,需要通过 四次挥手 (Four-way Handshake) 来断开连接。

全双工 (Full-Duplex)

一旦 TCP 连接建立,数据可以在两个方向上同时传输,也就是说,发送端可以同时也是接收端,反之亦然。

字节流 (Byte Stream)

TCP 将应用层的数据视为一个连续的字节流,而不是独立的报文段。它负责在发送端将应用层的数据分割成适合网络传输的 TCP 报文段,并在接收端将这些报文段重组成原始的字节流。

流量控制 (Flow Control)

TCP 使用 滑动窗口 (Sliding Window) 机制来防止发送端发送数据的速度过快,以至于接收端无法及时处理而导致数据丢失。接收端会在 ACK 报文中告知发送端自己当前可接收的缓冲区大小(即接收窗口大小),发送端会根据这个窗口大小来调整发送速率。

拥塞控制 (Congestion Control)

TCP 还会考虑网络整体的拥塞情况。当检测到网络拥塞时(例如,发送的数据包没有及时收到 ACK,或者收到了重复的 ACK),TCP 会主动降低发送速率,以缓解网络的压力。这通常通过 慢启动 (Slow Start)、拥塞避免 (Congestion Avoidance)、快重传 (Fast Retransmit) 和 快恢复 (Fast Recovery) 等算法来实现。

TCP 的工作流程

1. 三次握手 (建立连接)

三次握手是为了在客户端和服务器之间建立一个可靠的连接,确保双方都准备好进行数据传输,并同步双方的初始序列号。

一开始,客户端与服务端都处于CLOSE状态,先是服务端主动监听某个端口,处于LISTEN状态。

  • 第一次握手:SYN

    • 客户端发送一个 SYN (Synchronize) 报文给服务器,请求建立连接,之后处于SYN-SEND状态。这个报文包含客户端的初始序列号 (ISN)。

  • 第二次握手:SYN-ACK

    • 服务器收到 SYN 报文后,如果同意建立连接,会向客户端发送一个 SYN-ACK 报文,之后处于SYN-RCVD状态。这个报文包含服务器自己的初始序列号 (ISN),并且 ACK 字段的值是客户端的 ISN 加 1。

  • 第三次握手:ACK

    • 客户端收到 SYN-ACK 报文后,会发送一个 ACK 报文给服务器,之后处于ESTABLISHED状态,服务端收到之后,也处于ESTHED状态。其 ACK 字段的值是服务器的 ISN 加 1。

至此,连接建立成功,双方都可以开始发送数据。

必须是三次,不能是两次。

  • 无法确认服务器是否收到了客户端的 SYN 包,也无法确认服务器的 SYN-ACK 是否能被客户端收到。

    想象一下,如果客户端发送了一个 SYN 包,但由于网络问题,这个包丢失了。客户端会认为连接没有建立。
    • 如果服务器没有收到这个 SYN 包,但客户端认为它发出了,并且假设服务器已经回复了 SYN-ACK,那么客户端可能会收到一个“延迟的”SYN-ACK 包(可能来自之前某个连接的残留)。

    • 在两次握手中,如果服务器发送了 SYN-ACK,客户端就认为连接建立了。但实际上,服务器可能并不知道客户端是否真正收到了它的 SYN-ACK,甚至可能不知道客户端是否还在线。

  • 无法正确同步双方的初始序列号。

    • TCP 的可靠性很大程度上依赖于序列号。客户端和服务器都需要知道对方的初始序列号,以便在后续的数据传输中正确地计算和确认数据的顺序。

    • 在两次握手中,客户端发送的 SYN 包含了它的初始序列号,服务器也发送了它的初始序列号。但是,服务器的 SYN-ACK 中对客户端 SYN 的确认(ACK = Client_ISN + 1)只会在客户端接收到服务器的 SYN-ACK 后才能发送。

    • 如果只有两次握手,服务器发送 SYN-ACK 后,并不知道客户端是否收到了。因此,服务器无法发送对客户端 SYN 的确认。这意味着服务器无法确切知道客户端的初始序列号是否被正确接收和理解。

  • “已连接”状态的歧义

    • 在两次握手中,当服务器收到客户端的 SYN 并回复 SYN-ACK 后,它会进入“SYN-RECEIVED”状态(表示已收到客户端的连接请求并已发送确认)。

    • 如果客户端没有收到 SYN-ACK(比如 SYN-ACK 丢失了),它会一直等待或者超时。

    • 如果客户端收到一个“旧的” SYN-ACK(来自之前已失效的连接),它可能会认为连接已建立,并发送 ACK。此时,服务器仍然处于 SYN-RECEIVED 状态,而客户端却认为连接已建立。这种状态的不一致会导致混乱。

2. 数据传输

一旦连接建立,双方就可以通过 TCP 报文段进行数据交换。每个报文段都包含序号、确认号、校验和等字段,用于保证数据的可靠传输、排序和流量控制。

3. 四次挥手 (断开连接)

当一方不再需要发送数据时,会发起断开连接的请求。由于 TCP 是全双工的,数据可能在两个方向上同时传输,因此需要四次挥手来确保双方都已完成数据传输并同意关闭连接。

  • 第一次挥手:FIN

    • 假设客户端要关闭连接,它会发送一个 FIN (Finish) 报文给服务器,表示“我这边没有数据要发了”。进入FIN_WAIT_1状态。

  • 第二次挥手:ACK

    • 服务器收到 FIN 报文后,会发送一个 ACK 报文给客户端,表示“我知道你不再发送数据了”。此时,服务器可能还有数据要发送给客户端,所以连接并没有立即关闭。进入CLOSED_WAIT状态。此时客户端收到报文后进入FIN_WAIT_2状态。

  • 第三次挥手:FIN

    • 当服务器也完成了所有数据发送后,它会向客户端发送一个 FIN 报文,表示“我也没数据要发了”。服务器进入LAST_ACK状态。

  • 第四次挥手:ACK

    • 客户端收到服务器的 FIN 报文后,会发送一个 ACK 报文给服务器,表示“我知道你也发完了”。之后进入TIME_WAIT状态。服务器收到这个 ACK 后直接进入CLOSED状态,客户端会等待一段时间(通常是 2MSL,Maximum Segment Lifetime)以确保服务器收到了最后的 ACK,然后才真正关闭连接。也进入CLOSED状态。

等待时间原因

第二次挥手时,接收到主动方的FIN后,没有立刻回复FIN而是等待一段时间,却又立刻发送ACK,由于TCP是全双工的,当客户端发送FIN时,服务器可能还有数据未发送完。如果服务器立即发送FIN,那么这些剩余数据将无法传输。服务器需要时间来处理和发送这些数据,然后再发送自己的FIN。

第四次挥手时,这个TIME_WAIT状态的目的是为了防止最后一个ACK报文丢失。如果这个ACK丢失了,服务器会认为客户端没有收到FIN,从而会重传FIN。客户端只有在收到服务器的ACK之后,才能安全地关闭连接。等待一段时间(通常是2倍的最大段生命周期,即2MSL)可以确保服务器收到这个ACK,避免连接被错误地重新打开或导致数据传输问题。

RTT往返时延

在计算机网络中它是一个重要的性能指标,表示从发送端发送数据开始,到发送端收到来自接收端的确认,总共经历的时延。

由三部分决定,即链路的传播时间,末端系统的处理时间以及路由器的缓存中的排队和处理时间。

RTO超时重传时间

TCP每发送一个报文段,就对此报文段设置一个超时重传计时器。此计时器设置的超时重信时间RTO应当略大于TCP返时延RTT,一般可取RIO=2RTT。
但是,也可以根据具体情况人为调整RTO的值,列如可以设置此超专时间还未收到对时重传时间RTO=90秒,当超过了规定的超时重信息,则必须重新传输止此TCP报文段。

以太网帧结构

报头:这是以太网帧的开始标记,用于同步接收端的时钟。

MAC头部:包含源 MAC 地址和目标 MAC 地址,用于在局域网(LAN)内识别设备。

IP头部:包含源 IP 地址和目标 IP 地址,用于在互联网上路由数据包。

TCP头部:包含端口号、序列号、确认号等,用于在应用层之间建立可靠的连接。

FCS:帧校验序列,用于检查数据在传输过程中是否发生错误。

MTU(最大传输单元):以太网中的 MTU 一般是 1500 字节。如果数据包的长度超过这个值,就需要进行分片(fragmentation)。

MSS(最大分段大小):它的计算方法是从 MTU 中减去 IP 头部TCP 头部的长度。

以太网帧结构=目标MAC(6)+源MAC(6)+类型(2)+数据(46-1500)+帧校验(4)

数据(46-1500)MTU=IP头(20)+TCP头(20+TCP选项)+用户数据MSS

以太网帧的大小范围64-1518 

MSS最大大小=1500-20(IP头)-20(TCP头)=1460

累计应答

TCP接收方通过ACK号告知发送方已连续成功接收的最大字节序号

例如收到ACK=5001表示5000及之前所有字节已确认送达

即使收到乱序报文(如6001-7000),接收方仍会坚持发送最近的有序ACK(如仍发ACK=5001)

优点是等ACK时间缩短,加快了传输速度

TCP流量控制--滑动窗口

窗口概念:TCP 是每发送一个数据,都要进行一次确认应答。当上一个数据包收到并应答了,再发送下一个。这模式就有点像我和你面对面聊天,你一句我一句,但这种方宝的缺点是效率比较低的。
有了窗口,就可以指定窗口大小,窗口大小就是指无需等待确认应答,而可以继续发送致据的最大值。

窗口大小,在三次握手时交换窗口大小,通常窗口大小由接收方窗口大小决定的,发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。

若丢包了,则接收方返回的ACK为丢包的ACK,让发送方知道并发送丢包之后的数据。

流量控制

所谓流量控制,主要是接收方传递信息给发送方,使其不要发送数据太快,是一种端到端的控制。主要的方式就是返回的ACK中会包含自己的接收窗口的大小,并且利用窗口大小来控制发送方的数据发送多少。

TCP第一次传输有效数据是在第三次握手的时候。

粘包问题

TCP是字节流传输,是一种没有边界的,可以合并的传输数据方式,合并就要能拆开,拆不开就是粘包。

发送端和接收端都可能粘包问题,而UDP则不会粘包,因为他是数据报形式传输的。

解决办法:

1.短链接

三次握手-发数据-四次挥手,每次都建立一次连接,断开一次连接,浪费时间和资源。

适用于打开浏览网页,上下次操作间隔很长。

2.先发数据长度,再发数据包

仍旧是浪费时间资源,但是比较万金油。

3.固定包大小

如果数据很长,包大小设置就很长,但也会有短数据,其余用0填满,十分浪费空间。

4.设置标志位

每传输一个数据,都在尾部设置一个标志位,一检测到标志位,就知道这个数据包结束了。

但可能会有标志位与数据重复,可能造成错误拆包,适用于数据固定的包。

心跳机制

在长连接下,有可能很长一段时间都没有数据往来。理论上说,这个连接是一直保持连接的,但是实际情况中,如果中间节点出现什么故障是难以知道的。更要命的是,有的节点(防火墙)会自动把一定时间之内没有数据交互的连接给断掉。在这个时候,就需要我们的心跳包了,用于维持长连接,保活。

心跳机制就是每隔几分钟发送一个固定信息给服务端,服务端收到后回复一个固定信息。如果服务端几分钟内没有收到客户端信息则视客户端断开。

方式:应用层自己实现的心跳包/使用SO KEEPALIVE套接字选项

Nagle算法

Nagle算法就是为了尽可能发送大块数据,避免网络中充斥着许多小数据块。Nagle算法的规则:
(1)如果包长度达到MSS(最大报文段长度),则允许发送.

(2)如果该包含有FIN,则允许发送;

(3)设置了TCP NODELAY选项,则允许发送(.

(4)设置TCPCORK选项时,若所有发出去的小数据包(包长度小于MSS)均被确认,则允许发送。

(5)上述条件都未满足,但发生了超时(一般为200ms),则立即发送, Nagle 算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如,telnet或 ssh这样的交互性比较强的程序,则需要关闭 Nagle 算法。关闭Nagle 算法的方法:
int value = 1;

setsockopt(sock fd, IPPROTO TCP, TCP NODELAY, (char*)&value, sizeof(int));

拥塞控制

网络中的链路容量和交换结点中的缓存和处理机都有着工作的极限,当网络的需求超过它们的工作极限时,就出现了拥塞。在网络出现拥堵时,如果继续发送大量数据包,可能会导致数据包时延、丢失等,这时 TCP 就会重传数据,但是一重传就会导致网络的负担更重于是会导致更大的延迟以及更多的丢包,这个情况就会进入恶性循环被不断地放大…

慢开始与拥塞避免

发送方维持一个叫做拥塞窗口cwnd(congestion window)的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞窗口,另外考虑到接受方的接收能力,发送窗口可能小于滑动窗口。

慢开始算法的思路就是,不要一开始就发送大量的数据,先探测一下网络的拥塞程度,也就是说由小到大逐渐增加拥塞窗口的大小。

为了防止cwnd增长过大引起网络拥塞,还需设置一个慢开始门限ssthresh状态变量。ssthresh的用法如下:
当cwnd<ssthresh时使用慢开始算法。

当cwnd>ssthresh时改用拥塞避免算法。

当cwnd=ssthresh时慢开始与拥塞避免算法任意,  

拥塞避免算法让拥塞窗口缓慢增长,即每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1而不是加倍。这样拥塞窗口按线性规律缓慢增长。

快重传

快恢复

TCP协议总结

面向连接的,可靠的传输,基于字节流的传输方式。

面向连接指发送数据之前必须在双端建立连接,建立连接使用“三次握手”。

可靠传输:seq和ack

基于字节流传输:粘包问题

结局方案:1.短链接 2.固定包大小 3.加结束标志位 4.先包大小,再数据

为什么TCP是可靠的

1.三次握手与四次挥手

2.重传和确认机制

3.合理的分段

4.校验重新排序

5.滑动窗口--流量控制

6.拥塞窗口--4种拥塞控制算法

TCP是一对一传播的,理论来说不能发广播的。

内容仅供参考,禁止搬运

愿这份友谊长存!

本文标签: 服务端 客户端 协议 网络