- TCP如何有序性的
- ARP
- TCP的拥塞控制
- TCP的半连接队列与全连接队列
- TCP粘包与分包
- 网线断了, TCP怎么处理
- TCP的长连接与短连接
- TCP的11个状态迁移
- send返回正数, 是不是成功?
- 阻塞与非阻塞, send/recv, -1
如何取到一帧完整的数据包
- raw socket
- netmap
- dpdk
1 网络协议栈
1.1 OSI模型
物理层: 传输的光电信号
数据链路层: 数字信号
网卡: 将数字信号转换为光电信号(AD/DA), 因此不属于协议栈的那一层
1.2 协议栈是什么 ?
根据分层的思想, 每一层都有对应得协议, 从而形成一个协议家族 --> 协议栈
1.3 协议栈的实现
-
linux中, 网卡利用sk_buff实现将数据传进内核协议栈中, 接着协议栈进行解析
-
因此如果要自己实现协议栈解析, 就需要获取网卡内存中的数据, 这里利用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;
};
* 协议栈实现时会遇到的问题及其原理
- ping不通 --> 没有实现ICMP
- 过一段时间后, send数据不成功 --> 没有实现ARP, 源地址没得到回应, 会以为目标地址已经连接不上
- 因此我们自己实现一个协议栈, 每一个协议都要实现
1.3.2 TCP协议实现
有了前面 arp/icmp/udp 我们就可以实现 tcp
0) 如何实现tcp
- 协议头
- 三次握手/四次挥手, 11个状态迁移
- tcp的顺序, 如何保证有序, 延迟ack/滑动窗口
- 定时器, rtt, 慢启动与拥塞控制
- 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四个重要的定时器
- 超时重传
- 坚持定时器(探测)
- keep alive
- 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