美文网首页程序员之家计算机网络深度好文
《TCP/IP协议 详解》思考总结 · 三

《TCP/IP协议 详解》思考总结 · 三

作者: Noskthing | 来源:发表于2017-12-04 14:09 被阅读814次

    前言

    这一篇文章主要围绕了IP协议,ICMP协议和UDP协议展开,希望可以在这里大概做一个总结,将《TCP/IP协议详解 卷一》书中TCP相关章节前面的内容做一个结束,在下一篇文章专心的去写TCP相关的内容。

    如果不是专业相关链路层包括物理相关的内容可以忽略不用关心,唯一需要注意的就是MTU这个概念。所以包括本文在内的三篇文章并没有就链路层详细展开。

    这篇文章开始介绍的是IP协议。它是一个逐跳(Hop-by-hop)协议,提供的服务是在端系统和中间系统之间跨越的,这意味着IP协议所要处理的情况会非常的复杂,作者只是尽力的整理相关的内容,虽然已经非常努力的精炼,但文中所说仍然只占实际内容很少的一部分。

    其次介绍的是ICMP协议,作为IP协议的维护管理协议它在非常多的地方为我们提供了服务。实际的内容并不复杂,本文会从两个经典的程序pingtraceroute为大家做一个介绍。

    最后我们简单谈论了UDP协议,并借此讨论了广播和多播。实际UDP本身非常简单,但是却是我们要学习TCP一个非常重要的基础。因为UDP本身非常的简陋,但是提供可靠性传输却是一件非常复杂的事情。我们必须要了解UDP的不足,然后去思考解决方案,才可以真正理解和记住TCP中多而繁杂的设定。

    当今网络发展的非常迅速,高速而廉价的流量给了我们随时随地使用网络生活娱乐的可能,这常常会让我们面对很多问题的时候会有理所应当的想法。要正确看待各类协议的设计,代入当时的历史背景和限制也是非常必要的一件事情。


    谈一谈IP协议

    IP协议是TCP/IP协议簇中最为核心的协议,它几乎垄断了网络层。我们熟知的TCP UDP ICMP以及各类上层协议都依赖于IP协议提供的是不可靠且无连接的服务

    IP协议初识

    不可靠指的是IP协议只是尽力去传输,但不能保证数据报一定能够到达目的端。这一部分在之前的文章讨论过,IP数据报在遇到错误的情况下会丢弃该数据报,并以ICMP的方式通知源端。如果你的应用需要可靠性的支持,那么必须在上层协议中实现。(比如TCP)。

    IP协议首部

    而无连接则是指IP协议并不维护任何关于后续数据报的状态信息。每一个数据报的处理是相互独立的。

    首先,在这里连接的本质并不是说通信的双方预留了物理信道。物理层是共享的,我们提到过在有连接的TCP通信里,所谓的连接也只是一个虚拟的假设而非我们日常生活里所见的那种实际存在的连接。其次,无连接因为数据报独立,所以每份数据报都是有边界的,传递的过程我们可以假想成远远隔着的两个人通过空抛传递物品。而有连接则维护了整个通讯的状态,这个通讯是源端和目的端专有的。传输的过程我们可以抽象理解成流。什么时候传输,传输给谁,是否送达以及什么时候结束这些都是明确的。

    针对无连接的概念可以举一个简单的例子:在socket编程里,如果你选择了TCP通信,在调用write之后数据会从应用进程的缓冲区写入输出队列的缓冲区,发送一份数据报之后会留有一个副本,直到接收到对端的确认才会清除该数据报的缓存。而UDP虽然也有缓冲区的概念,但只是简单的限制发送数据报的大小,数据报会被直接输出不做任何操作。

    聊聊IP地址

    IP地址按照结构可以分为五类

    IP地址分类

    32位的地址通常写成四个十进制的整数。而区分各类地址最简单的方式就是看IP地址的第一个十进制整数的大小。

    如果熟悉socket编程的朋友应该记得struct in_addr是一个表示地址的结构体,其中in_addr_t是一个无符号长整数。

    struct in_addr{
         in_addr_t s_addr; 
    }
    

    实际在早期的版本中struct in_addr是一个包含多个structunion,这样的定义方式允许访问32位IPv4地址中的任意一个或两个字节(8位或者说其中一个十进制的整数)。在IP地址是A B和C三类地址的时候,非常便于使获取IP地址的适当字节。然而随着子网划分技术的来临和无类地址编排的出现,各种地址类正在消失,union也就不再需要。

    这里提到的子网划分是为了解决A类地址和B类地址位主机号分配太多空间的问题,两类地址可容纳的主机数分别是2^24 - 22^16 - 2个。现实的情况是在一个网络中人们不需要这么多的空间,因此我们通过划分子网来进一步分割。

    全为0或1的地址是无效的,因此我们需要去除两个地址。

    为了描述IP地址中子网号和主机号是如何划分,引入了子网掩码的概念。子网掩码的英文单词是subnet mask,其中mask就是掩码的意思。这个概念被应用的非常广泛,比如在iOS开发里,当你设置UIButton的state时,你会发现UIButton.h的头文件里是这样定义的

    typedef NS_OPTIONS(NSUInteger, UIControlState) {
        UIControlStateNormal       = 0,
        UIControlStateHighlighted  = 1 << 0,                  // used when UIControl isHighlighted is set
        UIControlStateDisabled     = 1 << 1,
        UIControlStateSelected     = 1 << 2,                  // flag usable by app (see below)
        UIControlStateFocused NS_ENUM_AVAILABLE_IOS(9_0) = 1 << 3, // Applicable only when the screen supports focus
        UIControlStateApplication  = 0x00FF0000,              // additional flags available for application use
        UIControlStateReserved     = 0xFF000000               // flags reserved for internal framework use
    };
    

    二进制的0和1可以很简洁的描述对立的两种情况。如果需要描述多个结果那么只需要包含相应位数的一串比特流即可,将这串比特流按照某种数据类型翻译出来,所得即是一个掩码。

    很基础的一个小知识,但第一次了解时我觉得很惊艳 :)

    IP选路

    选路是IP最重要的功能之一,基于此数据报能够被正确的转发投递。从概念上来看,IP的选路方式是非常简单的,特别是对于主机而言。

    主机和路由器的区别在于:主机从不把数据报从一个接口转发到另一个接口,如果一份数据报目的地址不是自己那么就会丢弃它;而路由器则会转发数据报。对于大部分的主机,我们也可以进行配置让它变成路由器。

    当IP层接收到一个数据报需要发送时,我们用伪码来说明一下处理的流程。

    search 路由表:/* */
        if 数据包来自网络接口:
            if 目的IP地址为本机的IP地址之一或IP广播地址:
                送入IP首部指定的协议模块进行处理
            else:
                if IP层被设置了路由功能:/* 路由器 */
                    路由选择
                else:
                    丢弃数据包 /* 主机 */
        else:
            这是环回接口
    

    以上可以看出,IP的路由选择是逐跳进行的。需要注意的是对于中间节点而言,IP层是不知道到达目的端的完整路径的,它只是提供下一站路由器的IP地址,假定下一站路由器更加接近目的端。

    伪码中出现的路由表是由内核进行维护的,其中提供的信息决定了IP层的决策。输入netstat -rn来查看本机的路由表。

    netstat -rn

    表中的每一项都包含了以下信息

    • 目的IP地址。它既可以是一个完整的主机地址,也可以是一个网络地址。具体的类型在标志字段进行了说明:主机地址的主机号非0指示一个特定的主机,网络地址中主机号为0指定网络中全部的主机。

    用一个网络地址指定一个路由器而避免为每个主机都指定一个路由器,这是IP路由选择机制的基本特性。可以极大地缩小路由表的规模

    • 下一跳路由器的IP地址,或是直连的网络的IP地址。

    • 标志。用以表明目的IP地址的某些特性。

    这里简单介绍几个常见的标志
    U 该路由可以使用。
    G 该路由是一个网关(Gateway)。如果没有这个标志则意味着目的端是直连的。
    H 该路由是一个主机。如果缺失则代表这个路由是一个网络。
    D 该路由是由重定向报文创建的
    M 该路由已被重定向报文修改

    • 为数据报的传输指定一个网络接口。

    配合路由表中的信息,IP层能够有序的进行路由选择:

    1. 搜索路由表,寻找能与目的IP地址完全匹配的表目,即网络号和主机号同时匹配。如果找到,报文会被发往该表目指示的下一跳路由器或直接相连的网络接口;如果没有进行下一步。

    2. 搜索路由表,寻找能与目的网络号相匹配的表目。如果找到,报文会被发往该表目指示的下一跳路由器或直接相连的网络接口;如果没有进行下一步。这意味着目的网络上的所有主机都可以通过这个表目来设置。

    3. 搜索路由表,寻找标位 default 的表目。如果找到,报文会被发往该表目指示的下一跳路由器;如果没有找到,那么该数据报就无法被传递,会通过ICMP向源端发送一个 主机不可达网络不可达的错误。

    这里有两点需要注意。第一,完整主机地址匹配一定在网络号匹配之前执行,default 路由只有在前两步都失败的情况下才会去执行。第二,我们需要关心default选项是如何被加入路由表中。

    默认路由的设置一般是通过配置文件来设置,在每次重新启动系统时会主动向路由表中加入该默认选项。除此之外,还可以利用ICMP路由器通告和发现报文来更新自身的路由表。

    路由请求报文格式 路由通告报文格式

    在考虑这两种报文的时候,我们必须要以路由器和主机两个不同的角度去看待。

    对于路由器而言,它会定期的广播(或者多播)通告报文,并且响应其他路由器或主机的路由请求报文。注意查看上图中通告报文的格式,其中的生存时间默认是30分钟。但是有一个非常特殊的情况就是,在路由器即将关闭的时候,它会发送一份生存时间为0的通告报文,用以删除自身负责的路由表目。

    对于同时存在多台路由器的网络,系统管理员必须为每台路由器设置优先级,用以给主机指明选择哪一个IP地址作为默认路由。

    对于主机而言,会在系统引导期间每隔 3s 发送一次路由请求报文,在收到一次有效的通告报文以后就会停止继续发送请求。之后主机会一直监听其他路由的通告报文,用以更新自己的路由表默认表项。如果在生命周期内没有接收到默认路由器的通告报文,那么默认路由器就会超时。

    对于默认路由器而言,一般会每隔10分钟发送一份通告报文,而报文的生命周期通常是30分钟。这也就是说即使错过1 - 2份通告报文主机路由表中的默认表项也不会超时。

    IP的未来

    本篇以及之前两篇在内的文章,谈论的大部分有关IP协议的内容,都是基于IPv4之上讨论的,在这之间我们也会穿插介绍一些IPv6与之不同的实现方法,用以比较IPv4的不足。确实,IPv6代表着IP协议的未来。

    在之前文章的讨论中我们也曾经说过,在网络发展的初期各类协议百花齐放,谁也不会料到IP协议最后会发展起来垄断了网络层,谁也没有想到几十年后的今天网络会发展的这么迅速。32位的IP地址几尽枯竭,虽然NAT技术为IPv4续了一波但仍然无法满足日益增长的需求

    • 实际IPv4被创造出来的时候谁也不会想到地址会枯竭的情况,所以我们看早期的地址分配实际是非常随意甚至浪费的。比如环回地址并非只有127.0.0.1,整个A类网络号127都是为环回接口所预留的,但是真正被广泛使用的只有一个地址。
    • 我们介绍了IPv4和IPv6,细心的朋友应该注意到了中间跨越了IPv5。作为因特网流协议,IPv5一直停留在实验室阶段,并未被大范围商用所以我们并不用去关心

    有关IPv6和IPv4的区别,展开来说是一个非常大的话题,本书不再赘述。好消息是近期看到国内有关IPv6将要大范围部署的消息,有兴趣的朋友可以自行查阅一下。:)


    初探ICMP

    ICMP(Internet Control Message Protocol)Internet控制报文协议,是IP层一个非常重要的组成部分,用以传递差错报文以及其它需要注意的信息。报文的格式非常简单。

    ICMP报文格式

    需要注意的是,在这里提到ICMP协议是作为IP层的重要组成部分,而没有称作网络层。虽然IP协议几乎垄断了网络层,两者之间几乎可以画上等号,但这样描述是为了两个方面的原因。

    • ICMP报文是由IP协议承载的

    • ICMP的定位就是IP协议的维护管理协议

    需要解释的是第二点。首先我们明确ICMP的定位:在之前的讨论有提到过IP协议是一个不可靠,无连接的协议。从开始设计的时候IP协议就没有考虑太多可靠性需求,而是将重心放在了构建一个易于使用而且健壮的无中心网络这一块。ICMP在此基础上为IP协议提供了网络控制诊断的服务,包括但不限于检测网络是否通畅、链路有没有死循环、时间戳、开机广播等等。其次我们解释一下ICMP的分层:ICMP和IP协议同属网络层确实非常的尴尬,这让网络分层的逻辑看起来有一些混乱,似乎ICMP更应该被划分在传输层。实则不然,ICMP服务于IP协议,协议栈的两端就是IP模块,报文的终点已经确定,所有的处理都在网络层完成;而上层协议比如TCP/UDP则是网络层处理完成之后再进行操作。

    对比各类ICMP报文,思考每一类报文提供的是什么服务?

    ICMP协议大致可分为两类:差错报文查询报文。其中8位的类型字段用以描述具体类型的ICMP报文,某些ICMP报文还可以使用8位的代码字段来进一步细分描述不同的条件。

    ICMP报文类型

    按照类型字段和代码字段来区分理由非常的容易理解:因为ICMP需要负责多种的服务,提供的字段可以让我们明确当前报文服务的内容。而划分差错报文和查询报文原因在于,差错报文有的时候需要一些特殊的处理,在某些情况下不会产生差错报文

    • 响应ICMP差错报文时永远不会生成另一份ICMP差错报文
      常见的差错报文有目的不可达,端口不可达,超时等等。我们需要明确在收到ICMP差错报文的时候,源端和目的端之间的通讯是存在问题的。如果移除了这一条限制,很可能会因为响应差错报文而生成另一个差错,差错再产生差错,无休止的循环下去。这不是我们所期待的结果。

    • 目的地址是广播地址或多播地址
      在《TCP/IP协议详解 卷一》的第十二章为我们介绍了广播和多播,在引言的部分提到了这么一段

    通常每个以太网帧仅发往单个目的主机,目的地址指明单个接收接口,因此称为单播 (unicast)。在这种方式下,任意两个主机的通信不会干扰网内其他主机(可能引起争夺共享信道的情况除外)。
    然而,有时一个主机要向网上的所有主机发送帧,这就是广播。

    换一句话来解释就是如果你的广播不是LAN内所有主机都需要的,那么势必会给不需要的接收广播的主机造成干扰。假设允许主机为广播地址的报文响应ICMP差错报文,那么即使你期待的主机已经成功接收到你的广播,其他不相关的主机也会生成大量的ICMP差错报文。这不仅仅是麻烦,更可能会造成LAN的拥堵。

    更何况广播和多播都是基于UDP,本身就是一个无连接的状态,只管尽力将数据抛出去。这样的前提下差错报文也就没有多大的意义了。

    • 作为链路层广播的数据报不产生差错报文

    • 如果数据报被分片,那么只响应分片的第一片

    要理解这个限制条件,首先要声明一点:当发送一份ICMP差错报文时,报文始终包含产生ICMP差错的IP报文的 首部 和 数据报的前8个字节

    IP首部包含了源IP地址和目的IP地址等等非常重要的信息,这对于接收主机是非常有用的。数据报的前8个字节听起来显得很多余,但如果你熟悉数据报的封装流程应该可以很快反应过来,IP数据报的前8个字节是传输层协议首部的前8个字节!(一般来说传输层协议首部的长度都是长于8个字节的)

    让我们回忆一下传输层的两个经典协议TCP/UDP的首部的前8个字节。

    TCP/UDP 首部

    ICMP差错报文提供的这两项内容,让接收端可以轻松将差错报文和某个特定的协议(根据IP首部中协议类型的字段)以及用户进程(传输层协议首部的前8个字节包含用户进程的端口号)联系起来。

    上面提到的是没有分片的情况。如果一个IP数据报被分片进行传输,那么目的端接收的IP报文数据报的前8个字节就不一定是传输层协议首部的前8个字节了。

    IP分片

    数据报分片之后,除了第一片的数据报都无法提供完整的信息给接收端。因此ICMP差错报文只在第一片数据报这里产生

    • 源地址不是单个主机的数据报。(零地址,环回地址,广播或多播地址)
      这一条也非常好理解。如果允许响应,源地址就变成了返回的差错报文的目的地址,是一个反向的广播了。
    Ping程序介绍

    ping的名字源于 声呐,是一个使用非常高频的指令,用于测试目的主机是否可达。实现的原理非常简单:源端发送一份ICMP回显请求给目的主机,等待目的端的回答。关于ping,需要注意以下几点。

    • 大部分情况下通过ping来判断目的主机是否可达是没有问题的。但是,如果无法ping通则目的主机不可达,这一句话放在当前来说是错误的!因为部分运营商会拦截过滤ICMP报文,ICMP的优先级比较低,经过路由器的时候可能会被丢弃。等等各类原因可能会造成我们ping的失败,但实际网络是畅通没有问题的。

    之所以不提高ICMP报文的优先级,有部分的考虑是因为构造它非常的简单,没有任何的防护措施。另外,短时间内大量的ICMP报文涌入,对于低端网络设备而言,CPU的压力非常的大。

    • ping本身是基于ICMP协议实现,也就是说协议栈的两端只是在网络层。虽然我们仍然将通信的双方认为是客户端-服务器模型,但这个和其他的模型是有很大区别的。因为ping的模型不涉及用户进程,所有的操作都在底层的协议栈上,所以一般是由内核提供服务。对于用户而言就无需额外的配置可以直接使用。

    要操作IP层,需要使用raw socket。但是一般情况下除了root用户,OS基本只提供传输层也就是TCP/UDP及其上层的服务给用户调用。

    虽然大部分情况下使用ping是为了测试网络通畅,但ping还为我们提供了记录路由选项和时间戳选项。受限于IP数据报选项部分的长度限制,这两部分的功能局限性非常的大。也许在发明ping的时候是够用的,但现在已经无法满足我们的需求了。

    Traceroute程序介绍

    traceroute程序提供了查看IP数据报从一台主机到另一台主机所经过的路由的服务。这个和ping程序的记录路由选项提供的服务非常相似,但本质还是有一些不同。要理解traceroute存在的意义,我们必须明确ping程序记录路由选项有哪些不足。

    • ping程序的记录路由选项需要路由器的支持。这需要额外的配置,并且我们无法保证途径的每个路由器都可以提供支持。
    • 虽然IP数据报往返的路径可能会发生变化,但大部分的情况是相同的。这意味着往返的路由记录有一半是重复的。
    • 受限于IP首部选项部分的长度限制,最多只能够存放9个IP地址。这是最为致命的地方,当前的环境下这是绝不够用的。

    正是因为这些条件的限制,traceroute应运而生。它的机制非常的简单,通过设置TTL来探测路径。发送一个UDP数据报,将目的端口的数值设置为它的进程ID和32768的逻辑或,依次设置TTL从1到255。如果数据报没有到达目的端,那么返回的差错报文是目的不可达;如果数据报到达目的端,那么返回的差错报文则是端口不可达

    尽管我们无法保证每次发出的TTL不同的探测帧走的是相同路径,也无法保证数据报的返回路径和我们探测的发送路径相同,但大部分情况下我们认为探测帧选择的路径是相同的。traceroute提供的是一个有一定误差但足够便捷的探测服务,这是可以接受的

    ping程序和traceroute程序都会计算RTT,但两者的方法不同。ping程序每隔1s发送一次,发送和响应的ICMP报文选项中都可以设置时间戳,ping程序可以依此计算出精确的RTT;traceroute每发送一次,会等待响应或超时,因为不提供时间戳的服务,只能够依据接收或超时的时间进行计算。


    UDP的简单思考

    UDP是一个非常简单的面向数据报的运输层协议:运行UDP的进程的每个输出操作都正好产生一个UDP数据报,并组装成一份待发送的IP数据报。

    UDP首部

    首部的选项参数相对同在运输层的TCP而言非常的少。源端口和目的端口用以指明两端主机上操作的进程,校验和选项甚至都可以选择关闭。我们可以简单的认为UDP只是将收到的IP数据报做了一个简单的校验,然后分发递送给对应的进程。所以UDP提供的服务和IP协议非常类似,是不可靠并且无连接的。

    在和TCP的比较当中,经常会强调的是UDP的传输比TCP高效。因为UDP只需要简单的收发不提供其他额外的服务以供选择。我觉得令人忽略的一点在于除去高效,UDP的存在还包括给予了应用层自定义传输逻辑的能力。

    我们在谈到TCP的时候,应该知道它的目的是尽可能快的将数据传递到彼端。除却常常被提到的可靠性以外,被遗忘的很重要的一点是尽可能快也就是尽可能充分的利用当前可利用的网络资源。因此TCP的实现中包含复杂的逻辑,用以应对各类未知的网络状况。

    但现实情况是部分时候我们不需要那么复杂的逻辑,或者说在网络环境恶劣的情况下TCP的一些逻辑甚至成为负担,UDP为此提供了解决的可能。传输层只简单分发接收的数据报,应用需要的服务由自己实现。

    比如TFTP协议就是依赖于UDP实现的一种停止等待的文件传输协议,TFTP在应用层实现了类似TCP中Negal算法的服务。虽然相对于FTP而言TFTP局限很大,系统的吞吐量也比较低,但在部分情况下TFTP仍然以自身简单短小更显优势。

    我们日常强调TCP可靠但消耗更多资源,UDP是不可靠。但得益于现今网络的快速发展,很多时候TCP额外的开销并不如我们想的那么不可承受,UDP在某些特定环境下比如LAN内传输也是非常可靠的。
    我喜欢将UDP类比成一把精雕的小刀,使用它可以允许你在逻辑里仔细雕琢每一个部分,但是面对大的工程往往显的力不从心。为了解决各种问题而不断新增代码,使用UDP很可能最后只是在模拟一个蹩脚的TCP协议。所以在选择使用TCP还是UDP的时候,遵循一句话:
    When in doubt, use TCP

    广播和多播

    广播和多播都是基于UDP实现的,不同于TCP端对端的通讯,广播和多播提供了一对多的数据报的服务。在讨论这两者之前,我们还是要把书中的一句话拿出来强调一遍。

    使用广播的问题在于它增加了对广播数据不感兴趣主机的处理负荷。

    参考协议栈各层对收到的帧过滤过程

    帧过滤过程

    假设主机对广播数据不感兴趣,一份广播的数据报也会一直上传到传输层,在找不到合适的端口分发之后数据报才会被丢弃,这需要主机承担额外的处理负荷。还有很重要的一点在于,我们一直强调的TCP是一个有连接的通讯,它维护了的会话的过程,两端之间唯一的连接靠四元组(源端IP地址,源端端口,目的端IP地址,目的端端口)来确定;但UDP是无连接的,它只是一个传输的过程,假设广播的目的端口在这台主机上恰好有进程在运行,那么这份数据报也会被分发给这个进程,尽管这并不是期望的目的进程!

    某些情况下链路层也是可以接收到其他主机的数据报,只不过在以太网内依赖于Mac地址可以早早的过滤掉这些数据报。而广播的问题在于必须到传输层甚至更上层才能发现滤除这些不关心的数据!

    广播总共有四类的IP广播地址

    类型 地址
    受限的广播地址 255.255.255.255
    指向网络的广播 主机号全部为1
    指向子网的广播 主机号全部为1且有指定的子网号
    指向所有子网的广播 子网号和主机号全部为1

    需要注意的是,同一个IP地址在不同的情况下(比如子网号不同)代表的可能是不同的广播类型。

    日常使用最多的应该是指向子网的广播,但实际应用的过程中也需要考虑广播给网络带来的压力。与之类似的还有255.255.255.255这样一个受限的广播地址,它更多是应用在系统引导期间,主机尚未知道自己的IP地址或掩码。

    一个简单的小例子

    之前写过一篇文章CocoaAsyncSocket UDP收发数据包大小限制里面讨论了因为iPhone对于默认缓冲区的限制为9216,UDP数据报的收发因此受到影响。前段时间有一个朋友在私信问我,他同样使用了CocoaAsyncSocket,但是发出的UDP数据报最大长度只能是1472。

    要解决这个问题之前我们必须要明白,限制UDP数据报长度的主要因素有哪些:

    1. UDP首部的标示长度的字段。考虑到它能表达的最大数值,UDP包最长允许65535。
    2. 缓冲区的大小。和TCP不同的在于,UDP的缓冲区实际是一个虚构的存在,这里只是一个指代允许发送的数据报的大小上限。如果超过这个范围那么系统会返回一个EMSGSIZE的错误。

    遗憾的是1472并不符合上面两条,但这个数字却非常特殊!因为1472 + 8 + 20 = 1500是MTU的大小。我开始的猜想是DF置1拒绝分片,但实际拿到的Demo里并没有对socket进行设置,CocoaAsyncSocket的源码也是默认关闭DF的。当我再次查看源码的时候发现目的端地址写的是255.255.255.255

    这就是问题所在了:iPhone拒绝为一个目的端为广播地址的一个数据报分片。我们必须要考虑到分片带来的是广播包数量的翻倍,短时间内一并发出很容易造成拥塞。其次,即使分片允许操作,广播出去以后对于目的端主机而言,很可能出现各类不可预知的情况(比如重复收到同一个数据报),如何处理重组分片的逻辑?

    问题的根源在于,广播并不是通讯的一种合理方式,它设计的初衷应该是为了简单消息的通报和发现通信对象。在网络中避免广播的使用是一种共识,实际在IPv6中也确实取消了广播这一特性。

    小结多播

    多播是介于广播和单播之间的一种服务。主机可以自由选择加入一个或多个多播组,网卡将据此决定是否接受多播帧。如我们之前所介绍,D类IP地址被用于多播。

    D类IP地址

    多播的优点在于可以自主选择,并且不相关的报文可以在底层就被过滤不会增加主机过多的负荷。但是这里我们必须提一个问题。

    多播的源端是无法确定接收端的数量,可以没有,也可以是一个或者多个。这种情况下我们如何设置以太网帧首部的MAC地址?

    首先可以确定使用ff:ff:ff:ff:ff:ff这个广播地址是不可行的,这样多播和广播就毫无区别了。多播给出的解决方案是提供一种简单的映射规则将IP地址和某一段MAC地址一一对应,网卡和IP层依次过滤数据报。

    多播IP地址和MAC地址的映射

    上图为我们解释了映射的规则。在解释这个规则之前先为大家讲一段为何选择23位的历史成因。IP地址一共32位,多播使用的是D类广播地址,也就是说开始的4位是固定的,我们如果希望MAC地址可以和D类IP地址一一对应,那么MAC地址中最少应当提供28位以供选择。那么MAC地址又是如何分配呢?在这里必须要简单介绍一下OUI(Organizationally unique identifier)组织唯一标识符,48位的MAC地址被分为两部分:24位组织唯一标志符(OUI),剩下24位供使用者自行决定。这也就是说28位必须要16个OUI才能够满足。但是当时做多播的人,ta的老板觉得这样成本太高,毕竟每个OUI都是要真金实银去购买的!老板只批准了1个OUI,并且只把其中的一半给了多播使用,这也就是为什么多播只能映射23位到MAC地址的原因。

    需要16个OUI的原因在于16 = 2 ^ 4也就是4个位的长度。
    24位长度也就是2 ^ 14个组合,一半也就是2 ^ 13 = 23位。
    我比较笨第一次看还理解了半天,哈哈 :)

    现在我们再来看看规则: 24位的OUI是00:00:5e,剩下的24位的所有组合只有一半供多播使用,24位的最高位置0的MAC地址是属于多播的。

    这样留下了一个隐患那就是多个多播地址可能会共享一个MAC地址。网卡无法准确的过滤掉不关心的数据帧。这就是为什么前文我们说 网卡和IP层依次过滤数据报 的原因,上层必须提供逻辑再次检查数据报是否是我们所期待的。

    多播数据报的转发

    单个物理网络的多播是简单的。如果多播扩展到多个网络需要路由转发,复杂性会增加很多。难点就在于路由如何确定网络中哪些主机是属于多播组内的。IGMP(Internet Group Management Protocol)组管理协议正是为解决这个问题应运而生的。但本文不再赘述,有兴趣的朋友可以参照《TCP/IP协议 卷一》的第13章。

    相关文章

      网友评论

      • changbenhe:大神请问一下,给定源ip port和目的ip port,以及要发送的数据,可不可以自己组包发送,还是说还需要其他信息才能组成完整的ip包,我因特殊需求,拦截了ip报文,现在需要自己构造响应报文返回给发包方,需要自己组装完整ip报文(发握手报文和data报文),即ip头部,tcp头部以及数据部分,请问自己构造ip报文的方法,能够提供c语言的源码更好,如可以提供技术支持必有重谢!
        changbenhe:@Noskthing 好的,谢谢!
        Noskthing:@changbenhe c语言的我不太清楚,python的Scapy应该可以满足你的要求。 报文只是一堆按照特定规则组成的数据,当然可以自己构造,技术不难,就是个脏活比较麻烦。
      • jddxs:我向百度发请求,数据进入路由器后怎么进入我的电脑的呢?作者可否讲解一下。关注中
        Noskthing:@jddxs 路由和主机的区别在于它们是否转发数据报,所以路由器投递数据报给路由还是主机中间没有什么区别的,都是以ip地址进行标识。至于路由转发的逻辑,参考文中IP章节。

      本文标题:《TCP/IP协议 详解》思考总结 · 三

      本文链接:https://www.haomeiwen.com/subject/opofvxtx.html