个人是比较喜欢研究网络问题的, 最初可能是为了玩更多高大上配置的服务器, 之后就是为了弄清楚编程中遇到的问题以及其原因.
本文主要记载一些网络基础知识, 但这些知识不是纯碎的理论研究, 而是为了后面
传输层
/应用层
网络编程服务的.2017年10月更新了本文:
nagle算法
+delayed ack算法
+重新整理排版
该文是我写的网络编程的第一篇文章, 更新成为网络最后一篇
, 权当总结.
大致上已经整理了以下内容:(链接都是我站内的链接)
- 网络理论基础部分 (本文)
- 基础套接字编程, 链接
- 本地套接字, 链接
- TCP 专题, 链接, 以及 流解析(粘包问题)
- UDP 专题, 链接
- IO 模型, 链接
- Epoll专题, 链接
- 服务端模型演变, 链接
- proto buffer 库, 链接
- thrift 库, 链接
- libevent 库, 链接
- asio, 链接
- muduo, 陈硕大神的库, 值得研究学习, 链接
- 网络常用工具: nc, dd, tcpdump, ping, traceroute等, 链接
大部分知识来自于书籍(unp 1, unix高级编程, TCP/IP详解 以及 linux系统编程上下册), 腾讯后端的同学的交流, 我的一些个人思考, 最后靠谱网友的博客.
这些整理记录了我的成长(完善知识体系), 也希望为看到的人带来帮助, 至少节约一些时间(
于我个人而言, 写作是一种乐趣
)
补充上层协议: (2017/9月整理)
分层模型
分层: 只暴露应该关注的细节, 方便重用和解耦, 降低软件的复杂度. 例如一个应用层开发人员不需要关心底层也可以做好开发.
这里有两种模型:
- 学术界的OSI模型
- 工业&商业界的TCP/IP四层模型
大致对比如下:
协议本身并没有太多分别, 只是大家的认可度不一样罢了,
试想你自己发明一套, 比如IP_MY协议, 大家都不遵守, 这种约定有何意义?
所以即便是都用于互联网, TCP/IP四层模型协议更被开发者所接受, OSI 7层模型只是带过.
OSI模型
OSI 模型中,网络被分为七层,由底层向高层依次是:物理层,数据链路层,网络层,传输层,会话层,表示层和应用层。
物理层 处于 OSI 七层模型的最底端,它的主要任务是将比特流与电子信号进行转换。
数据链路层 处于 OSI 七层模型的第二层,它定义了通过通信介质相互连接的设备之间,数据传输的规范。
网络层 处于OSI参考模型的第三层网络层的主要作用是实现终端节点间的通信。IP协议是网络层的一个重要协议,网络层中还有ARP(获取MAC地址)和ICMP协议(数据发送异常通知)
传输层 处于 OSI 七层模型的第四层, 主要作用是实现应用程序之间的通信。
会话层 处于 OSI 七层模型的第五层, 利用传输层提供的服务,使应用建立和维持会话,并能使会话获得同步。
表示层 处于 OSI 七层模型的第六层, 表示层是处理所有与数据表示及运输有关的问题,包括转换、加密和压缩。(字符集数据格式等处理)
应用层 处于 OSI 七层模型的第七层, 这是最高层, 也是网络应用程序直接使用的协议, 它利用下层协议完成网络通信, 根据用途不同, 种类也不一样.
TCP/IP四层模型
为了实践编码方便, 理所应当采用了简化的四层模型, 即TCP/IP四层模型, 业界俗称为TCP/IP协议族.
下面也以规模性作为重点记录.
各层要点(重要)
TCP/IP协议族按照层次由上到下,层层包装:
最上面的是应用层,这里面有http,ftp, smtp, pop3, telnet等等我们熟悉的协议。
第二层则是传输层,著名的TCP和UDP协议就在这个层次。
第三层是网络层,IP协议就在这里,它负责对数据加上IP地址和其他的数据以确定传输的目标。
第四层是数据链路层,这个层次为待传送的数据加入一个以太网协议头,并进行CRC编码,为最后的数据传输做准备。
TCP/IP协议通信的过程其实就对应着数据入栈与出栈的过程。入栈的过程,数据发送方每层不断地封装首部与尾部,添加一些传输的信息,确保能传输到目的地。
出栈的过程,数据接收方每层不断地拆除首部与尾部,得到最终传输的数据。(以太网帧协议是加装头尾, 注意46-1500字节, 后面还会出现)
完整的以 HTTP为例子:
各层主要涉及的协议:
网络接口层
物理层负责0、1比特流与物理设备电压高低、光的闪灭之间的互换。
数据链路层负责将0、1序列划分为数据帧从一个节点传输到临近的另一个节点,这些节点是通过MAC来唯一标识的(MAC,物理地址,一个主机会有一个MAC地址)。
主要作为围绕 MAC 地址展开:
- 封装成帧: 把网络层数据报加头和尾,封装成帧,帧头中包括源MAC地址和目的MAC地址。
- 透明传输: 零比特填充、转义字符。
- 可靠传输: 在出错率很低的链路上很少用,但是无线链路WLAN会保证可靠传输。
- 纠错处理: (CRC):接收者检测错误,如果发现差错,丢弃该帧。
个人觉得, 一般谈到 网络接口层, 主要是2个概念再反复说:
- MAC 地址
MAC 地址是被烧录到网卡 ROM 中的一串数字,长度为 48 比特,它在世界范围内唯一(不考虑虚拟机自定义 MAC 地址)。由于 MAC 地址的唯一性,它可以被用来区分不同的节点,一旦指定了 MAC 地址,就不可能出现不知道往哪个设备传输数据的情况。 交换机转发原理(转发表)
交换机是一种在数据链路层工作的网络设备,它有多个端口,可以连接不同的设备。交换机根据每个帧中的目标 MAC 地址决定向哪个端口发送数据,此时它需要参考“转发表”
转发表并非手动设置,而是交换机自动学习得到的。当某个设备向交换机发送帧时,交换机将帧的源 MAC 地址和接口对应起来,作为一条记录添加到转发表中。当然交换机还有其他作用, 就不多说了.
交换机自学过程的原理(端口MAC联系过程):
链路层一般就是用的以太网帧
结构, 当前的intel就是基于以太网标准的.
当然以前还有IEEE802标准
(简称802.3), 只不过现在很少能抓的到他的包(普通网络, 以及路由器之间的等等, 都是以太网帧).
区别在哪里?
帧的结构不同(源地址后面是类型字段的就是以太网帧结构, 表示帧的类型, 比如0800表示后面是ip(如果是封装IP的帧, 这里链路层的号码就是后面协议类型字段的值), 0806是arp;
如果是长度字段的就是802帧结构, 类型字段在别的地方), 换句话说, 标准不一样. 以太网标准要简单一些.
实际山, 从涉及MTU问题这里, 分组交换就已经涉及到了.
分组交换是指将较大的数据分割为若干个较小的数据,然后依次发送。使用分组交换的原因是不同的数据链路有各自的最大传输单元(MTU: Maximum Transmission Unit)。
ARP及RARP协议 :
(非重点, 我以前电脑中过arp病毒, 所以印象深刻)arp和rarp都是链路层的协议.
ARP (Address Resolution Protocol)是根据IP地址获取 MAC 地址的一种协议。(因为最终确定一台计算机的不是IP, 而是Mac地址, 硬件地址)
如果目标主机处在同一个数据链路上(本地网络),那么可以直接得到目标主机的 MAC 地址,否则会得到下一条路由器的 MAC 地址。
ARP(地址解析)协议是一种解析协议,详细说明即: ARP 请求和 ARP 响应。
首先,源主机会通过广播发送一个 ARP 请求包:“我要与 IP 地址为 xxx 的主机通话,谁知道它的 MAC地址?”。
数据链路上的所有主机都会收到这条消息并检查自己的 IP 地址,如果与 ARP 请求包中的 IP 地址一致,主机就会发送 ARP 响应包:
“我就是 IP 地址为 xxx 的主机,我的 MAC 地址是:xxxx”。
但是反复查询效率很低, 也是不推荐的做法; 通常的做法是把获取到的 MAC 地址缓存一段时间。
(一旦源主机向目标地址发送一个数据包,接下来继续发送多次的概率非常大,因此这种缓存非常容易命中)
当主机要发送一个IP包的时候,会首先查一下自己的ARP高速缓存
(就是一个 IP-MAC 地址对应表缓存)。
查看arp缓存(arp映射) :
arp -a
; 清理:arp -d
. (路由器上你要清理的话, 它基本上马上就会去再解析, 除非把接口, 即端重新断开接上)
如果查询的 IP-MAC 值对不存在,那么主机就向网络发送一个ARP协议广播包,这个广播包里面就有待查询的IP地址,而直接收到这份广播的包的所有主机都会查询自己的IP地址.
如果收到广播包的某一个主机发现自己符合条件,那么就准备好一个包含自己的 MAC 地址的ARP包传送给发送ARP广播的主机。(即arp响应)
而广播主机拿到ARP包后会更新自己的ARP缓存(就是存放IP-MAC对应表的地方)。发送广播的主机就会用新的ARP缓存数据准备好数据链路层的的数据包发送工作。
当下一次发送 ARP 请求或超过一定时间后,缓存都会失效,这保证了即使 MAC 地址与 IP 地址的对应关系发生了变化,数据包依然能够被正确的发往目标地址。
总结:
表面上发送的时候, 看逻辑地址, 实际上发送的时候就会去确定该逻辑地址对应的物理地址(如果不是直连网络, 即不是同一数据链路, 会先进行IP的不断查找, 找到下一跳, 直到最终找到相应IP的地址, 再获取到物理地址(技术上是arp全子网广播, 然后相应IP的主机发送arp回应给最开始的发送者)); (arp缓存表是补充手段, 本质还是靠物理地址进行发送的), 确定物理地址之后, 才开始真正发包. 只知道IP并不能发送任何数据(看不到任何IP, TCP流量).
代理arp:
一般路由器是默认就开启了代理arp功能的, 特别是对外进行NAT转换的路由器
.
比如说你请求的一个互联网地址, 找到了相应的路由, 如果他没有开启代理arp功能, 那么你的arp广播就会被忽略.
因为只有你请求该路由的IP时才会返回给你mac地址, 而不会对一些对该路由控制的子网主机的arp请求的回应.
其实是对子网的mac查询的支持, 不必具体找到子网内的具体主机再进行arp回应(外部看来, 找到该路由器或者防火墙就找了主机, 他们认为该路由的mac和该子网内的具体主机就是同一个).
(该机制也有弱点, 可以参考《路由交换 卷1》)
自由(free)arp:
开机的时候, 自己给自己发送一个arp请求, 一般情况下不希望有人回应, 那么我就可以用这个ip地址, 但是如果有人回应, 那就坏了, 说明我当前正在使用的ip地址, 有人使用了.
这个时候往往就会报错, duplicate addresss x.x.x.x
.
(其他主机收到这个包的时候, 也会尝试去更新本地的mac缓存信息, arp -a
可以查看; 小心free arp攻击, 冲掉合法mac地址)
RARP协议的工作与此相反,不做赘述。
只用IP是不行的, 只用Mac是非常低效的.
其中用意个人体会吧, arp和rarp协议有其存在的必要性.
MTU
关于 MTU 争议很多, 具体可以见下面 MTU问题部分.
最后补充, 数据链路层协议工作在物理介质相互连接的端点之间, 范围有限, 所以需要IP, 逻辑地址来夸不同链路.
网络层
数据链路层的作用在于实现同一种数据链路下的包传递,而网络层则可以实现跨越不同数据链路的包传递。(也就是说, 从这一层才涉及跨网络)
这一层内容比较多, 但是主要谈IP协议相关, 其他顺带谈一下.
IP协议是网络层的一个重要协议,网络层中还有ARP(获取MAC地址)和ICMP协议(数据发送异常通知)IP协议是TCP/IP协议的核心,所有的TCP,UDP,ICMP,IGMP的数据都以IP数据格式传输。要注意的是,IP不是可靠的协议,这是说,IP协议没有提供一种数据未传达以后的处理机制,这被认为是上层协议: TCP或UDP要做的事情(ICMP也起到了异常通知的作用)。
IP协议 : (主要说ipv4
, 因为ipv6也就我们中国再说, 看看世界上其他国家, 根本不鸟的, 题外话)
- 地址部分
- 路由部分
- 分包重组部分
- ip协议头(首部)
IP协议的特点:
- 不可靠: 比如路由器缓存满了, 它会丢掉包, 然后ICMP通知给源, 之后交由TCP保证重传之类的动作.
- 无连接: 不用建立会话连接才开始发送, 也不按照顺序发, 走的IP路径也不确定. (组装由TCP排序, 顺序不对的包一直被缓存, 它把数据捋顺)
IP 查看就用 ipcofig -all
或者 ifconfig
, 或者netstat -ano
, 或者你要改ip地址:(linux下)1
ifconfig eth0 1.2.3.4 netmask 255.255.255.0
寻址部分 :
IP地址是一种在网络层用于识别通信对端信息的地址。它有别于数据链路层中的MAC地址,后者用于标识同一链路下不同的计算机。
IP地址由32位正整数表示,为了直观的表示,我们把它分成4个部分,每个部分由8位整数组成,对应十进制的范围就是0-255。
比如172.20.1.1
可以表示为:10101100 00010100 00000001 00000001
。
转换规则很简单,就是分别把四个部分的十进制(0-255)与8位二进制数字进行转换。
IP地址由两部分组成:网络标识
和主机标识
。
- 网络标识用于区分不同的网段,相同段内的主机必须拥有相同的网络表示,不同段内的主机不能拥有相同的网络标识
- 主机标识用于区分同一网段下不同的主机,它不能在同一网段内重复出现
32位IP地址被分为两部分,到底前多少位是网络标识呢?
一般有两种方法表示:IP地址分类
、子网掩码
(无类IP)。
IP地址分类 :
IP地址分为四个级别,分别为A类、B类、C类和D类。分类的依据是IP地址的前四位:
- A类IP地址是第一位为“0”的地址。A类IP地址的前8位是网络标识,用十进制标识的话0.0.0.0-127.0.0.0是A类IP地址的理论范围。另外我们还可以得知,A类IP地址最多只有128个(实际上是126个,下文不赘述),每个网段内主机上限为2的24次方,也就是16,777,214个主机。
- B类IP地址是前两位为“10“的地址。B类IP地址的前16位是网络标识,用十进制标识的话128.0.0.0-191.255.0.0是B类IP地址的范围。B类IP地址的主机标记长度为16位,因此一个网段内可容纳主机地址上限为65534个。
- C类IP地址是前三位为“110”的地址。C类IP地址的前24位是网络标识,用十进制标识的话192.0.0.0-223.255.255.0是C类IP地址的范围。C类地址的后8位是主机标识,共容纳254个主机地址。
- D类IP地址是前四位为“1110”的地址。D类IP地址的网络标识长32位,没有主机标识,因此常用于多播。
简单记忆如下:
A类IP地址: 0.0.0.0~127.0.0.0
B类IP地址: 128.0.0.1~191.255.0.0
C类IP地址: 192.168.0.0~239.255.255.0
这类分类方法往往导致, 小网段不够, 大网段有余. 例如C类网段, 最多254台主机, 如果是255台怎么办? B类的上限是65534个, 明显浪费.
如果控制B类网址的上限数目? 因为B类网址, 说是以10
开头, 前16位为网络号, 但是具体是多少真正在用就不一定了.
那么就不对网络号规定位数?
, 但是这样就根本没有办法解析IP地址了, 因为不知道前多少位是网络号, 此时就演变出了子网掩码
, 用子网掩码来解释子网占了多少位, 而剩余的主机号占了多少位.
子网掩码 :
IP地址分类的本质是区分网络标识和主机标识,另一种更加灵活、细粒度的区分方法是使用子网掩码。
A类, B类型, C类, 这类规定了网络号位数的IP划分方式, 浪费了大量的IP, 现在的无类路由(CIDR), 即mask可以更改网络号的位数的划分方式更有利于充分利用IP地址,
也是对于IPV4的扩充 (单独的mask也可以用于解决子网主机限制的).
它也是32位的, 只不过用其中 1 的数量表示子网, 即网络号的部分, 剩余的表示主机位.
子网掩码的方式, 涵盖并对应了 IP
分类的方式, 但是又比规定死位数要灵活.
例如, B类网址, 即以10开头的网络号, 它本来网络号规定是16位, 可以容纳65534台.
现在通过子网掩码, 可以规定它的网络号是26位, 而不是规定死的16位, 可以容纳64台主机(除去主机号全0表示本子网, 全1表示广播外, 实际容纳62台)
但注意一下, 缺省情况下, 子网掩码和IP分类保持一致: 例如A类地址的缺省子网掩码为255.0.0.0,B类为255.255.0.0,C类为255.255.255.0。
全0的IP, 即表示本子网的IP, 只能是源IP, 不可能是目的IP; (DHCP 动态地址获取协议有用到, DHCP其实是封装了UDP包, 再没有获取真实IP地址前, 源IP地址就是0)
关于子网广播的内容部分, IP划分的子网, 比如8位, 24位等只能作为目的地址, 不能作为源地址. (常见的广播地址就是目的地址啦)
但是全1的, 比如255.255.255.255或者..*.255的有些操作系统, 比如window, 你如果Ping的话就不行.
另外一些则可以支持, 比如linux, 它会让所有子网内的主机都返回信息.(你可以认为它是被保留的, 不能作为主机去ping)
微软的很多东西就是不按照标准来, 它自己去实现了一套, 从子网广播这里就能看出来.
其他扩充手段:
- IPV4, 最多划分42亿个IP.
- IPV6不仅是长度(128位)的扩充, 而且, 它对于字段的规划&划分&长度要求等更加规范,标准.
回环IP :
loop ip: 127打头
的IP地址, 简单理解: 去了还能回来的接口. (IP输入(in 收), IP输出(out 出))
比如广播/多播, 还是ping自己, 都是通过本地回环接口(回环驱动程序), 直接进行IP输出输入的. 如果目标地址不是本地/本机网络机制, 那么才去走arp那条路, 之后返回给IP输入端口.
还有在配置nat的时候, 本机的某个地址如果配置成 IP_A – ANY, 所以所有本地访问出去的都要进行公网转换; 那么本地的 IP_B 也是会进行转换的(本地网络应该直连不用转换).
这个时候, 就可以利用一下回环IP, 配置策略路由, 即把源是 IP_A , 目的是 IP_B 的, 送到环回口.
因为NAT配置要满足3个条件:
- 内部接口入
- 外部接口出
- 满足配置的列表
我们破坏了第二个条件, out条件, 直接输出到IP回环地址上, 再从改地址配置列表到IP_B. 从路由的角度, 它看到是环回口进来的, 就不走NAT了, 而是直接匹配它的目的地址.
路由部分
路由控制(Routing)是指将分组数据发送到目标地址的功能,这个功能一般由路由器完成。
路由器中保存着路由控制表
(存储转发表),它在路由控制表中查找目标IP地址对应的下一个路由器地址。
下图描述了这一过程:
由图中也可看出路由器连接着两个子网, 并且在路由表中进行范围或者最佳匹配. 同一个数据链路网络(本地网络)直接传送, 没有太多要说的是.
但是如果经过路由的, 即不同网络: 目的IP地址不变, 但是源mask地址, 即子网地址是在不断的变化的, 其实是路由器的网关地址.
源站路由(严格, 宽松)指定跳的路径(一次性指定多个中间目的地址, 普通的IP包的目的地址是不变的, 但是这样情况IP包地址是可变的).
(防火墙一般会对tcp, ip设置选项部分的, 一概不予通过; 源站路由也是被防火墙封杀的)
路由控制表可以由管理员手动设置,称为静态路由控制,或者路由器可以和其他路由器互换信息比即使自动刷新路由表,
这个信息交换的协议并没有在IP协议中定义,而是由一个叫做“路由协议”的协议管理. netstat -r
查看路由表(网关或者静态路由).
总结:
IP选路和路由表相关, 一般就是udp, tcp包来了, 借用它来查询下一跳(然后往后传递); 或者从网络接口层拿到数据处理IP选项然后上发.netstat -rn
可以查看路由表(但不能更新路由表, 更新路由表,一般是route命令).
实际传输过程中, 数据包也会在路由器这里拆包并封包再转发, 因为要重新填写下一跳路由地址
路由控制这有会有两个问题:
- 转发成环问题(不存在相关的IP地址)
IP首部解决这个问题 - 分割与重组
IP受限于链路层的MTU, 过大的数据就要分组, 远端主机再重组(首部中的标志和偏移)
由于环
和分组
问题都依赖于IP首部
结构, 下面就说说.
IP首部
ip协议头(20字节, 加上选项部分, 最大60字节), 这里有很多设计讲究.
8位的TTL字段
(解决环的问题, 不可能让你无限传下去)
这个字段规定该数据包在穿过多少个路由之后才会被抛弃。某个IP数据包每穿过一个路由器,该数据包的TTL数值就会减少1,当该数据包的TTL成为零,它就会被自动抛弃。
这个字段的最大值也就是255,也就是说一个协议包也就在路由器里面穿行255次就会被抛弃了,根据系统的不同,这个数字也不一样,一般是32或者是64。
其他部分介绍:(从上往下一层层看)
- 4位版本信息(表示该协议的内容解析方式, 也记录了选项部分最多40字节: 15*4-20=40; 注意4bit最大值就是15)
- TOS 8位: 分别分为3组: 3bit(IP优先级), 4bit(最小延迟, 最大吞吐量, 最高可靠性, 最小费用; 置1表示选择; 全0表示普通包), 1bit(保留).
一般是给路由用的(给路由的建议, 路由器不理睬就没办法了), 保证其服务质量和服务侧重. (比如FTP就需要最大吞吐量, 它就会把这里面的最大吞吐量位置1) - 总长度(Total Length):表示IP首部与数据部分总的字节数,该段长16比特,所以IP包的最大长度为65535字节(2^16)。虽然不同数据链路的MTU不同,但是IP协议屏蔽了这些区别,通过自己实现的数据分片功能,从上层的角度来看,IP协议总是能够以65535为最大包长进行传输。
(上层看不到下层的MTU限制) - 16位标识(ID:Identification):用于分片重组。属于同一个分片的帧的ID相同。但即使ID相同,如果目标地址、源地址、上层协议中有任何一个不同,都被认为不属于同一个分片。
- 标志(Flags):由于分片重组,由3个比特构成。
第一个比特未使用,目前必须是0。
第二个比特表示是否进行分片,0表示可以分片,1表示不能分片。(例如如果你要分片, 我设置了df位, 你就不要分了, 直接丢弃然后icmp返回路由信息即可) (DF)
第三个比特表示在分片时,是否表示最后一个包。1表示不是最后一个包,0表示分配中最后一个包。(MF) - 片偏移(FO: Fragment Offset):由13比特组成,表示被分片的段相对于原始数据的位置。它可以表示8192(2^13)个位置,单位为8字节,所以最大可以表示8 x 8192 = 65536字节的偏移量。
- 协议: 表示IP首部的下一个首部属于哪个协议。比如ICMP协议号为1. TCP协议的编号为6,UDP编号为17.
- 首部校验和:用于检查IP首部是否损坏(IP校验和只校验IP首部)
- 可选项:仅在试验或诊断时用,可以没有。如果有,需要配合填充(Padding)占满32比特。
比如路径记录(路由的IP地址), 时间戳, 严格选站路径(每跳目的IP地址都变, 一般的情况只是源IP地址会变)
IP数据报最多65535字节, 但是实际上, 还受到应用程序的限制, 即socket api的实现限制; 以及内核实现(源/目的主机的网络实现)也有关系.
内核到用户空间的数据拷贝也和内核实现有关, 应用程序接口socket api有关.
IP分片
卷1里面讲完udp数据报协议之后才讲了IP分片.
术语:
- IP数据报: 一个完整的IP数据包
- IP分组: IP层和链路层传输的单元(即不分片的时是数据报, 分片时是一个分片)
在TCP的时候, 由MSS保证了应用数据的分/合(分片), 并且只按照字节计算, 所以TCP的包一般不存在IP分组, 可以理解成TCP帮助处理了流量数据, 所以避免了IP层在动手.
而UDP协议的时候, udp协议不做这个事情(udp不做判断, 也不做处理), 因为整个应用数据是报文(有边界), 直接按照应用数据来封包, 也不进行拆分, 一股脑交给IP, 即:1
应用数据 --> UDP头部+应用数据 --> IP头部+UDP头部+应用数据
所以这个时候, IP在传输的时候, 可能就要进行分组传输了(由链路层的MTU限制了)
udp 不做判断也有好处, 来一个任务, 就封装一个包, 直接传输走(tcp可能会嫌弃包的字节数太少, 等积攒一定数据才走, 流的重组装), 实时性高; 但是udp不管对方是否收到.
(tcp 保证可靠传输, 但是其建立连接握手, 协商, 重传等都是需要时间的)
IP把数据包和MTU进行比较(送出的时候才查), 然后决定是否分片, 中间跳可能多次再分片, 直到目的方才进行组装.
(但是udp层不知道 ip是否分片, 发送方也不知道中途是否会被分片; tcp自己处理了包的大小, 一般不存在分片)
中途如果丢了一个数据分片, 等于说整个包就失效了(tcp则可能就要重传了, 因为包不完整是不回应ack的, tcp不知道分片的事儿).
因为发起方(IP知道, 但是IP的上层才是发起方, 他们不知道)可能都不知道中途会被分片, 所以重传某一片是不可能的.
分片大小一般整体低于1500, 但是分片不涉及IP头部部分.
udp首部信息只能在第一片中出现(如果端口转发的路由, 后面的分片由于没有头部信息, 就转发不了), 后面的包的大小一般是8的倍数.
(如果最后一个分片不足, 应该填充0; 补齐到46字节) 尽量调整MTU大小, 避免IP分片.
NAT 和 NAPT 技术 :
NAT (Network Address Translator) 是一种用于将局域网中的私有地址转换成全局 IP 地址的技术。
在连接上无线路由器的时候,如果检查一下设备的 IP 地址,也许你会发现是类似于 192.168.1.1 这样的局域网 IP 地址。那不同网段中,IP 地址都是 192.168.1.1 的主机改如何通信呢?
下图描绘了 NAT 的工作原理:
局域网中 IP 地址为 10.0.0.10 的主机向全局 IP 地址 163.221.120.9 发送数据。NAT 路由器将数据包的源地址修改成自己的全局 IP 地址:202.244.174.37。同理,接收数据时,路由器把目标地址 202.244.174.37 翻译成内网地址:10.0.0.10
路由器只有一个对外的全局 IP 地址,如果有多个内网主机都向外部通讯怎么办呢?这时就要使用 NAPT 技术,它和 NAT 从原理上类似,但它可以转换 TCP 和 UDP 端口号。
使用 NAPT 技术时,不同的内网 IP 被转换成同一个公共 IP 地址,也就是路由器对外显示的全局 IP 地址,但是被附加不同的端口号以示区分:
不管是 NAT 还是 NAPT,都需要路由器路由器内部维护一张自动生成的地址转换表。以 TCP 为例,建立 TCP 连接首次握手的 SYN 包发出时会生成这个表,关闭连接时会发出 FIN 包,收到这个包的应答时转换表被删除。
ICMP协议 :
ICMP被认为是IP的一个附属协议(ICMP报文整体还是使用ip首部, 即ICMP报文封装在ip数据报内部), 负责异常通知(反应网络中出现的问题).
实际上不仅仅是不分包, 丢包的时候会发ICMP信息通知源, ping, traceroute等都有用到该协议. (ICMP其实蛮复杂的)
IP协议并不是一个可靠的协议,它不保证数据被送达,那么,自然的,保证数据送达的工作应该由其他的模块来完成。
其中一个重要的模块就是ICMP(网络控制报文)协议。
当传送IP数据包发生错误。比如主机不可达,路由不可达等等,ICMP协议将会把错误信息封包,然后传送回给主机。
给主机一个处理错误的机会,这也就是为什么说建立在 IP 层以上的协议是可能做到安全的原因。
其报文主要分为两种: 查询,差错报文.用类型 + 代码
区分: 8位类型, 8位代码.
比如ping request, 类型为8, 代码为0; response, 类型为0, 代码为0. 当然还有其他的代码, 比如不可达时候, 类型为3, 后面具体的代码根据不同的情况而不同(端口不可达是3,3).
也就是说, 可达不仅仅是包括IP层的可达, 即主机是否可达, 也还包含其他不可达, 比如协议不可达, 端口不可达等, 总之就是再反馈信息.
(反馈报文
里面除了包含包ID, 还包含协议端口信息, 即IP分片的第一片(它含有端口号信息))
该协议有一个重要应用, ping
命令, 具体可以参考下面 其他部分
.
哦, 还有一个TTL检测工具, traceroute
也需要借助它.
一般的网络问题, 处理起来大致是:
- 抓包
- 分析网络, 包, 协议分析
要么就看首部, 报文; 或者就是看ICMP差错报文信息.
传输层
这里已经不仅仅是理论了, 而且有大量的code实践, 可以单独参考我的其他文章:
- tcp: how-about-tcp
- udp: udp model
应用层
主要应用层协议:
FTP, Telnet, HTTP, SMTP/POP3, SSL, SNMP(了解即可), 语音协议(SIP等)
FTP : 数据传输的主流协议(块数据流)
- 两个端口: 控制信道(tcp 21) + 数据信道(tcp 20); 因为下面封装的是TCP所以都要经历TCP的握手和分手过程
- 两种模式: 主动模式(客户端PORT+6个数字地址信息) + 被动模式 (都是从服务器端来说的, 主要是第二信道的区别, 被动模式第二信道是服务器连接客户端; 第一信道一般都是由客户端发起)
其中客户端建立控制信道通信时 PORT(建立第二信道), LIST(罗列目录), PASV(询问是否支持下载), RETR(获取文件)等命令比较重要,
但是具体的数据是通过第二信道传输的(比如反馈具体的数据结果).
防火墙一般做控制就是根据这些命令来的, 比如不让外部下载, 那么看到RETR命令直接掐死. (NAT, NPAT或者防火墙都是要做地址转换的)
FTP不安全, 如果使用了账号密码, 域管理员抓包是可以看到具体的信息的(nc + ftp命令模拟一下就知道了);
TELNET : 交互式数据流(你的行为有反馈)
远程登录协议和主要方式(使用23号端口), 但不安全. 一般现在使用ssh协议.(不再是明文, 而是密文; 乱码)
(多个字符可能在多个包中, 收到第一个包才能收第二个; 可以回显出结果)
SMTP/POP3 : 发/收邮件的协议 (25号端口)
这个协议也完全可以用命令行指令(在TCP建立连接之后), 例如RCPT, DATA, QUIT等命令完成收发邮件.
防火墙监控, 也是通过上述协议里面的命令进行控制的. 上面所有这些协议, 都可以通过nc IP 端口
然后进行模拟.
DNS : Domain Name System,域名解析系统
因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。
通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。 (也还分A类解析, B类解析等)
以解析域名: www.ietf.org为例:
根服务器其实并不知道 www.ietf.org 的 IP 地址,但是它知道 itef.org 域名服务器的地址,所以它把这条查询请求转发给 itef.org 域名服务器。
DNS请求被逐层下发,直到找到对应的 IP 地址为止。
DNS协议运行在UDP协议之上,使用端口号53; (当然也能运行在TCP之上, 只是同时查询广播, 用UDP更好)
应用层, 我接触的,最主要的还是 HTTP, HTTPS协议, 由于比较重要, 我还是单独成文了.
HTTP :
超文本传输协议(请求应答协议, 80端口)
它其中也有一些命令比如GET, HEAD等8个(OPTIONS, DELETE, POST等服务器考虑到安全性, 一般是不支持的), 但一般由于浏览器已经可以使用HTTP协议, 所以一般看不到.
也还有cookie和session来保存客户端或者服务端的状态信息(因为HTTP是无状态&瞬时协议, 完成任务连接就没有了, 根本不维护数据或者状态或者连接), 让多次HTTP连接可以共用一些信息.
例如我已经登录了微博, 我在新开一个窗口, 发现还是登录的.
多连接问题, 比如远端网页信息比较大, 它会分多次get去拿数据, 即一次通信可能就有几十个连接同时发出.
HTTP非加密传输(中途可能会用到Base64编码方式(基本认证), 相当于明文), 总之是不安全的, 有些抓包软件比如omni peek
直接给解码了.
一般要走认证的服务, 可以使用https, 而不是使用http.
详细可以参考: HTTP相关
HTTPS :
详细可以参考: HTTPS相关
其他问题
MTU 相关问题
MTU
, 根据协议性质&网络性质&操作系统(内核协议栈)实现等因素的不同而不同, 通常认为这也是IP分片传输或者设置TCP MSS的依据.
其实这这个MTU指的是链路层的数据区, 并不包括链路层的首部和尾部的18个字节.
链路层谈MTU, 即链路层的传输极限. 一般认为MTU如果是以太网就是1500(802.3shi 1492).
并且都是出方向去查MTU, 例如如果IP层的数据大于1500, 那么IP层就会进行分片(fragmentation). 分为若干片, 让每一片都小于MTU. (组装只会在目的地进行组装)
(实际上从卷1的介绍来看, 其实tcp有mss保证, 所以一般不会经由IP再分片; 但是udp本身不处理数据大小, 所以会让IP插手分片事宜)链路层的MTU(最大传输单元)
会和操作系统(Unix, Linux, windows), 和 TCP分节(MSS, max sgement size), 网络类型(以太网, ASDL)会扯上关系:
- 如果只谈 以太网 最大传输单元, 那么就是 1500 字节
- 如果谈IP数据报的数据区长度, 最大为1480字节
- 如果谈TCP packet 最大长度, 那么 window 是, 1500-20-20=1460(减掉ip, tcp头部各20字节), linux/mac/unix则还要减去12字节 tcp time stamp, 即1448字节
tcp time stamp 是避免序列回绕以及计算RTT的依据; 当然windows没有这么实现, 自然也不需要. - 如果谈udp, UDP数据报的首部8字节, 所以UDP数据报的数据区最大长度为1472字节
强调一下, 这里说的是最大, 最小为64字节
(以太网最大数据帧1500+14+4=1518字节, 头14字节, 尾CRC校验位4字节)
大于MTU, 这个时候发送方IP层就需要分片(fragmentation). 把数据报分成若干片,使每一片都小于MTU.而接收方IP层则需要进行数据报的重组.
用于ping时, 一般就是1500-20-8 = 1472, ipv4中icmp协议头为8字节, 但是ipv6中可能是4也可能是8.
MTU 路径发现
但是 MTU 不是一成不变的, 它会根据网络的性质(比如中途有低速链路), 通信状态(网络的传输能力可能会有一些差别)进行一些列的调整.
比如传输过程经过3条(3个链路网络), 分别是: 800, 600, 500; 此时路径MTU就是500, 保证每个包(分组)的数据小于路径MTU, 那么才绝对不会分片(IP分片).
否则, 不能保证一定不被分; 例如数据大小是700, 第一跳(hop)不被分, 但是第二跳就被分了.
并且, 一旦分片, 可能还会被多次分片. 这样带来的丢失重传处理, 很麻烦/浪费时间, 降低传输效率.
但同时也希望在不分片的基础上, 尽可能大的数据包去发送.
MTU路径发现, 就是解决办法:
主机会首先获取整个路径中所有数据链路的最小MTU,保证每次传输都小于该值。
传输过程(通常都只是检查出去方向的, 即由发送方控制)中的任何一个路由器都不用进行(再)分片工作(但路由器有这个能力)。
- 为了找到路径MTU,主机首先发送整个数据包,并将IP首部的禁止分片标志设为1.
这样路由器在遇到需要分片才能处理的包时不会分片,而是直接丢弃数据并通过ICMP协议将整个不可达的消息发回给主机
(如果路由器配合的话, 还会返回下一跳, 即出口MTU的值; [不配合的话, 那么就逐跳减少探测) - 主机将ICMP通知中的MTU设置为当前MTU,根据整个MTU对数据进行分片处理。如此反复下去,直到不再收到ICMP通知,此时的MTU就是路径MTU。
一般互联网, 即以太网链路层的网络, 一般都是1500, 而如果是隧道, 比如vpn, 那么可能就需要自己配置一下了(因为还有物理接口, VPN等字段需要配置&传输), 比如降低一下MTU值.
还是以udp为例子:(1500-20-8=1472的理论值)
上图也可以看到ICMP协议也是用的IP首部
由链路层的传输能力, 限制了上层的IP层的分片(分组), tcp的分节(segment)MSS. MTU是对输出链路探测的结果, MSS大小是双方协商的结果.
MSS
TCP中谈论的更多的是, MSS (maximum segment size) 最大分节大小. 一般为了避免分节, 也会进行相关的协商或者探测, 取网络的最小值.
撇开系统因素(比如linux/unix下可能会有tcp time stamp部分), 一般是:1
2ipv4 1500-20-20 = 1460 字节
ipv6 1500-20-40 = 1440 字节
MSS属于TCP的内容, 它通常也会和通知窗口
, 拥塞窗口
搅和在一起, 但是总可以认为, 不管其他因素怎么变, 一般TCP发送的大小最大是MSS.
因为它是可靠传输, 它自己处理好上层应用数据的分节, 这样才不会让IP层再进行分片/分组交付给链路层; 其实也方便它自己处理丢包重传等问题.
详细内容请查看 TCP 的文章: how-about-tcp
大小端问题
编写一个程序, 看一下主机字节序是little-endian
还是big-endian
.
下面给出一种简单的方案:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
int a = 0x1122;
char *b = (char*)&a; //取低8位
if(*b == 0x11){
printf("BE\n");
}else{
printf("LE\n");
}
printf("%d\n", *b==0x22); //1
return 0;
}
当然你用联合union也挺好:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int main()
{
typedef union {
int num;
char num_low8;
} NUM;
NUM number;
number.num = 0x1122;
if(number.num_low8 == 0x11){
printf("BE\n");
}else{
printf("LE\n");
}
printf("%d\n", number.num_low8==0x22); //1
return 0;
}
注意:操作系统读写内存都是从低地址到高地址。
一般本地主机是小端,但是网络字节序就是大端了
这个时候涉及到转换问题,一般是IP地址的转换(端口可能也需要),有相应的函数htons()、ntohs()、htonl()、ntohl() ,
并且像protobuf库也有相关的API, asio库直接就封装好了, 什么都不用管.
Ping原理
ping可以说是ICMP的最著名的应用,是TCP/IP协议的一部分。利用“ping”命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障。
例如:当我们某一个网站上不去的时候。通常会ping一下这个网站, 如果丢包研究就是网络问题, 如果其他连接不可达, 就会出现超时或者主机拒绝连接等信息。
它利用ICMP协议包来侦测另一个主机是否可达, 原理如下:
用类型码为0的ICMP发请求, 收到请求的主机则用类型码为8的ICMP回应
ping: 除了type和code注意, 还要注意TCP/IP在内核中就支持了ping服务端.
并且ping报文部分, 即ICMP报文部分就包含了标识符, 即用户进程的id号(linux实现), 但是windows实现, 是全局统一编号, 而不是进程对应;
其他部分还包括收发包的时间(报文体记录或者window上是由系统记录).
ping -R <ip>
路径IP记录, 最多记录9个IP (2条就会用掉6个)
(因为IP首部整个最多60字节, 去掉20字节, 选项部分还剩下40字节(40/4=10); 加上ping自己需要传送的信息, 最多就只能记录9个路径ip地址)
用于ping时, MTU一般是1500-20-8 = 1472, ipv4中icmp协议头为8字节, 但是ipv6中可能是4也可能是8.
Traceroute原理
这个工具比较强大, 给的选项也比较多, 通常是来检查跳(hop)数.
Traceroute是用来侦测主机到目的主机之间所经路由情况的重要工具,也是最便利的工具。
原理: (改变ttl数, 借助icmp反馈)
它收到到目的主机的IP后,首先给目的主机发送一个TTL=1的UDP数据包,而经过的第一个路由器收到这个数据包以后,就自动把TTL减1,而TTL变为0以后,路由器就把这个包给抛弃了,并同时产生 一个主机不可达的ICMP数据报给主机
主机收到这个数据报以后再发一个TTL=2的UDP数据报给目的主机,然后第二个路由器给主机发ICMP数据报
如此往复直到到达目的主机
traceroute 用的udp的包(目的端口号33434), 但ttl=1, 即路由不转发该包, 然后收到icmp反馈(超时差错报文), 知道该跳的相应时间.
之后发送ttl=2(此时目的端口号33435, 之后慢慢增长), 然后再收到反馈, 直到到达目的地(此时端口号肯定大于33434), 然后该服务器反馈端口不可达差错.
这样,traceroute就拿到了所有的路由器IP.
就写这么多吧.