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

Linux高级--2.4.1 网络概念(分层、TCP)

关于网络分层理解的难点 

        对于一般人(不参与设计和维护网络协议栈的人)来讲,物理层和应用层很容易理解,也很好记住。首先,物理层是看的到的网线、基站的实体。再者,应用层是用户自己参与编写的程序。

而那些不长接触的数据链路层、IP层、网络层 确实最难理解和记住的。下面的内容会重点讲这三层。

        应该很多人有这样的疑问:IP层和网络层为什么不合并在一起,既然分层那这两层的界限在哪?数据链路层又和IP层之间的界限又在哪?

        IP层和网络层为什么不合并在一起?

                1. 有些协议只工作在IP层,比如IPv4/IPv6、ICMP、ARP、RIP、OSPF、BGP、

                    IGMP等。(这些协议操作是针对IP的存在相关的)

                2. IP层对于包的传输有自己的策略:上层给的长度大于MTU,则分片,收到分片的包要

                    合并,一个片丢了则所有片都丢掉。校验不过则丢掉,没有重传、备份、确认机制。

                    这样尽可能保证IP层任务处理的简单性,最小化性---这也是保证运行在IP层协议的

                    轻量性。至于数据完整性,则靠上层协议自己保证--》使得上层协议的的多样性。

                    因为上层协议就是可能必须要可靠传输(TCP),或者必须要不可靠传输(UDP)。

        IP层和MAC层为什么不合并?

                1. 有的协议只在MAC层工作:主要涉及物理地址和链路层的控制,如Ethernet、gPTP、

                    HDLC、PPP、Wi-Fi、VLAN、Frame Relay、FDDI等。(这些协议是针对设备的操

                    作,因为一个设备通常是一个网卡)

                2. MAC层没有分包和组包,因为网卡为了保证传输的速度只管收包,且MAC一般依赖网

                     卡的DMA技术快速缓存移动(不依赖CPU),没法分包和组包。所以分包和组包的

                     工作就交给上层了。

                3. MAC层有重传,对设备来说是守大门的,对于最基本的CRC校验错误的包,不能让流

                    转回上层,否则就浪费了资源和时间。

        

// 一个IPv4数据包的结构
struct ip_packet {
    uint8_t version;      // IP版本(IPv4或IPv6)
    uint8_t ihl;          // IP头部长度
    uint8_t tos;          // 服务类型
    uint16_t total_len;   // 总长度(IP头部 + 数据部分)
    uint16_t id;          // 标识
    uint16_t frag_off;    // 分片偏移
    uint8_t ttl;          // 生存时间
    uint8_t protocol;     // 协议类型(TCP或UDP等)
    uint16_t checksum;    // 校验和
    uint32_t src_ip;      // 源IP地址
    uint32_t dest_ip;     // 目的IP地址
    uint8_t *data;        // 数据部分(载荷),指向TCP或UDP段
};

struct tcp_segment {
    uint16_t src_port;       // 源端口
    uint16_t dest_port;      // 目的端口
    uint32_t seq_num;        // 序列号
    uint32_t ack_num;        // 确认号
    uint8_t data_offset;     // 数据偏移(TCP头部长度)
    uint8_t flags;           // 控制标志位(如SYN, ACK, FIN等)
    uint16_t window_size;    // 窗口大小
    uint16_t checksum;       // 校验和
    uint16_t urgent_pointer; // 紧急指针
    uint8_t *data;           // 数据部分(载荷)
};

struct udp_datagram {
    uint16_t src_port;       // 源端口
    uint16_t dest_port;      // 目的端口
    uint16_t length;         // UDP长度(头部 + 数据)
    uint16_t checksum;       // 校验和
    uint8_t *data;           // 数据部分(载荷)
};

struct sock {
    struct socket *socket;             // 套接字的实际结构
    struct net_device *dev;            // 绑定的网络设备
    struct sockaddr_in local_addr;     // 本地地址和端口
    struct sockaddr_in remote_addr;    // 远程地址和端口
    unsigned short state;              // 套接字的状态
    unsigned int flags;                // 套接字的标志
    struct sk_buff_head sk_receive_queue; // 用于接收数据的队列
    struct sk_buff_head sk_send_queue;    // 用于发送数据的队列
    int protocol;                      // 协议类型(UDP,TCP等)
    ...
};


struct tcb {
    unsigned long seq;            // 当前的发送序列号
    unsigned long ack;            // 当前的接收确认号
    unsigned long snd_wnd;        // 发送窗口大小
    unsigned long rcv_wnd;        // 接收窗口大小
    unsigned long snd_nxt;        // 下一次要发送的序列号
    unsigned long rcv_nxt;        // 下一次要接收的序列号
    unsigned long rcv_buf_size;   // 接收缓冲区大小
    struct sockaddr_in src_addr;  // 源地址
    struct sockaddr_in dest_addr; // 目标地址
    int state;                    // 连接的当前状态(例如 ESTABLISHED, SYN_SENT 等)
    // 其他字段,包括缓冲区、计时器、重传机制等
};


struct sk_buff {
    struct sk_buff *next;       // 链表中的下一个skb,用于链表或队列中的组织
    struct sk_buff *prev;       // 链表中的前一个skb
    struct net_device *dev;     // 指向网络设备的指针(网络接口卡)
    unsigned int len;           // 数据包长度
    unsigned char *data;        // 数据指针,指向包的有效载荷
    unsigned char *tail;        // 数据包尾指针
    unsigned char *end;         // 数据包的结束指针
    unsigned char *head;        // 数据包的起始指针
    struct socket *sk;          // 套接字,指向相关的socket
    unsigned int protocol;      // 协议字段,标识当前skb的数据类型(比如IP,ARP等)
    unsigned int priority;      // 包的优先级
    struct dst_entry *dst;      // 路由目的地信息(用于IP层)
    struct sk_buff *next_free;  // 空闲链表中的下一个skb
    ...
};

网络层与传输层概述

网络层:
  • 抽象概念:网络层是基于 IP 的抽象概念,与数据链路层用 MAC 地址标记设备不同。MAC 地址是一种具体化的概念,绑定于所在的物理网络,而 IP 地址可以是固定的,也可以通过路由动态变化。
  • 功能:所有“网络”的“网”是基于 IP 的抽象概念,而链路层的“路”是基于 MAC 的具体化概念。
传输层:
  • 协议决定内容:传输层与物理信号的形式(光信号、电信号、无线信号)或目的地无关,传输层的协议决定了传输的内容是可靠的还是不可靠的。例如:
    • 不可靠传输协议:适用于允许丢帧和数据丢失的流媒体传输。
    • 可靠传输协议:适用于需要保密、稳定且不允许数据损坏或丢失的场景,并能控制传输的速度和质量。

数据链路层的作用与网卡驱动

数据链路层的命名

数据链路层之所以称为“链路层”,是因为它负责在物理链路上传输数据。链路指网络中相邻节点之间的物理连接(例如两台计算机或交换机)。

数据链路层的主要作用
  1. 帧的封装和拆解

    • 把网络层的数据打包成帧(Frame)。
    • 在接收端解包帧,提取网络层数据。
  2. 差错检测与纠正

    • 使用校验和机制(如 CRC)检测传输过程中的错误。
    • 部分情况下还能纠正错误。
  3. 流量控制与重传

    • 控制数据流速,防止发送过快导致接收端无法处理。
    • 在数据帧丢失时请求重传。
  4. MAC 地址管理

    • 确保数据在局域网内正确发送到目标设备。
数据链路层与网卡驱动的关系
  • 数据链路层的职责:定义如何在物理链路上传输帧,并确保数据传输的可靠性。
  • 网卡驱动的职责
    • 是数据链路层的一部分。
    • 负责从网卡接收数据并传递给网络层。
    • 与操作系统协作完成数据链路层功能。
数据链路层与网络层的关系
  1. 网络层的职责:负责逻辑寻址和跨网络的数据路由。
  2. 网络层的工作方式
    • 接收来自链路层的帧,解封装出 IP 数据包。
    • 基于 IP 地址和路由信息,将数据传递到目的地。

TCP/IP 协议栈

 tcp组包时 校验和 计算 -- 按照协议中指出,校验和本身在校验时 作为0计算。

校验和前面 还包含了 伪包头 包含了IP地址,是为了防止路由错IP 

主要组成部分
  1. 链路层(Link Layer):对应 OSI 模型中的数据链路层。
  2. 网络层(Internet Layer):主要协议是 IP,负责跨网络的数据路由。
  3. 传输层(Transport Layer):如 TCP 和 UDP,提供端到端的数据传输。
  4. 应用层(Application Layer):处理应用数据和服务,如 HTTP、FTP。
数据包的传递流程
  • 链路层负责在物理网络中传输数据帧。
  • 网络层通过路由实现数据的跨网络传递。
  • 传输层提供可靠或不可靠的数据传输服务。

网卡驱动与 sk_buff 结构体

sk_buff 的作用
  • 是 Linux 内核中描述网络数据包的重要数据结构。
  • 用于存储和管理网络协议栈的所有数据包。
典型的数据包接收流程
  1. 网卡驱动接收数据
    • 网卡驱动接收到数据包后,分配一个 sk_buff
    • 将数据存储到 sk_buff 的缓冲区中。
  2. 传递给协议栈
    • 通过 netif_rx() 等函数将 sk_buff 传递给网络层。
  3. 网络层处理
    • 解封装数据,提取 IP 包进行进一步处理。
sk_buff 的主要字段
  • data:指向数据部分。
  • len:数据长度。
  • protocol:上层协议类型。
  • dev:关联的网络设备。
  • 头部信息指针:mac_headernetwork_headertransport_header
sk_buff 示例
struct sk_buff *skb = netdev_alloc_skb(dev, len);
if (!skb) {
    return -ENOMEM;
}
memcpy(skb_put(skb, len), data, len);
skb->protocol = eth_type_trans(skb, dev);
netif_rx(skb);

传输层与 TCB 结构体

TCB 的作用
  • 传输控制块(TCB, Transmission Control Block)用于管理和维护 TCP 连接。
  • 在Linux内核中,TCB(Transmission Control Block)是专门为TCP通信服务的,它是TCP连接的核心数据结构,用来维护和管理每个TCP连接的状态。在UDP中并没有类似于TCP的TCB结构,因为UDP是无连接的协议,它不需要维护像TCP那样的复杂状态。

    对于TCP,TCB通常是指Linux内核中的struct tcp_sock,它继承自struct inet_sock,而后者又继承自struct sock。这些结构体联合起来负责维护一个TCP连接的各种状态和信息。

    ,TCB 主要由 struct tcp_sock 表示。
tcp_sock 定义位置
  • 定义于 include/net/tcp.h
TCB(struct tcp_sock)的主要成员变量:
  1. 发送和接收窗口

    • snd_wnd:发送窗口的大小。
    • rcv_wnd:接收窗口的大小。
    • snd_nxt:下一个要发送的字节序列号。
    • rcv_nxt:下一个期望接收的字节序列号。
  2. 重传和拥塞控制

    • snd_cwnd:拥塞窗口,用于拥塞控制。
    • snd_ssthresh:慢启动阈值,用于拥塞控制。
    • retransmit_timer:重传定时器,用于控制何时触发重传。
  3. 连接状态和控制

    • state:TCP连接的状态(例如,ESTABLISHEDSYN_SENTCLOSED等)。
    • fastopen_req:快速打开请求的相关信息。
  4. RTT(往返时间)和RTO(重传超时)

    • srtt_us:平滑的往返时间,用来估算RTO。
    • rttvar_us:RTT变化,用于计算RTO。
    • rto:重传超时的值。
  5. 序列号和确认号

    • snd_una:已发送但未确认的最早字节序列号。
    • snd_up:紧急指针的序列号。
    • rcv_up:接收端的紧急指针。
  6. TCP选项

    • tcp_header_len:TCP头的长度。
    • tcp_mstamp:TCP报文的时间戳。
    • options:TCP连接的选项,例如SACK(选择性确认)、TS(时间戳)等。
  7. 定时器

    • delack_timer:用于延迟确认ACK的定时器。
    • retransmit_timer:重传定时器。
tcp_sock 的连接生命周期
  1. 初始化:三次握手时分配并初始化。
  2. 状态更新:更新发送和接收窗口的序列号。
  3. 关闭连接:释放 tcp_sock,完成连接关闭。
传输层的数据流向
  • 发送方向
    • 数据通过 send() 进入传输层,存储到发送缓冲区。
    • 在合适时机发送到网络。
  • 接收方向
    • 数据通过网卡驱动传递到 sk_buff,存储到接收缓冲区。
    • 应用层通过 recv() 获取数据。
tcp_sock 示例
struct tcp_sock {
    __u32 snd_nxt;
    __u32 rcv_nxt;
    __u32 snd_una;
    struct sk_buff_head write_queue;
    struct sk_buff_head receive_queue;
    // 其他状态变量
};
TCB的连接状态和半连接状态:

是的,在TCP协议中,TCB(Transmission Control Block)不仅维护与连接状态相关的信息,还负责管理全连接队列半连接队列,这些队列在TCP三次握手过程中起着非常重要的作用。

在Linux内核中,全连接队列半连接队列分别管理已经建立和正在建立的TCP连接。这些队列的核心与struct sock以及相关的数据结构密切相关。

  1. 半连接队列(Syn Queue)
    • 半连接队列中保存的是处于SYN_RECEIVED状态的连接,即那些客户端发起了SYN请求,服务端回复了SYN+ACK,但还未收到客户端的ACK确认的连接。
    • 这些连接还没有完全建立,仍在等待客户端的最终ACK。
    • 半连接队列是为了解决TCP三次握手中的中间状态,常用于抗击SYN Flood攻击(半连接攻击)等问题。

    在Linux内核中,半连接队列由struct request_sock管理,它是inet_csk模块的一部分。半连接队列使用inet_csk_reqsk_queue来处理。

  2. 全连接队列(Accept Queue)
    • 全连接队列中保存的是已经完成三次握手、处于ESTABLISHED状态的连接,即已经完全建立的TCP连接。
    • 服务端已经准备好接受数据传输,等待应用程序调用accept()函数来从队列中取出连接。
    • 全连接队列的大小可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog参数进行调整,它定义了全连接队列的最大长度。

    在Linux内核中,已建立的连接由struct sock(具体来说是struct tcp_sock)管理,已完成的连接进入全连接队列等待应用层程序的处理。

相关结构体
  • 半连接队列(struct request_sock:用于管理尚未完全建立的连接。
  • 全连接队列(struct tcp_sock:用于管理已经建立的TCP连接。
三次握手过程中的队列变化
  1. 客户端发送SYN

    • 服务端收到客户端的SYN请求后,在半连接队列中创建一个新的request_sock条目,并进入SYN_RECEIVED状态。
  2. 服务端发送SYN+ACK

    • 该连接在半连接队列中继续等待客户端的ACK确认。
  3. 客户端发送ACK

    • 一旦服务端收到客户端的ACK,三次握手完成,服务端会将该连接从半连接队列中移出,放入全连接队列中。
    • 之后,服务端等待应用程序通过accept()函数来处理这个连接。
总结
  • 半连接队列:用于存放正在进行TCP三次握手、未完全建立的连接,服务端处于SYN_RECEIVED状态。
  • 全连接队列:存放已完成三次握手、处于ESTABLISHED状态的连接,等待应用层处理。

这两个队列在TCP连接建立过程中起到了维护连接状态、管理连接流量的作用。


0voice · GitHub



http://www.kler.cn/a/452897.html

相关文章:

  • c/c++ 无法跳转定义
  • Apache Doris 创始人:何为“现代化”的数据仓库?
  • 【流量、洪水数据下载】网站介绍和下载经验....不断更新!
  • C++软件设计模式之装饰器模式
  • 设计模式的主要分类是什么?请简要介绍每个分类的特点。
  • Mono里运行C#脚本5—mono_file_map_open
  • webpakc介绍
  • 一个从oracle使用spool导出数据到kadb的脚本
  • 基于Springcloud的智能社区服务系统
  • 浅谈Java注解之ResponseBody
  • CentOS7-yum服务器的搭建
  • Pytorch详解 train() 和 eval() 模式会影响Layer Norm吗?(中英双语)
  • 无人机之惯性导航概述!
  • 【ES6复习笔记】Map(14)
  • YOLO11改进-模块-引入星型运算Star Blocks
  • 在vscode中的ESP-IDF插件中使用Arduino框架作为组件
  • 鸿蒙-什么是Ability Kit
  • 人才画像系统如何支撑企业的人才战略落地
  • 【React 基础及高级用法】
  • Docker安装Neo4j
  • SQL进阶技巧:如何求解最大矩形面积问题? | LeetCode 84- 柱状图中最大的矩形
  • 【GD32】从零开始学GD32单片机 | DAC数模转换器 + 三角波输出例程
  • 浅谈TARA在汽车网络安全中的关键角色
  • adb 安装教程
  • nginx-rewrite(多种实现方法)
  • 从一次线上故障聊聊接口自动化测试