TCP/IP 网络协议栈基础 —— 网络分层与套接字
1、TCP/IP 5层模型
2、封包
① 网络层:建立主机到主机之间的通信。传输层:建立端口到端口的通信。套接字 socket = 主机 + 端口。
② 通常要将数据包发给目标机器,需要知道目标机器的 mac 地址。
通常目标的 ip 地址是已知的,如果目标机器在同一个子网络, 则 mac 地址可以通过 ARP 协议获取。
不在同一个子网络,则发送给网关处理。
当一台机器给另一个机器发送数据时:
场景 | 数据包地址 |
---|---|
同一个子网络 | 对方的mac地址,对方的IP地址 |
非同一个子网络 | 网关的mac地址,对方的IP地址 |
③ TCP 与 UDP 比较
TCP | UDP |
---|---|
面向连接 | 无连接(不需要预先建立连接 |
提供可靠服务(不丢失、不重复、按序到达) | 不保证可靠 |
无边界 | 有边界 |
传输层分段(IP层数据包小于MTU) | IP层分片 |
- | 实时性高 |
点对点 | 一对一、多对一、多对多 |
资源消耗较高 | 资源消耗较少 |
其中 TCP 可靠性由下列机制保证:
校验和,重传机制,序号标识,滑动窗口,确认应答
④ DHCP 协议
DHCP 协议是应用层协议,建立在 UDP 协议之上。
一开始请求方机器会发送一个广播,在局域网内的服务器在收到广播的数据包时。
对于接收方,如果是 DHCP服务器,会分析其中的IP地址,发现发送方IP为 0.0.0.0,接收方IP为 255.255.255.255,即知道该数据包是发给自己的。DHCP服务器返回一个响应,将分配给请求方的IP地址与网关等网络参数包含在DHCP数据包的data部分响应给请求方。非DHCP服务器则通常会丢弃该数据包。
3、套接字
网络层与传输层都工作在内核态,只不过应用层可以通过普通套接字与工作在内核态的传输层进行通信。此外,网络层可以通过原始套接字(需要root权限)进行通信。
- RAW 套接字 socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); # ICMP 协议即是通过原始套接字实现
- TCP 套接字 socket(AF_INET, SOCK_STREAM)
- UDP 套接字 socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
注:
1.套接字是操作系统提供的与工作在内核态的网络协议栈进行通信的API。
2.除了使用普通的 TCP/UDP 套接字 也可以直接通过原始套接字自定义TCP/UDP 报文,其中包括设置源/目标端口、序列号、校验和等字段。此外,还需要处理序列号递增、握手与挥手逻辑等。
举例(使用原始套接字模拟普通UDP套接字):
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one));
char packet[1500];
struct iphdr *ip = (struct iphdr *)packet;
struct udphdr *udp = (struct udphdr *)(packet + sizeof(struct iphdr));
// 填写 ip, udp 头和数据
// 计算校验和
struct sockaddr_in dest;
dest.sin_family = AF_INET;
dest.sin_addr.s_addr = inet_addr("目标 IP");
sendto(sock, packet, 长度, 0, (struct sockaddr *)&dest, sizeof(dest));
疑问:为什么 ICMP工作在网络层而不是传输层?并且可以跨过传输层,直接与应用层通信。
比如 TCP(传输层的主要职责是提供端到端的通信服务,需要通过端口号标识应用进程,确保数据能够正确地从源主机传输到目标主机)内核解析IP头,确认协议为TCP后,提取目标端口号。在内核维护的 套接字哈希表 中,根据目的端口号找到对应的监听套接字。将数据放入对应套接字的接收缓冲区,唤醒用户态进程读取。
而 ICMP 在目标主机内核收到请求后,直接生成Echo Reply,无需查找端口。应用程序比如 ping程序通常通过原始套接字读取IP层数据报。
参考文章
https://microchipdeveloper.com/tcpip:tcp-ip-five-layer-model
https://users.exa.unicen.edu.ar/catedras/comdat1/material/TP1-Ejercicio5-ingles.pdf