TCP/IP协议深度解析

本文深入探讨了TCP/IP协议栈的实现,包括物理层、数据链路层、网络层和传输层的主要协议,如以太网、IP、UDP和TCP。详细阐述了TCP的顺序保证、拥塞控制、连接管理和状态迁移,并提供了简单的UDP数据包解析代码示例。同时提到了netmap库在捕获和解析网络数据包中的应用。
摘要由CSDN通过智能技术生成
  1. TCP如何有序性的
  2. ARP
  3. TCP的拥塞控制
  4. TCP的半连接队列与全连接队列
  5. TCP粘包与分包
  6. 网线断了, TCP怎么处理
  7. TCP的长连接与短连接
  8. TCP的11个状态迁移
  9. send返回正数, 是不是成功?
  10. 阻塞与非阻塞, send/recv, -1
如何取到一帧完整的数据包
  1. raw socket
  2. netmap
  3. dpdk

1 网络协议栈

1.1 OSI模型

在这里插入图片描述

物理层: 传输的光电信号
数据链路层: 数字信号
网卡: 将数字信号转换为光电信号(AD/DA), 因此不属于协议栈的那一层

1.2 协议栈是什么 ?

根据分层的思想, 每一层都有对应得协议, 从而形成一个协议家族 --> 协议栈

1.3 协议栈的实现
  1. linux中, 网卡利用sk_buff实现将数据传进内核协议栈中, 接着协议栈进行解析

  2. 因此如果要自己实现协议栈解析, 就需要获取网卡内存中的数据, 这里利用netmap, 可实现截获网卡中的数据, 并将网卡内存映射到本机内存空间中(netmap利用mmap实现映射, 底层是DMA)
    在这里插入图片描述


1.3.1 UDP协议
0) 一帧完整的udp数据
  • 从网卡收到的数据
    在这里插入图片描述
struct udppkt {
    struct ethhdr eth;
    struct iphdr ip;
    struct udphdr udp;
    unsigned char body[0];
};
1) 以太网协议头

在这里插入图片描述

#define HEAD_LENGTH         6
struct ethhdr {
    unsigned char dst[HEAD_LENGTH];
    unsigned char src[HEAD_LENGTH];
    unsigned short proto;
};
2) IP协议头

在这里插入图片描述

struct iphdr {
    unsigned char version   : 4,
                  hdrlen    : 4;
    unsigned char tos;
    unsigned short tot_len;
    unsigned short id;
    unsigned short flag     : 3,
                   offset   : 13;
    unsigned char ttl;
    unsigned char proto;
    unsigned short check;
    unsigned int sip;
    unsigned int dip;
};
3) UDP协议头

在这里插入图片描述

struct udphdr {
    unsigned short sport;
    unsigned short dport;
    unsigned short length;
    unsigned short check;
};
* 协议栈实现时会遇到的问题及其原理
  1. ping不通 --> 没有实现ICMP
  2. 过一段时间后, send数据不成功 --> 没有实现ARP, 源地址没得到回应, 会以为目标地址已经连接不上
  • 因此我们自己实现一个协议栈, 每一个协议都要实现

1.3.2 TCP协议实现

有了前面 arp/icmp/udp 我们就可以实现 tcp

0) 如何实现tcp
  1. 协议头
  2. 三次握手/四次挥手, 11个状态迁移
  3. tcp的顺序, 如何保证有序, 延迟ack/滑动窗口
  4. 定时器, rtt, 慢启动与拥塞控制
  5. POSIX send/recv接口, epoll如何实现 (与协议栈交互)
1) tcp协议头

在这里插入图片描述

struct tcphdr {
    unsigned short sport;
    unsigned short dport;
    unsigned int seqnum;
    unsigned int acknum;
    unsigned char hdrlen    : 4,
                  resv      : 4;
    unsigned char cwr       : 1,
                  ece       : 1,
                  urg       : 1,
                  ack       : 1,
                  psh       : 1,
                  rst       : 1,
                  syn       : 1,
                  fin       : 1;
    unsigned short wsize;
    
    unsigned short tcpcheck;
    unsigned short urg_pointer;
    unsigned char options[0];
};
2) 三次握手/四次挥手, 11个状态迁移
  • 三次握手
    在这里插入图片描述
    1> 当服务器listen时, 收到客户端的syn, 会往syn队列中加入TCB, TCB中包含五元组(为了标识是哪个客户端)
    2> 当三次握手完成时, 会对比连接是否在syn队列里, 如果有则将syn上的TCB搬到accept队列里(队列的移除和插入)
    备注: 开始的seqnum为随机值

  • 四次挥手
    在这里插入图片描述

  • 11个状态迁移
    在这里插入图片描述

  • 四次挥手时, 根据情况FIN_WAIT_1会有不同:
    1> 先ack --> 后fin FIN_WAIT2
    2> 先fin --> 后ack CLOSING
    3> 同时ack+fin TIME_WAI

  • 出现很多CLOSE_WAIT: 被动方没及时close

  • 主动方出现fin_wait_2: 对方处理; 无解, kill把进程干掉; 再建立, connect

  • time_wait 是用来干什么的: 保证ack能够发成功, 它改变状态只能通过定时器超时, 超时后变为close状态

3) 延迟ack/滑动窗口
  • 如果采用发送一帧数据, 等待收到对方应答, 再发送一帧数据的方式, 这样效率低下, 因此采用连续发送多个包, 然后等待接收对方的确认. 这样效率高了, 但也产生了一个问题, 即连续发送多个包无法保证到达对方的时候包的顺序是连续的, 因此有了tcp如何保证有序的问题

  • 延迟ack
    可通过设置取消, 除非网络环境好, 要不然不建议取消
    1> 接收方在收到数据后, 并不会立即回复ACK, 而是会延迟一定的时间. 每次收到数据包后, 定时器会定时200ms, 直到超时
    2> 接收方如果有数据要发送, 那便会在发送的数据包内带上ACK. 这样可避免大量的ACK以一个tcp包发送的情况

  • 滑动窗口
    滑动窗口的大小是变化的
    1> 接收端在延迟ack超时时, 会在ACK包里带上最小没收到的包号
    2> 发送端收到ACK后, 会将最小没收到的包号及之后窗口内的包全部重发(不管之前有没有收到)

4) tcp四个重要的定时器
  1. 超时重传
  2. 坚持定时器(探测)
  3. keep alive
  4. time_wait
5) RTT
  • 一次数据往返所需要的时间
  • 计算方式 T(n) = 0.9 * T(n-1) + 0.1 * cur_t
6) 慢启动与拥塞控制
  • 决定每次连续发送多少个包
    在这里插入图片描述

慢启动: 开始从1 -> 2 -> 4 … 16 (该过程为指数增长)
拥塞控制: 当到达ssthresh值时, 会呈线性增长, 当增长到窗口大小时, 会变为1/2

2 demo代码(只实现udp)

/* netmap */
#include <stdio.h>
#include <unistd.h>
#include <sys/poll.h>
#include <arpa/inet.h>

#define NETMAP_WITH_LIBS
#include <net/netmap_user.h>

#pragma pack(1)     //单字节对齐

#define PROTO_IP            0x0800
#define PROTO_UDP           17

#define ETH_LENGTH          6

struct ethhdr {
    unsigned char dst[ETH_LENGTH];
    unsigned char src[ETH_LENGTH];
    unsigned short proto;
};

struct iphdr {
    unsigned char version   : 4,
                  hdrlen    : 4;

    unsigned char tos;
    unsigned short tot_len;

    unsigned short id;
    unsigned short flag     : 3,
                   offset   : 13;

    unsigned char ttl;
    unsigned char proto;
    unsigned short check;

    unsigned int sip;
    unsigned int dip;
};

struct udphdr {
    unsigned short sport;
    unsigned short dport;
    unsigned short length;
    unsigned short check;
};

struct udppkt {
    struct ethhdr eth;
    struct iphdr ip;
    struct udphdr udp;

    unsigned char body[0];
};

struct tcphdr {
    unsigned short sport;
    unsigned short dport;

    unsigned int seqnum;
    unsigned int acknum;

    unsigned char hdrlen    : 4,
                  resv      : 4;

    unsigned char cwr       : 1,
                  ece       : 1,
                  urg       : 1,
                  ack       : 1,
                  psh       : 1,
                  rst       : 1,
                  syn       : 1,
                  fin       : 1;
    unsigned short wsize;
    
    unsigned short tcpcheck;
    unsigned short urg_pointer;

    unsigned char options[0];
};

int main()
{
    struct nm_desc *nmr = nm_open("netmap:eth0", NULL, 0, NULL);
    if (nmr == NULL) {
        return -1;
    }

    struct pollfd pfd = {0};
    pfd.fd = nmr->fd;
    pfd.events = POLLIN;

    while (1) {
        int ret = poll(&pfd, 1, -1);
        if (ret < 0) continue;

        if (pfd.revents & POLLIN) {
            struct nm_pkthdr h;

            unsigned char *stream = nm_nextpkt(nmr, &h);
            struct ethhdr *eth = (struct ethhdr*)stream;

            if (ntohs(eth->proto) == PROTO_IP) {
                struct udppkt *udp = (struct udppkt*)stream;

                if (udp->ip.proto == PROTO_UDP) {
                    int udp_length = ntohs(udp->udp.length);
                    udp->body[udp_length-8] = '\0';
        
                    printf("udp --> %s\n", udp->body);
                }
            }
        }
    }

    return 0; 
}

3 netmap 安装

参考:
https://blog.csdn.net/weixin_43326322/article/details/108265924

评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值