网络层协议——IP协议

1. 网络层

在学习TCP时,我们学习了TCP保证的是能将一个数据可靠的传输到另一个主机上。主要是数据的可靠传输。而网络层和数据链路层则是帮助我们如何将数据传输到另一台主机上。

我们在应用层给对方发数据时,数据并不是直接传输给对方的,而是需要自顶向下贯穿协议栈。其中经过传输层,网络层,数据链路层和物理层,每一层都需要添加对应的报头。报文通过网络的传输,到达目标主机。再从下至上贯穿协议栈,每一层会对报文进行解包分用,并向上交付。所以站在每一层的角度都是和对方的对应层直接通信。

就比如我和对方在一个小区不同的单元楼里,现在我有一个包裹要交给对方,我就必须要下楼,经过一段路程,到达对方楼下,再上楼,将包裹交给对方。网络传输也是一个道理,在这个例子中,包裹就是报文,下楼的过程就是数据的封装(添加报头),经过一段路程指的是网络传输,上楼就是解包分用。

而我们今天的主角——网络层的任务就是将数据从一台主机发送到另一台主机(从一个单元楼找到另一个单元楼)。

TCP和IP的关系?

TCP保证数据的可靠传输,而IP保证的是将数据传输到另一台主机。也就是说,IP能负责将数据传输到另一台主机上,但是具体能不能可靠的传输到对端主机,这就不能保证了(比如,你有能较大概率考100分的能力,但是能保证你每次都考100分吗)。而TCP能通过一系列机制来保证数据可靠传输。TCP保证可靠,IP付出行动

(关于TCP保证可靠性机制,感兴趣的可以看一下传输层协议——TCP协议-CSDN博客)

2. IP协议

2.1 数据路由

网络层的作用是将数据从一个主机传输到另一台主机,那么传输的过程肯定是涉及到路径的选择。

数据进行的网络传输一般都是跨网络的,而路由器就是连接多个网络的硬件设备,因此数据在进行跨网络传输时一定需要经过多个路由器。

在上图中,主机B想给主机C发送数据,就是先找到下一跳主机(路由器F),然后继续找下一跳主机(路由器H)最终找到了主机C。

所以目的地是非常重要的,他将决定了我们的路径选择。只有路径选对了,我们才能到达目标主机。

路由器扮演着什么样的角色

由于主机B将来发送的就是报文数据,他本身并不知道路该往哪走,只能通过不断问路人,路人会告诉主机B如何找到通往主机C的路。这个例子中路由器就是路人的角色,帮助报文找到目标主机。

路由器中有一个路由表,上面记录了下一跳该到哪去,如主机B发送的报文到达路由器F中,说他要到主机C,问路由器F他应该往哪走,路由器F查了查路由表,然后告诉路由器应该先到路由器G的地方,就这样报文最终到达了主机C。

IP = 目标网络 + 目标主机

例如北京故宫,目标网络就是北京,目标主机就是故宫。

IP地址是数据路由的根本,实际上,IP协议也是靠目的IP,才能够找到对方主机。

2.2 协议报头

  • 4位版本号(version): 指定IP协议的版本, 对于IPv4来说, 就是4.
  • 4位头部长度(header length): IP头部的长度是多少个32bit, 也就是 length * 4 的字节数. 4bit表示最大的数字是15, 因此IP头部最大长度是60字节.
  • 8位服务类型(Type Of Service): 3位优先权字段(已经弃用), 4位TOS字段, 和1位保留字段(必须置为0). 4位TOS分别表示: 最小延时, 最大吞吐量, 最高可靠性, 最小成本. 这四者相互冲突, 只能选择一个. 对于ssh/telnet这样的应用程序, 最小延时比较重要; 对于ftp这样的程序, 最大吞吐量比较重要. 16位总长度(total length): IP数据报整体占多少个字节.
  • 16位标识(id): 唯一的标识主机发送的报文. 如果IP报文在数据链路层被分片了, 那么每一个片里面的这个id都是相同的.
  • 3位标志字段: 第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到). 第二位置为1表示禁止分片, 这时候如果报文长度超过MTU, IP模块就会丢弃报文. 第三位表示"更多分片", 如果分片了的话,最后一个分片置为1, 其他是0. 类似于一个结束标记.
  • 13位分片偏移(framegament offset): 是分片相对于原始IP报文开始处的偏移. 其实就是在表示当前分片在原报文中处在哪个位置. 实际偏移的字节数是这个值 * 8 得到的. 因此, 除了最后一个报文之外, 其他报文的长度必须是8的整数倍(否则报文就不连续了).
  • 8位生存时间(Time To Live, TTL): 数据报到达目的地的最大报文跳数. 一般是64. 每次经过一个路由, TTL -= 1, 一直减到0还没到达, 那么就丢弃了. 这个字段主要是用来防止出现路由循环
  • 8位协议: 表示上层协议的类型
  • 16位头部校验和: 使用CRC进行校验, 来鉴别头部是否损坏. 32位源地址和32位目标地址: 表示发送端和接收端.
  • 选项字段(不定长, 最多40字节): 略

报头和有效载荷如何分离?

和TCP一样,IP报头中有一个4位首部长度(单位为4bit),表示的是整个报头的大小。还有一个字段为16位总大小,当我们提取出这两个字段后,就能计算出报头的大小还有数据的大小。然后我们就能将两者分离了。

如何进行解包分用(交给上层)

报头中还有一个字段叫做8位协议,指的是上层使用的协议,当我们提取该字段时,就知道交给上层的哪个协议了(如UDP或者TCP)。

这个字段是发送端的传输层将数据交给网络层时填写的,如果是TCP交给他的,这个字段就填写TCP协议。

如何理解协议

协议其实就是结构化的字段。在内核当中本质就是一个位段类型,给数据封装IP报头时,实际上就是用该位段类型定义一个变量,然后填充IP报头当中的各个属性字段,最后将这个IP报头拷贝到数据的首部,至此便完成了IP报头的封装。

struct iphdr {
#if defined(__LITTLE_ENDIAN_BITFIELD)
    __u8    ihl:4,    //4位版本号
        version:4;    //4位首部长度
#elif defined (__BIG_ENDIAN_BITFIELD)
    __u8    version:4,
        ihl:4;
#else
#error  "Please fix <asm/byteorder.h>"
#endif
    __u8    tos;      //8位服务类型
    __be16  tot_len;  //16位总长度
    __be16  id;       //16位标识
    __be16  frag_off; //3位标志 + 13位片偏移
    __u8    ttl;      //8生存时间
    __u8    protocol; //8位协议
    __sum16 check;    //16位校验和
    __be32  saddr;    //32位源IP
    __be32  daddr;    //32位目的IP
    /*The options start here. */
};

如何理解向上/下交付?

内核中有一个sk_buff,用于表示一个网络数据包的主要数据结构。其中有几个比较重要的字段。

struct sk_buff
{
    char* head;
    char* data;
    char* end;
    char* tail;

    sk_buff* prev;
    sk_buff* next;  
};

其中这几个指针分别指向了对应的区域。

现在,应用层要将数据"hello world"交付给下层传输层,只需要移动data指针,填充字段,然后将sk_buff向下交付即可。

传输层交给网络层也是一样的道理,只需要创建一个TCP协议对象,然后填充字段,再将data指针前移将协议对象填充进sk_buff中。

网络层向下交付也是同理。

现在我们理解了向下交付的过程,那么向上交付了,其实也很简单。例如IP报文向上交付,前面提到过IP报文的结构字段iphdr。将data指针强转成iphdr*类型,那么看到的就是一个iphdr的大小,再提取报文大小,上层协议等字段后,然后将data移动到后面的位置,就完成了报文和有效载荷分离的过程。然后向上交付即可。

2.2.1 8位生存时间

在网络传输中可能会存在各种意外,比如出现环路路由问题。

例如主机B给主机C发消息,结果找不到路径了,路径变成了B->F->G->E->A->B->C->H->G->E,这样就形成了一个环状,这种现象叫做环路路由问题。如果不解决这个问题,这个报文就会一直在这个环路中传输。如果出现环路路由的报文越来越多,那么最终就会导致网络拥塞。

所以IP报文中设置了8位生存时间,代表的是报文到达目标位置的最大跳数,报文每经过下一跳主机后,生存时间就减一,当减到零时,就丢弃该报文。

2.2.2 16位标识

16位标识号(ID):这是一个唯一的标识符,用于标识主机发送的报文。如果IP报文在数据链路层被分片了,那么每一个分片里面的这个ID都是相同的。这样,当报文在传输过程中被分片并随后重新组合时,接收端就可以根据这个ID来正确地将各个分片组合成原始的报文。

2.2.3 3位标志

  • 第一位是保留位,目前不使用,但可能在未来被赋予某种功能。
  • 第二位是DF(Don't Fragment)位。如果DF位被设置为1,那么表示这个报文不能被分片。如果报文长度超过了MTU(最大传输单元),那么IP模块就会丢弃这个报文。如果DF位被设置为0,那么报文可以被分片。
  • 第三位是MF(More Fragments)位。如果MF位被设置为1,那么表示这个分片后面还有其他的分片。如果MF位被设置为0,那么表示这是最后一个分片。

2.2.4 13位片偏移

这个字段表示当前分片在原数据报中相对于用户数据字段的偏移量,也就是在原数据报中的相对位置。当报文被分片时,这个字段用于帮助接收端在重新组合报文时确定各个分片的顺序和位置。

实际偏移的字节数是这个值 * 8 得到的. 因此, 除了最后一个报文之外, 其他报文的长度必须是8的整数倍(否则报文就不连续了)。

其他字段的意思和TCP报文类似。

2.3 分片与组装

将来网络层的报文是要向下交付给数据链路层的,但是数据链路层规定好了网络传输的最大长度。这个长度叫做最大传输单元(Maximum Transmission Unit,MTU),这个值的大小一般是1500字节。我们可以使用ifconfig命令来查看

这就代表着,网络层传给数据链路层的报文大小不能太大。

分片与组装

如果IP层报文过大,则需要分片,将一个大报文拆分成多个小报文,再向下交付。

当数据到达对端的IP层时,则需要进行组装。

  • 实际在网络通信过程中最好不要分片,因为数据分片可能会增加丢包的概率。
  • 数据的分片和组装发生在IP层,不仅源端主机可能会对数据进行分片,数据在路由过程中的路由器也可能对数据进行分片。因为不同网络的MTU是不一样的,如果传输路径上的某个网络的MTU比源端网络的MTU小,那么路由器就可能对IP数据报再次进行分片。
  • 分片数据的组装只会发生在目的端的IP层。

 

  • 16位标识:唯一的标识主机发送的报文,如果数据在IP层进行了分片,那么每一个分片对应的id都是相同的。
  • 3位标志字段:第一位保留,表示暂时没有规定该字段的意义。第二位表示禁止分片,表示如果报文长度超过MTU,IP模块就会丢弃该报文。第三位表示“更多分片”,如果报文没有进行分片,则该字段设置为0,如果报文进行了分片,则除了最后一个分片报文设置为0以外,其余分片报文均设置为1。
  • 13位片偏移:分片相对于原始数据开始处的偏移,表示当前分片在原数据中的偏移位置,实际偏移的字节数是这个值× 8得到的。因此除了最后一个报文之外,其他报文的长度必须是8的整数倍,否则报文就不连续了。

IP完成切片和组装的流程注意是靠IP协议中的这几个字段完成的。

2.3.1 分片流程

假设传输层交给了IP层4500字节的数据,IP层添加报头后就是4520字节,超过了最大传输单元1500字节,所以需要切分。

先切分前1500字节,这1500字节包括20字节IP报文,1480字节的数据。

然后再切分1480字节,因为除了第一次切分的,后面切分的数据都不携带IP报文,我们需要添加IP报文。

最终就拆分成了这样几个报文。

分片报文总大小IP报文大小字节数据大小在数据中的位置
115002014800-1479
215002014801480-2459
315002014802960-4439
48020604440-4499

那么IP报文中的字段如何填写呢,假设16位标识为1234。

分片报文16位标识更多分片13位片偏移
1123410
212341185
312341370
412340555

需要注意的是,13位片偏移当中记录的字节数是当前分片在原数据开始处的偏移字节数的值÷ 8得到的,比如分片报文2在原始数据开始处的偏移字节数是1480,其对应的13位片偏移的值就是1480 ÷ 8 = 185。

2.3.2 组装流程

1.当接收到收到报文时,根据IP报文中的16位标识,将相同16位标识号的报文聚集在一起。

2.对于不同的报文:

  • 第一个分片报文中的13位片偏移的值一定为0。
  • 最后一个分片报文中的“更多分片”标志位一定为0。
  • 对于每一个分片报文来说,当前报文的13位片偏移加上当前报文的数据字节数 ÷ \div÷ 8所得到的值,就是下一个分片报文的所对应的13位片偏移。

所以只要有16位标志位,3位标志还有13位片偏移我们就能将报文组装回去。

2.3.3 其他问题

如果出现了丢包问题怎么办

  • 1.如果第一个分片报文丢失,那么就无法找到片偏移为0的报文,接收端能判断出来,通知对方重发。
  • 2.如果是最后一个分片报文丢失,那么就无法找到更多分片为0,片偏移不为0的报文,也能判断出来。
  • 3.如果丢失的是中间的报文,接受端会通过计算(前一个报文的大小+片偏移 = 当前报文的片偏移)
  • 4.如果丢失的是没有切片过的报文(更多分片为0,片偏移为0)的报文,接收端不知道有这个报文的存在也就不会给发送端响应,一段时间后,发送端会重新给接收端发送。

为什么不建议分片?

分片会大大增加丢包概率。如果我们只发一个报文,对方收到了,就认为这个数据可靠的传输给对方了,但是如果我们将一个报文分片成多个报文,必须要让所有报文都被对方收到才算可靠传输。如果中间丢了一个报文,那么所有报文都会被重新发送(TCP不关心分片,当接收端没有收全报文时,TCP会将所有数据重新发一遍),这样就会造成资源浪费问题。

假设现在网络丢包的概率为千分之一,如果我们将一个报文拆分成十个报文,丢包概率就从千分之一变成了百分之一,所以我们不建议分片?

如何避免分片?

实际数据分片的根本原因在于传输层一次向下交付的数据太多了,导致IP无法直接将数据向下交付,如果传输层控制好一次交给IP的数据量不要太大,那么数据在IP层自然也就不需要进行分片。

因此TCP作为传输控制协议,它需要控制一次向下交付数据不能超过某一阈值,这个阈值就叫做MSS(Maximum Segment Size,最大报文段长度)。
通信双方在建立TCP连接时,除了需要协商自身窗口大小等概念之外,还会协商后续通信时每一个报文段所能承载的最大报文段长度MSS。
MAC帧的有效载荷最大为MTU,TCP的有效载荷最大为MSS,由于TCP和IP常规情况下报头的长度都是20字节,因此一般情况下 MSS = MTU - 20 - 20,而MTU的值一般是1500字节,因此MSS的值一般就是1460字节。

所以一般建议TCP将发送的数据控制在1460字节以内,此时就能够降低数据分片的可能性。之所以说是降低数据分片的可能性,是因为每个网络的链路层对应的MTU可能是不同的,如果数据在传输过程中进入到了一个MTU较小的网络,那么该数据仍然可能需要在路由器中进行分片。

3.网段划分

3.1 IP地址的划分

IP地址分为两个部分, 网络号和主机号

  • 网络号: 保证相互连接的两个网段具有不同的标识;
  • 主机号: 同一网段内, 主机之间具有相同的网络号, 但是必须有不同的主机号;

例如:上图中一共有两个网段(上下两个方框),同一网段内的主机网络号是相同的,但是主机号不同;不同网段内的主机网络号不相同,但是主机号可能相同。

  • 不同的子网其实就是把网络号相同的主机放到一起.
  • 如果在子网中新增一台主机, 则这台主机的网络号和这个子网的网络号一致, 但是主机号必须不能和子网中的其他主机重复

3.2 DHCP技术

当子网内新增一台主机要为这台主机分配一个IP地址,当他断开网络时需要对IP地址进行回收,但是如果我们手动的增加删除就非常麻烦。

有一种技术叫做DHCP, 能够自动的给子网内新增主机节点分配IP地址, 避免了手动管理IP的不便.一般的路由器都带有DHCP功能. 因此路由器也可以看做一个DHCP服务器。

实际上我们连接WiFi的动作,就是让路由器为你分配一个IP地址,有了这个IP地址,你就可以上网了。

3.3 网段划分

分类划分法

过去曾经提出一种划分网络号和主机号的方案, 把所有IP 地址分为五类

网段划分类似于分蛋糕的做法,将一个蛋糕切分成若干大小的A类,B类,C类,我们直接申请你想要A类还是B类。

随着Internet的飞速发展,这种划分方案的局限性很快显现出来,大多数组织都申请B类网络地址, 导致B类地址很快就分配完了, 而A类却浪费了大量地址;

例如, 申请了一个B类地址, 理论上一个子网内能允许6万5千多个主机. A类地址的子网内的主机数更多.然而实际网络架设中, 不会存在一个子网内有这么多的情况. 因此大量的IP地址都被浪费掉了.

子网掩码

  • 针对这种情况提出了新的划分方案, 称为CIDR(Classless Interdomain Routing):
  • 引入一个额外的子网掩码(subnet mask)来区分网络号和主机号;
  • 子网掩码也是一个32位的正整数. 通常用一串 "0" 来结尾;
  • 将IP地址和子网掩码进行 "按位与" 操作, 得到的结果就是网络号;
  • 网络号和主机号的划分与这个IP地址是A类、B类还是C类无关;

此时一个网络就被更细粒度的划分成了一个个更小的子网,通过不断的子网划分,子网中IP地址对应的主机号就越来越短,因此子网当中可用IP地址的个数也就越来越少,这也就避免了IP地址被大量浪费的情况。

举个例子:

  • 比如在某一子网中将IP地址的前24位作为网络号,那么该网络对应的子网掩码的32个比特位中的前24位就为1,剩下的8个比特位为0,将其用点分十机制表示就是255.255.255.0。
  • 假设该子网当中有一台主机对应的IP地址是140.252.20.68,那么将这个IP地址与该网络对应的子网掩码进行“按位与”操作后得到的就是140.252.20.0,这就是这个子网对应的网络号。
  • 实际在用子网掩码与子网当中主机的IP地址进行“按位与”操作时,本质就是保留了主机IP地址中前24个比特位的原貌,将剩下的8个比特位的值清0了而已,也就是将主机号清0了,那么子网地址范围就为:【140.252.20.0 ~ 140.252.20.255】

3.4 特殊的IP地址

  • 将IP地址中的主机地址全部设为0, 就成为了网络号, 代表这个局域网;
  • 将IP地址中的主机地址全部设为1, 就成为了广播地址, 用于给同一个链路中相互连接的所有主机发送数据包;
  • 127.*的IP地址用于本机环回(loop back)测试,通常是127.0.0.1

我们常常使用127.0.0.1来测试我们的网络服务,那么他的原理是什么?

使用本地环回时,数据会自顶向下贯穿协议栈,封装报头;然后报文不会发送到网络当中,而是直接自底向上贯穿协议栈,拆分报头。

3.5 IP地址的数量限制

我们知道, IP地址(IPv4)是一个4字节32位的正整数. 那么一共只有 2的32次方 个IP地址, 大概是43亿左右. 而TCP/IP协议规定, 每个主机都需要有一个IP地址,这意味着,,一共只有43亿台主机能接入网络么?

实际上, 由于一些特殊的IP地址的存在, 数量远不足43亿; 另外IP地址并非是按照主机台数来配置的, 而是每一个网卡都需要配置一个或多个IP地址.

CIDR在一定程度上缓解了IP地址不够用的问题(提高了利用率, 减少了浪费, 但是IP地址的绝对上限并没有增加), 仍然不是很够用. 这时候有三种方式来解决:

  • 动态分配IP地址: 只给接入网络的设备分配IP地址. 因此同一个MAC地址的设备, 每次接入互联网中, 得到的IP地址不一定是相同的;
  • NAT技术:能够让不同局域网当中同时存在两个相同的IP地址(私有IP和公网IP做转换),NAT技术不仅能解决IP地址不足的问题,而且还能够有效地避免来自网络外部的攻击,隐藏并保护网络内部的计算机。
  • IPv6: IPv6并不是IPv4的简单升级版. 这是互不相干的两个协议, 彼此并不兼容; IPv6用16字节128位来表示一个IP地址; 但是目前IPv6还没有普及;

3.6 私有IP地址和公网IP地址

如果一个组织内部组建局域网,IP地址只用于局域网内的通信,而不直接连到Internet上,理论上 使用任意的IP地址都可以,但是RFC 1918规定了用于组建局域网的私有IP地址

  • 10.*,前8位是网络号,共16,777,216个地址
  • 172.16.到172.31.,前12位是网络号,共1,048,576个地址
  • 192.168.*,前16位是网络号,共65,536个地址
  • 包含在这个范围中的, 都成为私有IP, 其余的则称为全局IP(或公网IP);

3.7 LAN口IP和WAN口IP

路由器是可以构建局域网的,而且一定是横跨两个子网,所以路由器至少会配置两个IP,分别是LAN口和WAN口:

  • LAN口:表示连接本地网络的端口,主要与家庭网络中的交换机、集线器或PC相连。
  • WAN口:表示连接广域网的端口,一般指互联网。

以上图为例:运营商分配到了一个公网IP122.77.241.4/24,当我们通知运营商要网络服务时,运营商会给我们一个家用路由器,然后将我们的家用路由器和运营商的路由器连接起来,此时家用路由器就有一个LAN口和一个WAN口。

LAN对应的就是我们自家的局域网,而WAN口对应的就是运营商的路由器所在的局域网了。

当我们使用电脑上网时,我们的局域网IP是192.168.1.201/24,在通过家用路由器时,就会转换成10.1.1.2/24,通过运营商的路由器会转化成122.77.241.4/24,也就是一个公网IP,这样就能成功与对方通信了。

也就是说,公网IP被通过LAN口和WAN口转化成了内网IP,也就是通过这种方式划分成了无数个局域网,有效的解决了IP不够的问题。

这种在转发报文的时候不断的把源IP和WAN口IP进行替换的技术就叫做NAT(网络地址转换)。

私有IP不能出现在公网中

因为IP地址用于标识主机的唯一性,而私有IP在不同子网中是会出现相同的。一个公网IP在全世界只能有一个,但是一个私有IP可以是任意一台主机。

通信过程

例如我们现在要访问QQ的服务端,我们主机会判断目标主机并不在当前局域网内,于是将数据交给家用路由器,家用路由器判断也不在他的局域网中,于是继续向上交付,直到报文到达公网后,由于知道对方的IP地址,所以直接去找对应的路由器,然后再向一层层向下路由,最终到达了QQ的服务端。

4.路由

路由的过程, 就是这样一跳一跳(Hop by Hop) "问路" 的过程.

所谓 "一跳" 就是数据链路层中的一个区间. 具体在以太网中指从源MAC地址到目的MAC地址之间的帧传输区间.

IP数据包的传输过程也和问路一样.

1.当IP数据包, 到达路由器时, 路由器会先查看目的IP;

2.查看目的IP有三种结果:

  • 路由器经过路由表查询后,得知该数据下一跳应该跳到哪一个子网。
  • 路由器经过路由表查询后,没有发现匹配的子网,此时路由器会将该数据转发给默认路由。
  • 路由器经过路由表查询后,得知该数据的目标网络就是当前所在的网络,此时路由器就会将该数据转给当前网络中对应的主机。

3.依次反复, 一直到达目标IP地址;

我们可以使用route命令查看路由表

  • Destination:代表的是目的网络地址。

  • Gateway:代表的是下一跳地址。

  • Genmask:代表的是子网掩码。

  • Flags:U表示正在使用,G就表示默认网关(路由器)。

  • Iface:代表的是发送接口。

当报文到达路由器时,该报文会通过物理层,数据链路层最终到达网络层。路由器就会提取出该报文中的目的IP地址,依次与路由表中的子网掩码 Genmask进行“按位与”操作,将结果与对应的目的网络地址Destination进行比对,如果相同则说明该数据包下一跳应该跳去这个网络地址,此时就会将该数据包通过对应的发送接口Iface发出。

如果将该数据包的目的IP地址与子网掩码进行“按位与”后,没有找到匹配的目的网络地址,此时路由器就会将这个数据包发送到默认路由,也就是路由表中目标网络地址中的default。可以看到默认路由对应的Flags是UG,实际就是将该数据转给了另一台路由器,让该数据在另一台路由器继续进行路由

 总结:
1.目的IP & 子网掩码遍历路由表,找到要去的目标网络,没找到就走默认网关。
2.通过Iface发送。

  • 24
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值