IP报文头解析问题 | 豆包MarsCode AI刷题

347 阅读5分钟

IP报文头解析问题,这道题让我想起计算机网络的某些知识......与其说是想起,不如说是被迫回忆!因为我当初也是看不懂这个东西而在实验上卡壳!!!为了解决此类问题,我们还是先从IP协议/IP报头开始了解。

IP报头

IP头部(报头)固定长度为20字节,每行的宽度为32位(4字节),总共有5行。

image.png

可以看下这张图,出自博主

以下面这个报头为例子:

45 00 10 3c 7c 48 20 03 80 06 00 00 c0 a8 01 02 c0 a8 14 b8

20个字节的详细字段说明:

  • 版本和首部长度(Version and Header Length)45

    • 4 表示IP协议的版本为IPv4。
    • 5 表示首部长度为20字节(单位为4字节,因此5 * 4 = 20字节)。
  • 区分服务字段(Differentiated Services Field,原TOS)00

    • 表示服务类型或优先级。
  • 总长度(Total Length)10 3c

    • 表示整个IP数据报的长度为16进制0x103C
  • 标识(Identification)7c 48

    • 用于唯一标识IP数据报片段。
  • 标志和片偏移(Flags and Fragment Offset)20 03

    • 标志为20,片偏移量为03
  • 生存时间(Time To Live, TTL)80

    • 表示数据包可以经过的最大路由数量。
  • 协议(Protocol)06

    • 对应的是TCP协议
  • 首部校验和(Header Checksum)00 00

    • 用于检测IP头部在传输过程中是否出错,这里为00 00表示暂时未计算或忽略。
  • 源IP地址(Source IP Address)c0 a8 01 02

    • 表示源IP地址。
  • 目标IP地址(Destination IP Address)c0 a8 14 b8

    • 表示目标IP地址。

这边需要强调一下,标志位位于标志和片偏移字段中的第6和第7字节,在例子中对应的是20中的二进制表达0010 0000的前三位,也就是001

OK,IP报头结构搞清楚后,现在来看一下题目。

IP报文头解析问题

问题

问题描述:

小R 负责解析IP报文头信息,现有一个十六进制格式的IP报文头数据 header,他需要从中解析并输出其中的总长度、标志位以及目的IP地址,用逗号分隔。
IP报文头信息依次包含多个字段,其中标识(16位)和目的IP地址(32位)是重点。输入数据为合法的十六进制IP报文头,固定长度为59个字符,每两个十六进制数字表示一个字节,字节之间以单空格分隔。

注:报文数据为大端序(即高位字节在低地址),小R需要将这些数据进行解析,输出的总长度和标志为十进制整数,目的IP地址为点分十进制格式(如192.168.20.184)。

返回规则:解析其中的总长度标志位以及目的IP地址,用逗号分隔。

测试样例:

输入:header = "45 00 10 3c 7c 48 20 03 80 06 00 00 c0 a8 01 02 c0 a8 14 b8"
输出:"4156,1,192.168.20.184"

解析

我们注意到,header是用空格隔开的,所以首先可以选择split方法将header转化为数组形式。

题目要求返回总长度、标志位、目的IP地址,也就是分别对应header的第3和第4字节、第7个字节的前3位、最后4个字节。

好了,现在来看一下代码。

实现

public class Main {
    public static String solution(String header) {
        String[] bytes = header.split(" ");
        
        // 总长度
        int totalLength = (Integer.parseInt(bytes[2], 16) << 8) + Integer.parseInt(bytes[3], 16);
        
        // 标志位
        int flags = (Integer.parseInt(bytes[6], 16) >> 5) & 0x07;
        
        // 目的IP地址
        String ipAddress = String.format("%d.%d.%d.%d",
            Integer.parseInt(bytes[16], 16),
            Integer.parseInt(bytes[17], 16),
            Integer.parseInt(bytes[18], 16),
            Integer.parseInt(bytes[19], 16));
        
        return String.format("%d,%d,%s", totalLength, flags, ipAddress);
    }

    public static void main(String[] args) {
        System.out.println(solution("45 00 10 3c 7c 48 20 03 80 06 00 00 c0 a8 01 02 c0 a8 14 b8").equals("4156,1,192.168.20.184"));
        System.out.println(solution("4b ba 0d 15 d0 42 16 bc 50 25 38 33 cb e0 77 ed 56 a4 30 46").equals("3349,0,86.164.48.70"));
        System.out.println(solution("dd fb 25 3b 41 92 12 33 cb cd a1 c8 41 3e 75 29 c4 7f 98 65").equals("9531,0,196.127.152.101"));
    }
}
  • 第6行:Integer.parseInt(string, radio)意思为将这个string以radio进制转换为整数(十进制)。

    • (Integer.parseInt(bytes[2], 16) << 8) + Integer.parseInt(bytes[3], 16)将header的第3字节转换为十进制整数向左移动8位(也就是乘以282^8),再加上第4字节转换为十进制整数的值。
  • 第9行:Integer.parseInt(bytes[6], 16) >> 5将header的第7个字节转换为十进制整数向右移动5位(也就是除以252^5),然后与上0x07

    • 为什么要与上0x07?为了好理解,我们姑且用二进制进行举例。假设原本二进制为:0010 0000,向右移动5位后为0000 0001,此时的低三位正好是原本的标志位的大小(也就是移动前二进制的高三位),所以与上0x07也就是与上0000 0111,除了低三位,其它位上的数都变成0。这样我们就得到想要的标志位的值。
    • 为什么不直接与上0xE0,也就是1110 0000,反而要先右移然后与上0x07?因为如果直接与上0xE0,那么得到的答案就不是标志位了,还得在做操作处理数据。就像这样:int flags = (Integer.parseInt(bytes[6], 16) & 0xE0) >> 5;这样处理也一样,反正横竖都得移位,只是早晚的问题hhh...
  • 第12行到16行:通过String.format构造目的IP地址。

OVER!!!!!!


这里吹爆String.format,神来之手,真的好好用!!!

小结

这道题没涉及循环什么的,就不给大家做样例模拟了。

题目不难,主要是要知道IP报头的结构,不然真的束手无策。

下期见,byebye~~~