C#网络编程(一)----DNS/TCP/UDP协议

简介

计算机网络 是指将分布在不同地理位置的计算机系统、设备通过通信线路和设备连接起来,遵循共同的通信协议,以实现 数据传输、资源共享、协同工作 的系统

。它是现代信息技术的核心基础设施,支撑着互联网、物联网、云计算等众多领域的发展。

其核心要求为:

  1. 数据通信
    实现不同设备间的信息传输。
  2. 资源共享
    共享硬件资源(如打印机、存储设备)、软件资源(如应用程序、数据库)和数据资源(如文件、图片)。
  3. 分布式处理
    将复杂任务分配给多台计算机协同完成。
  4. 高可靠性与荣誉
    多条传输路径,避免单点故障。

网络模型

为了简化网络设计,实现高内聚,低耦合。网络设计采用分层架构 ,每层实现特定功能并通过接口与上下层交互。

七层模型

在分层模型中,每一层都有实体,这些实体指的是执行特定层次功能的硬件或软件组成。

协议是一套标准和规则。分完层后,用于控制同一层次内的实体如何通信

功能描述 典型协议/技术
应用层 直接为用户应用提供服务(如文件传输、电子邮件、Web服务) HTTP、FTP、SMTP、DNS
表示层 数据格式转换、加密解密、压缩解压缩(确保跨系统兼容) JPEG、SSL/TLS、ZIP
会话层 建立、管理和终止应用程序间的会话(如会话认证、断点续传) SSH、RPC、NetBIOS
传输层 端到端的数据传输控制(确保可靠性或效率) TCP、UDP
网络层 路由选择和网络互联(将数据从源网络转发到目标网络) IP、ICMP、OSPF、BGP
数据链路层 相邻节点间的帧传输,处理物理寻址和错误检测 Ethernet(MAC协议)、PPP、VLAN
物理层 比特流在物理介质上的传输(定义硬件接口、信号形式等) RJ45、光纤、Wi-Fi射频信号

四层模型

TCP/IP四层模型,也称为互联网协议套件(Internet Protocol Suite)。因为理论和实践的不同,7层模型太复杂,因此简化为4层。

将物理层与链路层合并,合并了表示层,会话层。

OSI是学术和法律上的国际标准,TCP/IP是事实上的国际标准。

还有5层模型,只是为了方便教学而臆想出的模型,不再赘述

应用层经典协议,DNS协议

访问某个网站,本质上是与某台服务器通信。为了访问服务器,我们需要使用IP地址。但IP地址不太好记。

因此,域名(Domain Name)由此而生,替代了难以记忆的IP地址。

在域名与IP中间,有一个转换协议,叫做DNS(Domain Name System,域名系统)。将域名转换成IP。

# 访问一个网站期间发生了什么?

  1. 用户请求
  2. 浏览器本地缓存检查
    浏览器先检查本地缓存中有没有域名的IP地址,没有就执行下一步
  3. 查询本地DNS服务器
    浏览器向本地DNS服务器发送查询请求,没有就执行下一步
  4. 查询根域名服务器
    本地DNS服务器向根域名服务器查询,根域名服务器返回顶级域名服务器(TLD Server)的地址
  5. 查询顶级域名服务器
    再由本地DNS服务器向顶级域名服务器查询,顶级域名服务器返回权威DNS服务器的地址
  6. 查询权威DNS服务器
    本地DNS服务器向权威DNS服务器查询,返回目标IP地址
  7. 访问目标网站
    浏览器获得IP地址访问目标网站。
  8. 建立TCP连接
    浏览器通过IP地址与服务器建立了TCP连接
  9. 发送HTTP请求
    浏览器构建HTTP请求(request header/request body/request row),将请求发送到服务器。
  10. 服务器接收请求
    服务器接收到HTTP请求后,先由反向代理(如果有的话)来判断是静态请求还是动态请求。动态请求交由后端处理(kestrel/tomcat),生成HTTP响应(response)
  11. 浏览器接收响应
    解析HTML,构建DOM树,解析CSS与JS文件。最终渲染页面。
  12. 断开连接
    TCP四次挥手断开连接。

传输层经典协议,TCP/UDP协议

PDU

协议数据单元(Protocol Data Unit,PDU,分为头部(PCI)与负载(SDU)计算机网络各层之间交换信息的单位

在不同的层中,它的title各不相同:

  1. 在链路层中叫帧(Frame)
  2. 网络层/应用层中叫数据包(Packet)
  3. 传输层中叫报文段(Segment,TCP),数据报(Datagram,UDP)

数据传输的过程

对于发送方,就是层层加码,对于接收方,则是层层解码

TCP协议

TCP是一种面向连接,可靠的,基于字节流的传输层通信协议。

可靠是建立在链路层不断地前提下,硬件断了,说啥都没用。

  1. 面向连接
    TCP是一种面向连接的协议,在数据交换之前,两个通信端必须先建立连接。
    这个连接通过三次握手(SYN-SYNACK-ACK)来建立
  2. 可靠传输
    发送方为每个报文段分配一个序列号,接收方通过发送确认应答(ACK)来确认已经收到特定序列号的报文段,如果发送方没有在超时时间内收到ACK,它将重传报文段。
  3. 流量控制
    TCP使用滑动窗口来进行流量控制,防止发送方过快地发送数据,导致接收方来不及处理。
  4. 阻塞控制
    TCP使用慢启动,阻塞避免,快重传和快恢复等算法,避免网络中地过度阻塞。动态调整数据的发送速度,从而提高网络效率。
  5. 数据排序
    由于网络延迟和路由变化,TCP报文段的到达顺序可能混乱。TCP能够通过序列号来重新排序
  6. 端到端通信
    每个TCP连接由四个元素指定地址:IP,端口,目标IP,目标端口

TCP报文段格式

  1. 源端口号与目标端口号
    每个字段16bit,标识端口号
  2. 序列号(sequence number field)
    一个32位的数,用于标识从TCP源到目标的数据字节流,确保数据的有序传输和重组。
    比如发送1000byte的数据流,TCP为数据流构建10个报文段,那么序列号就是0,100,200,300,....
  3. 确认号(acknowledgement number field)
    是对之前接收数据的确认,比如收到了122字节的,那么ack就是123,希望收到122之后字节流的意思
  4. 数据偏移(data offset)
    标识TCP头部的长度,因为TCP头部包含了可变的填充,所以长度不固定,因此需要记录长度信息。
  5. 保留(reserved)
    预保留,主要是为数据偏移溢出做准备
  6. 控制位
    一共6个bit,每个bit对应一个状态,控制TCP不同行为,比如建立连接(SYN),终止连接(FIN)。
    1. URG
      紧急标志,为1表示当前报文段紧急,接收方应该优先处理,后面的紧急指针指出了数据的位置
    2. ACK
      确认标志,为1表示该报文段已经被成功接收,连接建立后,直到释放前,ACK标志始终为1。
    3. PSH
      为1表示接收方应该立即将数据交给上层
    4. RST
      为1表示连接出现错误,要求接收方终止连接,并重新建立。
    5. SYN
      该标志用于建立连接,TCP前两次握手,SYN为1
    6. FIN
      为1表示发送发已经没有数据发送,需要断开。
  7. 窗口大小
    用于控制发送方的数据量,避免接收方的缓冲区溢出。
  8. 校验和
    用于检查整个TCP报文段的错误,确保数据的完整性。
  9. 紧急指针
    与URG控制位配合,如果为正数,表明从当前序列号开始的紧急数据的偏移量。
  10. 选项(Options)
    TCP可以包含各种可选字段,这些字段不固定,比如最大报文段大小,窗口缩放选项,选择性确认等,用于优化TCP连接的性能。
  11. 填充(padding)
    TCP是基于32位体系结构的,为了内存对齐。需要确保它的头部长度为32位的整数倍。由于选项(options)的长度不固定,所以需要使用填充来保证TCP头部长度是32位的整数倍

TCP仅仅头部是32位对齐,数据部分不要求。

TCP的状态

  1. CLOSED
    初始状态和最终状态
  2. LISTEN
    在服务端监听来自客户端的连接请求,服务端在这个状态下,等待客户端的报文。
  3. SYN_SENT
    客户端发送一个连接请求到服务端(SYN包),客户端进入SYN_SENT状态
  4. SYN_RECEIVED
    服务端接收到SYN报文后,响应一个SYN+ACK报文,同时进入SYN_RECEIVED状态
  5. ESTABLISHED
    连接已经建立成功,数据可以开始传输。三次握手完成后,双方都会进入的状态
  6. FIN_WAIT_1
    当一端完成数据发送任务后,会发送一个FIN报文,表示数据发送完成。随即进入FIN_WAIT_1状态。
  7. CLOSE_WAIT
    接收到对方的FIN报文后,发送一个ACK响应报文,并进入CLOSE_WAIT状态
  8. FIN_WAIT_2
    接收到对方的ACK响应后,进入FIN_WAIT_2状态,等待对方处理好关闭后续事件后,再次发送FIN报文。
  9. LAST_ACT
    在接收方处理完自己的数据后,会在此发送一个FIN报文,进入LAST-ACK状态
  10. TIME_WAIT
    当进入FIN_WAIT_2状态的一方,在此接收到FIN报文后。就会进入TIME_WAIT状态。并发送一个ACK报文。
    这样可以正确地关闭连接。
  11. CLOSING
    在同时关闭的情况下,当两端几乎同时发送FIN报文时,可能会进入CLOSING状态,表示双方都在等待对方的FIN报文的确认

TCP三次握手

  1. 第一次握手,SYM
    客户端发送一个TCP报文段到服务器。在这个报文段的头部中,SYN位被设为1,表明这是一个连接请求。并随机一个序列号,并在SEQ中随机一个数字x。
    此时客户端状态变为SYN_SENT状态
  2. 第二次握手,SYN+ACK
    服务器接收请求后,如果同意连接。服务端就再发送一个TCP报文段,在此报文段中,ACK位与SYN位会设为1,表明已经收到你的连接请求,顺带也要请求你。此时服务器也会随机一个数组y,作为服务器的seq,并将客户端的seq+1,作为ack返回回去。
    此时服务器进入SYN_REVD状态
  3. 第三次握手,ACK
    客户端收到服务器的应答后,还需再发送TCP报文段,在此报文段中,ACK位为1,SYN重置为0,表明这是一个单纯的应答请求。此时客户端将服务端发来的y+1,返回回去,完成三次握手。
    此时客户端与服务器状态都进入ESTABLISHED状态

说人话就是:

男:处对象吗?我微信是X。

女:处!确认一下,确认你的微信是X。顺带告诉你,我的微信号是Y.

男:收到!确认一下,你的微信号是Y。

为什么要三次握手?

TCP 三次握手(Three-Way Handshake)是建立可靠连接的核心机制,其设计目的是确保通信双方同步初始序列号(ISN)、确认双向通信能力,并避免旧连接的残留数据包干扰新连接。

  1. 同步初始序列号(ISN)
    通信双方通过交换SYN包各自的seq,避免数据混淆。

为什么需要初始化ISN时要随机? 类似数据库的自增主键,很容易被预测出下一个的值而遭到攻击。所以需要随机。

  1. 确认双向通信能力
    服务端收到SYN,确认客户端能发送数据
    客户端收到SYN-ACK,确认服务端收发能力
    服务端收到ACK,确认客户端接收能力

如果仅仅两次握手,那么无法确认客户端是否有接收能力。

  1. 防止旧连接数据包干扰
    若网络中存在旧连接的延迟SYN包(如因网络拥塞滞留的数据包),通过三次握手的序列号机制可识别。
    因为客户端的ISN是基于随机数的递增,旧SYN包的序列号会小于当前ISN,客户端收到后直接忽略。

如果仅仅两次握手,服务器会无法识别旧数据包。因为不知道第三次握手的ack。

半连接队列/全连接队列

当服务器端进入LISTEND状态时,会在内部创建两个队列。半连接队列(SYN Queue)与全连接队列(ACCEPT Queue)。

SYN Flood Attack(SYN泛洪攻击)

SYN泛洪水攻击是一种经典的DOS攻击,利用了 TCP 协议的设计缺陷。攻击者伪造大量虚假IP地址,向服务器发送海量SYN包,服务器会为每个SYN连接请求分配资源(内存,句柄/FD)等,并返回SYN-ACK。但攻击者永远不会回应(ACK),导致这种半开链接长期占用资源(需要超时自动释放),当连接队列被占满时,服务器就无法处理正常用户的请求。

防御手段:

  1. SYN Cookies(延时分配资源)
    服务器在收到SYN时,不立即分配资源。而是根据客户端的IP,端口号生成一个Cookie,并将其嵌入到SYN-ACK报文段中。当客户端返回ACK时,验证Cookie是否合法,再分配资源,创建连接。
  2. 调整参数
    增大半开连接队列,缩短超时时间。目的是减少无效请求对资源的占用。
  3. 防火墙拦截
    针对IP,或者短时间内不合理的SYN请求进行丢弃。
  4. SYN Proxy
    在服务器前端部署反向代理,先由代理服务器完成于客户端三次握手,再将连接转发给服务器。

DDoS/Dos攻击本质上是一场社会工程学的较量,所谓道高一尺,魔高一丈,无法一劳永逸的防御,只能不断提高攻击者的成本,让攻击者知难而退。

TCP数据传输

  1. 延时确认

    接收方在接收到报文段后,不会立即发送ACK。而是等待一段时间,看有没有报文继续发送过来,以此来降低网络开销。

  2. 累积确认

    TCP连接的建立和释放,ACK报文的值为接收到的seq/fin的值+1。
    同样,在数据传输过程中,ack报文的值也会根据seq的值产生变化。 ack=seq+接收到的数据长度

    因此,当多个报文过来时,把报文累计起来,多个报文的ACK合并为一个ACK。

  3. 超时重传

    当发送方,如果未在预定时间内接收到ACK。发送发会重新发送。

  4. 滑动窗口(接收方)

    让发送端根据接收端的实际接收能力控制发送的数据量。根据缓冲区的窗口大小,接收方可以在返回的ACK报文中,告诉发送方,当前缓冲区还剩下多少,当窗口大小为0时,发送发会暂停发送,一点时间后,待接收方窗口足够了。再发送报文通知发送方。

  5. 拥堵,阻塞控制(发送方)

    拥塞控制(Congestion Control)是计算机网络中用于防止网络因数据流量过大而导致性能下降甚至瘫痪的关键机制。它通过动态调整发送方的数据传输速率,使网络负载维持在合理范围内,确保网络资源的公平分配和高效利用

    当网络出现拥塞时,会导致数据包延迟,丢失等问题。当丢包到达一定时间,又会触发TCP重传机制,又进一步加重了网络负担。所以当网络拥塞时,TCP会自动降低发送的数据量。

    举个例子:当高速拥堵时,交管部门就会封了某些高速入口,只出不进.

    TCP 通过 拥塞窗口(Congestion Window, cwnd) 控制发送方的速率,其大小反映了发送方对当前网络拥塞程度的估计。拥塞控制算法通过调整 cwnd 实现流量调控,主要包括以下四个阶段:

    • 慢启动:
      cwnd一开始比较小,每收到一个ack,就指数增长。当cwnd达到慢启动的一个阈值(Slow start Threshold)时,进入拥塞避免阶段,此时每收到一个ack,cwnd就只+1。
    • 拥塞避免:
      随着网络的拥堵,如果限定时间内未收到ack,就会判定超时。将阈值调整为当前cwnd的一半,并将cwnd重置为1,重新开启慢启动过程。
    • 快速重传:
      当发送方连续收到3个重复的ack时,马上判定报文段丢失,立即重传,而不是等待ack。
    • 快速恢复:
      当快速重传发生时,阈值调整为当前cwnd的一半,不必从1开始。

偷个懒,盗了尚硅谷的图。

综上所述:发送方的窗口大小应该是拥塞窗口与接收窗口两者的最小值

TCP四次挥手

  1. 第一次挥手

    客户端向服务端发送一个FIN结束报文,表示客户端发送完毕。但仍然可以接收数据,进入FIN-WAIT-1状态

  2. 第二次挥手

    服务端收到FIN报文后,向客户端发送一个ACK报文,确认收到。服务端进入CLOSE-WAIT状态并开始处理收尾工作,客户端收到ACK报文,进入FIN-WAIT-2状态。

  3. 第三次挥手

    服务端擦完屁股后,向客户端发送一个FIN报文,表示服务端也没有数据要发送了。服务器进入LAST-ACK状态

  4. 第四次挥手

    客户端收到FIN报文后,向服务端发送ACK报文,确认收到。服务端进入CLOSEED状态。客户端进入TIME-WAIT状态,等待一段时间确保服务端收到ACK报文。等待2MSL后客户端变为CLOSEED状态,连接断开

说人话就是:

男:分手吧,我累了。

女:分就分!我把我的东西从房里搬出去。

......

......

女:我收拾完了,再见

男:再见,再也不见。

为什么要四次挥手?

TCP采用 "四次挥手" 来释放连接,是由其全双工通信特性决定的。在全双工模式下,通信双方(客户端和服务器)各自独立管理发送和接收通道,因此需要分别关闭两个方向的数据流,确保数据完整传输且连接安全终止。

如果降低为3次,就会导服务端还有数据没发送完毕。连接就关闭的现象,数据就会丢失。

为什么要等待2MSL,才进入CLOSED状态?

在TCP四次挥手过程中,客户端在发送最后一个ACK包后会进入 TIME_WAIT 状态,并等待 2 倍 MSL(Maximum Segment Lifetime,最大段生命周期)的时间后才会最终关闭连接(进入 CLOSED 状态)。这一设计的核心目的是确保连接安全关闭,并避免旧连接的残留数据干扰新连接

  1. 确保ACK成功到达服务器
    客户端发送的ACK包可能丢失,此时,服务端一直未收到ACK。所以会重发第三次挥手的FIN包,重新请求关闭连接。
    此时等待2MSL时间,客户端会在TIME_WAIT状态中收到服务器重发的FIN包,重新发送ACK进行确认,并重置2MSL的定时器,确保服务器正确关闭连接。
    若不等待直接关闭:客户端已关闭连接,无法响应服务器的重发FIN,导致服务器因超时未收到AC 而持续处于 LAST_ACK状态,浪费资源。
  2. 防止旧连接的数据包干扰新连接
    假设第四次挥手后,又马上建立一个连接。此时,旧连接中延迟到达的数据包可能仍在网络中传输。
    此时等待2MSL时间,确保旧连接的所有数据包都已超过MSL存活时间,被网络设备完全丢弃。
    若不等待直接关闭:新连接接收到旧连接的数据包,导致出现混乱数据。

为什么是2MSL,而不是1MSL?

1MSL是单向路径的最大延迟,一个数据包的往返时间最大不超过2MSL。所以取最大值。

等待2MSL可确保:

  1. 最后一个 ACK 包的往返确认(若丢失可重发)。
  2. 旧连接的所有数据包(无论发送方向)都已从网络中消失。

其本质是类似流浪地球电影中的饱和式救援,保证容错与隔离。

TCP重传机制

TCP(传输控制协议)重传机制是保障数据可靠传输的关键部分,它能够应对网络传输过程中可能出现的数据包丢失、损坏等问题。

  1. 超时重传
    发送方在发送数据包后,会为每一个数据包启动一个超时定时器。如果在超时时间(Retransmission Time-Out,RTO)外没有收到ACK。发送方就认为数据包丢失,从而触发重传操作。

RTO的计算是动态调整的过程,与往返时间(RTT,Round-Trip Time)相关。如果发生超时重传后仍然没有收到 ACK,后续的 RTO 会以指数形式增长,即每次重传时 RTO 翻倍,这就是指数退避策略,目的是避免大量重传数据包同时涌入网络,进一步加剧网络拥塞。

  1. 快速重传

    当接收方收到乱序的数据包时,会立即发送重复的ACK,表明自己期望收到的包的序列号。

    如果发送方连续收到3个重复的ACK,就会认为这个ACK所对应的数据包之后的所有数据都丢失,立即重传,不等待超时定时到期。

  2. 选择确认重传(SACK)

    快速重传解决了超时时间的问题,但面临另一个问题:重所有数据,会造成重复发送

    为了解决此问题,选择确认重传允许接收方在 ACK 中告知发送方哪些数据包已经正确接收,即使这些数据包是不连续的。这样发送方就可以只重传真正丢失的数据包,而不是重传从丢失数据包开始的所有未确认数据包,提高了重传效率

  3. 重复ASCK(D-SACK)

    Duplicate SACK,是在 SACK 的基础上做了一些扩展。

    接收方在接收到重复的数据包时,会直接丢弃这些重复数据包,并向发送方发送相应的ACK,避免对重复数据进行不必要的处理。

看上去没什么卵拥,我都已经接收到数据了,只是重复了而已,丢弃就行。为什么还要发送ACK发送端呢?

主要目的是帮助发送方判断,是包失序,ACK丢失,包重复,伪重传。让发送端知晓更多信息,更好做网络控制。

TCP的粘包与拆包

TCP协议是面向字节流的,它本身不保证消息边界。 也就是说,应用层的多个数据包可能会被合并成一个TCP段来传输,一个数据包也可能被拆分为多个TCP段

其本质是应用层消息边界在TCP流中消失

究其发生的原因,无外乎两种,数据包过小与过大。

  1. 数据包过小
    TCP会将多个数据包合并为一个TCP段。发生粘包
  2. 数据包过大
    CP 会将其拆分为多个段传输(如 10KB 数据拆成 3 个段),发生拆包,接收方需重组后才能获取完整消息。
  3. 接收端读取数据不及时
    多个TCP段累计在缓冲区中。无法一次性读完,因此不知道数据的边界在哪。发生粘包。

解决粘包/拆包的核心思路

  1. 消息定长
    每个数据包的长度固定
  2. 分割符标记
    比如C++中经常出现的EOF标记
  3. 头部包含长度字段
    在每个数据包的头部添加一个固定长度的字段,标明消息体的总长度。接收方先读取头部获取长度,再按长度读取完整消息体

UDP协议

特性 TCP(传输控制协议) UDP(用户数据报协议)
连接方式 面向连接(三次握手建立连接,四次挥手断开连接) 无连接(直接发送数据报,无需预先建立通道)
可靠性 可靠传输: - 序列号与确认应答(ACK) - 超时重传与快速重传 - 流量控制(滑动窗口) - 拥塞控制(慢启动、拥塞避免) 不可靠传输: - 不保证数据到达、顺序或完整性 - 无重传、流量控制或拥塞控制
传输模式 流式传输: - 数据无边界,需应用层处理消息边界(可能出现粘包问题) 数据报传输: - 每个数据报独立传输,接收方明确获取边界
首部开销 固定 20 字节(含序列号、确认号、窗口大小等复杂字段) 仅 8 字节(源端口、目标端口、长度、校验和)
延迟与速度 延迟较高(连接建立/重传机制耗时),传输速度较慢 延迟低(无连接/轻量级协议),传输速度快
应用场景 - 需可靠性场景 :HTTP/HTTPS(网页浏览)、FTP(文件传输)、SMTP(邮件)、SSH(远程登录) - 典型案例:下载文件时确保数据完整 - 需实时性场景 :视频会议(WebRTC)、在线游戏(如《王者荣耀》)、直播 - 典型案例:视频直播中容忍少量丢包以降低延迟
其他关键差异 - 全双工通信 :支持双向同时传输 - 面向字节流:数据以连续字节流传输 - 无连接状态 :无需维护连接参数 - 数据报独立性:每个数据包独立寻址
协议结构 - 首部包含 16 位源端口、16 位目标端口、32 位序列号、32 位确认号等 - 首部仅 4 个字段:源端口、目标端口、长度、校验和
拥塞控制 支持(通过慢启动、快速恢复等算法) 不支持(可能加剧网络拥塞)
适用网络环境 复杂网络(如互联网),需处理高丢包率和延迟 可靠网络或对可靠性要求低的场景(如局域网)