IP 是TCP/IP协议栈中重要的层次, TCP UDP ICMP IGMP都是依赖IP层进行传输的。首先它是一种不可靠,无连接的协议。不可靠:它不保证IP包能正确到达目的地,无连接:表示IP并不会维护后续数据包的信息,每个数据包的传输都是独立的。数据包的可靠性需要依赖传输层协议来保证如TCP协议,也就是说当一个比特流从网络接口发送向网络之后,所经过的每个路由器会解析数据包的网络层的信息,再通过路由算法选一条路发送出去,所有包不一定会选择同一条道路。网络层负责点到点的通信,尽量将数据包发送到目的主机,若不能到达目的主机IP有一个简单的错误处理算法:丢弃该数据报,然后发送 ICMP消息报给信源端。
IP首部信息:
如果没有IP选项的话IP首部应该是20字节。用wireshark抓包分析一下
4位版本号: 0100 表示IPv4 若是IPv6的话应该是0110
4位首部长度:0101 表示20个字节,这是因为首部长度指的是首部占 32 bit字的数目,包括任何选项。由于它是一个 4比特字段,因此首部最长为6 0个字节。也就是说这四个比特一个比特表示4个字节。 8位服务类型:服务类型(TO S)字段包括一个3 bit的优先权子字段(现在已被忽略) ,4 bit的TOS子字段和1 bit未用位但必须置0。4 bit的TOS分别代表:最小时延、最大吞吐量、最高可靠性和最小费用。普通数据包里一般都为0x00. 对于不同应用可能会选择不同的TOS。如TFTP telnet等。 16位总长度:表明IP数据包的总长度 最大65535个字节, 但考虑到链路层的MTU 一般这个值要小于1500。拿这个总长度减去IP首部长度就是TCP UDP 等数据包长度。 接下来的4个字节就是表示IP分片的记录信息: 前两个字节表示:那个数据包的分片,标识同一个数据报分片 3位标志表示:是否有更多分片,最后一个IP分片里都为0表示没有分片了 13位片偏移:分片在原始数据的偏移量。 另外在分片过程中传输层信息只会出现在第一个分片中。IP层不知道也不需要保证在每个分片中都有传输层首部。到达目的主机时IP层会把这些IP片组合起来。若有片丢失的话,就会重传整个数据报。组合起来的数据报再交给传输层解析。最后的几个字节包含目的IP源IP和IP选项(若有)。
下面做一个UDP传输实例看一下IP层分片。#include#include #include #include #include #include #define PORT 4444int main(int argc, char *argv[]){ if(argc != 2) { printf("./udpclient IP\n"); return 1; } int fd = socket(AF_INET, SOCK_DGRAM, 0); if (fd < 0) return 1; struct sockaddr_in server; server.sin_family = AF_INET; server.sin_port = htons(PORT); if (!inet_pton(AF_INET, argv[1], (void *)&server.sin_addr)) { printf("IP error\n"); return -1; } char buff1[65535] = "sdfsdfsdf"; char buff2[2048] = { 0}; socklen_t size = sizeof(struct sockaddr); while(1) { sendto(fd, buff1, 65535, 0, (const struct sockaddr *)&server, size); sleep(5); recvfrom(fd, buff2, 2048, 0, NULL, NULL); printf("recv : %s\n", buff2); }}
服务器端
#include#include #include #include #include #include #define PORT 4444int main(){ int fd = socket(AF_INET, SOCK_DGRAM, 0); if(fd < 0) return -1; struct sockaddr_in server, client; server.sin_family = AF_INET; server.sin_port = htons(PORT); server.sin_addr.s_addr = htonl(INADDR_ANY); bind(fd, (const struct sockaddr *)&server, sizeof(struct sockaddr)); socklen_t len = sizeof(struct sockaddr); char buff[2048] = { 0}; while(1) { recvfrom(fd, buff, 2048, 0,(struct sockaddr *)&client, &len); printf("%s send msg: %s \n", inet_ntoa(client.sin_addr), buff); sendto(fd, "OK", 2, 0, (const struct sockaddr *)&client, len); }}
抓包结果可以看到:
~# tcpdump -i eth0 -v udp and host 192.168.1.3tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes18:21:40.171370 IP (tos 0x0, ttl 64, id 39920, offset 0, flags [+], proto UDP (17), length 1500) 192.168.1.3.39170 > 192.168.1.4.4444: UDP, length 204818:21:40.171385 IP (tos 0x0, ttl 64, id 39920, offset 1480, flags [none], proto UDP (17), length 596)
这里可以看到被分成的两个组,2096 - 2048 = 48字节: 多出的48个字节包含俩个IP头40 + 一个UDP头8 字节??