Andy Niu �����ĵ�

Andy Niu

Andy Niu Help  1.0.0.0
Effective__TCP__IP

变量

 【T01】理解面向连接和无连接协议之间的区别
 
 【T02】理解子网和CIDR的概念
 
 【T03】理解私有地址和NAT
 
 【T04】开发并使用应用程序框架
 
 【T05】套接字接口比XTI_TLI更好用
 
 【T06】记住TCP是一种流协议
 
 【T07】不要低估tcp的性能
 
 【T08】避免重新编写TCP
 
 【T09】要认识到TCP是一个可靠的,但不是绝对可靠的协议
 
 【T10】记住,TCP__IP不是轮询的
 
 【T11】提防对等实体的不友好动作
 

详细描述

变量说明

【T01】理解面向连接和无连接协议之间的区别
1、面向连接和无连接指的是协议,本质区别在于:对于无连接协议来说,每个分组的处理都独立于其他的分组。
    而对于面向连接的协议,协议实现维护了当前分组与后继分组有关的状态信息。
2、无连接就是指udp,分组称为数据报(datagram),每个分组都独立寻址,udp会尽最大努力发送数据报,
    但是不保证数据报不丢失,不延迟,不错序。
    注意:对于udp,可以在应用层维护数据报的状态。
3、面向连接就是tcp,tcp的分组称为段(segment),协议维护了分组之间的状态,记住这些状态,协议可以提供可靠的传输。
4、一个标准的类比就是:udp就像寄明信片,tcp就像打电话。
5、udp有这么多缺点,为什么还要使用这种协议呢?
    a、面向连接只支持一对一通信,无连接很方便地支持一对多和多对一的通信,面向连接需要多个独立的连接才能实现。
    b、更重要的是,无连接是面向连接的基础,udp和tcp在ip协议之上,ip协议只转发分组,不对底层做任何假设,
        ip就是不可靠的无连接服务。
6、tcp是怎么提供可靠的传输?
    tcp在ip协议上增加了3项功能:
    a、为tcp段中的数据提供校验和,在接收端检查数据是否损坏
    b、tcp为每个字节提供序列号,即使在接收端错序了,可以根据恰当的顺序重装起来
        注意:tcp并没有为每一个字节附加一个序列号,而是在tcp发送的段中为第一个字节标识了序号,
        这样就隐含了段中其它字节的序列号
    c、tcp提供了确认重传机制
7、确认重传机制是如何工作的?
    tcp是全双工的,每一端既是接收端又是发送端。
8、站在接收端的角度,接收端维护了接收窗口,左边界是期望接收的下一个字节的序号,右边界表示接收缓冲区的能容纳的最大字节序号。
    接收端通过告诉发送端自己的接收窗口,可以用于流量控制,防止缓冲区溢出。
    对于接收的数据(一组字节,会覆盖接收窗口的一个区间),如果在左边界之前,说明是已收到的数据,丢弃。
    如果在右边界之后,说明是没有对应的缓冲区存储,丢弃。
    如果不是所期望的下一个字节,说明是错序的,需要等待前面缺少的数据到达。
    如果是所期望的下一个字节,通知应用程序有数据可读。这时候所期望接收的字节序号加上段中的字节个数,进行更新,
    窗口向右滑动。同时向发送端发送ack,告知所期望接收的下一个字节序号。
9、站在发送端的角度,发送端维护发送窗口,窗口划分成两部分,已发送但还未被确认的字节,可以发送但还未发送的字节。
    对于已发送但还未被确认的字节,会启动RTO(Retransmission TimeOut 超时重传)定时器,超时认为丢失了,重新发送。
    注意:RTO超时并不意味着原来的数据没有到达目的地,有可能是ack丢失了,也有可能是网络延迟,导致收到ack的时候已经超时。
    重传不会导致什么问题,因为接收端会丢弃重新收到的数据。
10、udp在ip协议上增加了2项功能,分别如下:
    a、提供了可选的校验和用来检测数据是否损坏。ip也有校验和,但是ip的校验和只是对ip分组的首部进行计算,
        而udp和tcp提供的校验和用于保护他们自己的首部和数据。
    b、udp增加了端口的概念(当然tcp也是),ip地址对应主机,端口对应主机上的进程。
【T02】理解子网和CIDR的概念
1、IP地址分为5类,A、B、C、D、E,它们的前缀分别是:
    A:0     网络个数2^7,主机个数2^24,大概1千6百万
    B:10        网络个数2^14,大概1万6千,主机个数2^16,大概6万5千
    C:110       网络个数2^21,大概2百万,主机个数2^8,254个
    D:1110      用于多播编址
    E:1111      留作未来使用
    注意:对于主机号,全0和全1特殊用处,因此可用的地址要减去2
2、子网划分,IP地址分为网络ID和主机ID,考虑B类的主机ID,可以容纳6万5千个主机,现实中根本用不了这么多,
    因此在内部(比如局域网)可以从主机ID中,划出一部分作为子网。如果子网掩码相同,可以认为主机在同一个网络中。
3、广播地址分为以下情况:
    有限广播为255.255.255.255,路由器从不转发这种数据报,局域网内的所有主机可以收到
    网络直接广播为指定网络ID,主机ID全部设为1,传送到目标网络的所有主机
    子网直接广播为指定网络ID和子网ID,子网内的所有主机收到数据报
4、对于C类地址,能够容纳主机个数太少,需要网络ID减少,这可以认为CIDR(Classless Inter Domain Routing 无类别域间路由)
    子网掩码是加长网络ID,而CIDR使其变短,同时指定网络掩码。
    虽然B类地址,加上子网掩码,也类似C类地址缩短网络号,但是B类地址从外部用来看,认为网络号还是不带子网的前缀。
    CIDR不按照传统的IP地址分类,而是指定网络掩码,使得IP地址的分配更加有效。
5、为什么CIDR使得IP地址的分配更加有效?
    举例来说,现在有3个1000G的硬盘,使用传统的方式,只能进行分区如下:
    10个100G的分区,100个10G的分区,1000个1G的分区,分别对应于A类,B类,C类地址。
    但是在实际使用中,可能根本不需要10个100G的分区,但是没有办法,只能进行这样进行分区来使用。
    而CIDR彻底废弃了这种方法,无分类就是不考虑IP地址所属的类别。
    根据实际的需求进行分区,比如第一个1000G的硬盘,可以分成25个40G的分区,或者40个25G的分区,或者200个5G的分区。
6、更重要的一点是,CIDR这种组网技术可以将一组较小的无类别网络汇聚为一个较大的单一路由表项,
    减少Internet路由域中路由表条目的数量。
【T03】理解私有地址和NAT
1、私有地址包括三块:
    10.0.0.0   到 10.255.255.255
    172.16.0.0   到 172.31.0.0
    192.168.0.0  到 192.168.255.255
2、私有地址接入公网,需要进行NAT(Network Address Translation)
    出去进行SNAT,在NAT服务器修改Source
    进来进行DNAT,在NAT服务器根据端口,修改Destination,也就是端口映射
【T04】开发并使用应用程序框架
1、TCP/IP应用程序分为
    TCP服务器
    TCP客户端
    UDP服务器
    UDP客户端
2、构建框架库是比较简单的一件事,主要就是对socket编程。
【T05】套接字接口比XTI_TLI更好用
1、用于网络编程的API接口有两种:
    Berkeley套接字
    XTL
2、套接字是加州大学伯克利分校为其Unix操作系统版本开发的,TLI是AT&T(贝尔实验室)为Unix系统V3.0开发的
3、二者通信能力没有差别,但是语义差别很大,套接字是更好的选择。首先套接字具有更好的移植性,其次,套接字比XTI更好使用。
【T06】记住TCP是一种流协议
1、TCP是一种流协议(stream protocol),这意味着数据是以字节流的形式发给接收者的,没有固定的报文和报文边界的概念。
    接收端读取tcp数据,无法预知在这一次读操作中会返回多少个字节。
2、假设主机A向主机B发送两条报文M1和M2,调用两次send发送两条独立的报文,但是数据在传输过程中并不会遵循这个方式。
    在发送端,send操作只是将数据复制到主机A的TCP/IP协议栈,由TCP决定怎么发送和每次发送多少。
    决定过程很复杂,取决于很多因素。如:发送窗口,拥塞窗口,路径上的最大传输单元等。
    也就是说,主机A真正发送数据的情况有很多种:
    M1和M2分开发送,M1和M2合并起来发送,M1先发送一部分,剩下的和M2一起发送,发送M1和M2的一部分,再发送M2剩下的一部分。
    注意:发送窗口由对端控制,防止发送过多数据,导致接收端的缓冲区溢出。
    拥塞窗口自己控制,tcp要提供可靠的传输服务,因此具备超时重传功能。
    考虑,现在网络比较拥堵,超时没有收到回复,认为丢失,重传。导致越重传,越拥堵,越拥堵,越重传的的恶性循环。
    tcp发送策略的一个主要目标是:尽可能高效地利用网络带宽。因此,对于上述的恶性循环,
    更好的策略是:不要再发送数据了,等到网络缓解的时候在发送。
    拥塞窗口就是,记录当前网络能够容纳的流量。当前tcp能够发送的数据量是发送窗口和拥塞窗口的最小值。
    也就是说,发送窗口允许发送很多数据,但是网络环境比较差,你也必须少发一些。
3、因此,接收端主机B不能对读取数据做任何假设,需要读几次,每次读多少,都是不可预测的。
4、对于定长报文来说,接收端读取到一定长度就认为是一个完整报文,那么问题来了,对于不定长的报文,每个报文的长度是不确定的,主机B怎么才能知道读取了一个完整的报文?
5、解决办法有两个:
    办法一:报文的前面加一个首部,在固定位置标识这个报文的长度,接收端就能知道读取多少,就是一个完整的报文了。
    办法二:在报文中增加结束标记来分割报文,这个结束标记是一个特殊字段。
    那么问题来,如何表示结束标记本身呢? 
    需要使用转义字符。
【T07】不要低估tcp的性能
1、tcp在ip的基础上增加了校验和、可靠性和流量控制的功能,而udp只增加了校验和的功能,看起来udp应该会比tcp快很多,
    但事实不是这样,有时候tcp比udp的性能还要好。
2、思考,在什么情况下udp的性能要好于tcp,以及为什么?
3、这两种协议的cpu开销主要是进行数据拷贝和校验,二者区别不大。
4、tcp为了提供可靠的传输,会发送ack包进行确认,但是这个工作量不大。首先,ack往往会作为捎带信息附属在包含数据的报文段中,
    其次,tcp没有必要对每个段都进行ack,可以间隔一段时间对多个段一起进行ack。
5、相对于ucp,tcp在建立连接三次握手,拆除连接四次握手,如果连接维持的时间比较长,进行大量的交互,
    连接的建立和拆除被分摊下来,对整个的性能影响很小。
    如果应用程序只是进行简短的请求应答回话,udp的性能好。
    如果应用程序持续时间比较长,并且进行大量的数据传输,tcp的性能会好于udp
6、客户端和服务端都是本地,tcp的性能要远远好于udp,这个奇怪的现象是因为BSD的tcp协议库实现,
    无论是不是环路udp的MTU是1500,对于环路的tcp的MTU可以达到1万5千多。注意:对于环路,数据报并不经过网络,
    而是直接放入接收队列。
【T08】避免重新编写TCP
1、有时候为了所谓的性能,我们倾向于使用udp,但是我们又期望数据的传输是可靠的,因此需要在应用层提供可靠性。
2、可靠、健壮的udp必须提供:
    a、在合理的时间内没有收到回复,进行重传
    b、保证应答和请求的匹配
3、基于udp协议,在应用层提供可靠性,需要做很多事情,比如:滑动窗口,拥塞控制,对这些事情都进行研究,就相当于重写tcp
4、因此,如果要可靠的网络传输,就直接使用tcp,而不是使用udp,在应用程序提供可靠性。原因有:
    a、应用程序实现的tcp功能肯定比不上真正的tcp协议,tcp协议实现是大量实现和研究的结果,也在不断改进。
    b、tcp协议的实现在内核中执行,性能更高。
    c、有时候在应用层超时重传并不是用户期望的,比如客户端发送一个转账请求,超时没有收到回复,然后重传,
        导致一次转账请求,在服务端处理了两次,这不是我们所期望的。而tcp的超时重传,在传输层会丢弃收到的重复请求,
        在应用层只收到一次请求。
    因此,使用udp,在应用层提供可靠性并没有什么意义。
5、因此,udp的使用场景是:对可靠性的要求不是很高,允许丢包,丢包不影响应用程序。
    tcp的使用场景是:对可靠性的要求很高。
6、如果既想拥有tcp的可靠性,同时减少建立连接和拆除连接的开销,可以使用T/TCP,T/TCP是对tcp的扩展,
    避免了建立连接的三次握手,同时缩短拆除连接时的TIME_WAIT阶段。
【T09】要认识到TCP是一个可靠的,但不是绝对可靠的协议
1、稍微想一下就知道,TCP不是绝对可靠的协议,比如:网络断开,主机崩溃,无论TCP如何努力,都无法将数据传给对方。
2、考虑应用程序A向应用程序B发送数据的TCP流程,数据流从应用程序A通过他所在主机的TCP/IP栈向下传输,经过几台中间路由器,
    通过应用程序B所在主机的TCP/IP栈向上传输,最后到达应用程序B。
    特别注意:中间的路由器(即使拥有TCP/IP协议栈)并不涉及TCP层和应用层,只是转发IP数据报,
    也就是说,不会在路由器的TCP/IP协议栈向上传输,然后在向下传输,只有在发送端和接收端才有这部分流程。
3、考虑一种极端的情况,TCP确认了收到的数据,但是应用程序还没有来得及把这部分数据取走,就崩溃了。
    这种情况下,发送端认为接收端已经准确无误地收到了数据,但是接收端的应用程序并没有取到数据。
    除此之外,还有其他一些的情况,
4、网络中断,如果没有备用的路径,数据传输一直失败,知道传输路径修复好,或者存在了备用的路径。
5、对等实体崩溃,也就是对端的应用程序崩溃,
    注意:对等实体崩溃与对等实体调用close(windows下调用closesocket)以及exit是无法区分的。
    这种情况下,对等实体会发送fin包,表明没有办法再发送数据了。
6、对等实体的主机崩溃,这和对等实体崩溃不同,主机崩溃了,没法发送fin给我们。
【T10】记住,TCP__IP不是轮询的
1、TCP/IP协议栈无法将连接的丢失立即通知应用程序.
    TCP为什么不提供这种通知机制,不这么做的优点和缺点,应用程序怎么检测链接的丢失。
2、TCP为什么不提供连接丢失即时通知的功能?
    a、其他通信协议,比如SNA和X.25,在连接丢失的时候会通知应用程序。他们是如何做到的?
        他们的策略是轮询发送显示报文"有东西要发给我吗?" 或者采用后台静态帧的形式,不断地监视虚电路的状况,
        这意味着要消耗一定的网络带宽。这是原因之一。
    b、还有哲学方面的考虑,上层协议不应该对下层协议做任何假设,TCP只是负责发送数据报.
        应用程序根据需求,来决定是否检测连接的丢失。
    c、还有一个重要的原因,和TCP/IP的主要设计目标有关:出现网络故障时维护通信的能力。
        TCP/IP的起源是美国国防部要求,出现战争或者自然灾害等严重网络故障,也能维护可靠通信的网络协议。
        也就是说,网络故障往往是暂时的,路由器会重新找到一条路径,可以认为具备自动修复的功能.
        这种暂时的连接丢失,再应用程序还没有意识到的时候就已经恢复好了。如果连接丢失立即通知应用程序,反而不是所期望的。
3、如何检测连接的丢失呢?
4、TCP的保活机制,是为了检测长时间没有交互的死连接,并且丢弃这些连接。
    TCP/IP协议栈运行在系统内核,独立于应用程序。如果对等应用程序终止或者崩溃,内核中的TCP/IP协议栈会发送fin包,
    表明我不再向对端发送数据了。
    如果对等应用程序所在的主机崩溃,运行在内核中的TCP/IP协议栈也立即退出了,来不及发送fin包。
    如果到达对等主机,但是应用程序没有运行,内核中的TCP/IP协议栈发送rst包。
5、TCP的保活机制涉及到时间间隔,要求是至少2个小时的默认空闲时间,然后发送9次探测信号,每次间隔75秒。
    这就意味着TCP的保活机制要2个多小时以后才能检测到连接丢失。
    这两个时间间隔可以修改,但是这种修改是全局的,会影响到所有的TCP连接。
    如何时间间隔设置太短,就违背了它清除长时间死连接的最初目标。
    另外,TCP保活机制不仅检测死连接,还丢弃这些连接,这往往不是应用程序所期望的。
6、那么问题来了,应用程序如何检测连接的丢失呢?
    在应用程序添加心跳,设置心跳的频率,已经多久收不到心跳信号,认为连接丢失了。
7、还有另外一种办法,思路是使用一条新的连接来发送心跳信号,也就是一条连接来检测另一条连接,看起来很奇怪,但是非常合理。        因为,对于网络故障或者系统崩溃,这两条连接要么都受到影响,要么都不会。具体实现往往是:启动一个新的线程用于心跳控制。
8、TCP没有提供连接丢失即时通知应用程序的功能,但是在应用程序可以很方便地构建这种机制。
【T11】提防对等实体的不友好动作
1、好的软件应该能够处理各种可能的错误,也就是防御式编程。
2、作为服务端,应该检测客户端的终止,客户端的无效输入。
Copyright (c) 2015~2016, Andy Niu @All rights reserved. By Andy Niu Edit.