Chapter1: 浏览器生成消息
- 为什么要用域名代替IP地址访问?
- 域名比IP地址更好记
- 如果服务器使用了虚拟主机功能,则无法通过IP地址访问
- 为什么要用DNS把域名转换为IP地址,直接使用域名确定通信对象不行吗?
- IP地址长度为32bit(4字节),而域名需要几十个字节,增加了路由器负担,传送时间也会变长
- 而且IP长度固定,域名长度不固定,处理更复杂
- 域名不像IP地址那样有区域性和层次性
- Socket库解析IP地址的步骤
- 调用了
gethostbyname
函数
- 调用了
- IP地址由网络号与主机号组成,通过子网掩码分割
- 二者边界不是一定在句号上的
- 二者边界不是一定在句号上的
Chapter2: 用电信号传输TCP/IP数据
协议栈
- 操作系统中控制网络通信的部分称为协议栈
- 一般包括上层TCP,UDP协议模块和下层IP协议模块
- 应用程序(例如浏览器)委托它传递信息
- 套接字就是协议栈中存放控制信息(地址,端口等)的内存空间
- 套接字本身只是一个抽象概念
- Socket连接过程
- 开辟一块内存放置套接字结构体,并写入初始信息
- 例如要使用IPv4,tcp连接
- 客户端把要通信的服务器IP和端口写入套接字,向服务器发送连接信息
- 服务器等待连接,当数据包到来时,解析出TCP头部中的端口号,找到对应的套接字,写入客户端信息(地址,端口)并修改状态为正在连接
- 开辟一块内存放置套接字结构体,并写入初始信息
- 缓冲区
- socket在初始化时会开辟一块缓冲区临时存放要收发的数据
- 协议栈并不是一收到应用程序发来的数据就发送网络包的,这样会产生大量小包,而是累计一定数据后才发送
- 所以对于上层应用程序来说,调用了send向协议栈(具体来说是某一套接字)发送数据后就直接返回了,send的任务仅仅是把数据拷贝到协议栈缓冲区中,发送给目标套接字是TCP的任务
- socket在初始化时会开辟一块缓冲区临时存放要收发的数据
- 协议栈判断发送时机的依据
- 等待收到的数据超过或接近网络包所能容纳的最大数据长度后(MSS)才发送
- 网络包(MTU)最大大小取决于传输过程中经过网络的类型,如以太网,无线网等
- 协议栈内置超时
- 防止应用程序发送数据频率太小,为了等待数据接近最大数据长度而造成的延迟
- 等待收到的数据超过或接近网络包所能容纳的最大数据长度后(MSS)才发送
- TCP与IP的分片
- 上面的操作实际上的TCP协议主动帮IP协议做的分片操作
- 实际上TCP是可以不分片的,例如UDP就不会分片,而是由IP分片
- 为什么TCP要多此一举呢?
- 因为如果交给IP分片,那么如果其中一个分片丢失,由于IP协议没有错误补偿功能,导致TCP发送失败,这样TCP就需要再次发送整个大数据
- 如果TCP先分片了,就只要重传一个分片的数据
- 而UDP本身没有重传机制,所以无所谓丢失一个分片还是整个数据
参考:https://blog.csdn.net/ns_code/article/details/30109789
TCP通信过程
-
使用ACK号确认网络包已经收到
- 发送方在发送数据包的时候,会在TCP头部写入数据的序号
- 及数据是从头开始的第几个字节
- 为了通信安全,序号不从1开始,而是随机计算一个初始值
- 接收方把数据包长度减去头部长度得到数据长度,并和序号相加,写入TCP头部的ACK字段,返回给发送方
- 并且要设置控制位的ACK比特为一,表示ACK数据有效
- 在收到接收方的ACK号并确认无误前,发送方把发送的数据暂存在缓冲区中,以便重传
- 其实可以不用每次接受都发送一个ACK号,而是几次接收后返回最后一个号
- 发送方在发送数据包的时候,会在TCP头部写入数据的序号
-
因为TCP 协议拥有错误补偿机制,因此网卡,路由器,集线器等都不再有错误补偿机制,而是发生错误直接丢包
-
SYN控制位置1表示连接开始,此时序号为随机的初始值
-
动态等待ACK返回的时间
- 发送方在长时间收不到ACK返回时重传数据
- 但是返回时间收到网络状况影响,可能会产生不必要的重传,增加网络负担
- 因此发送方会监测ACK返回时间并动态调整超时时间
-
滑动窗口
- 初始的TCP,发送方在等待ACK返回再发送下一个包
- 为了提高效率,可以一次性发送多个包,接收方应用程序处理前把包存在缓冲区中
- 为了防止发送速率大于处理速率时产生缓冲区溢出,接收方需要告知发送方缓冲区还有多少空间,发送方据此调整发送速率
- 该窗口更新的信息的包可以和ACK包合并为一个包
-
TCP断开连接过程
- 允许服务器(HTTP1.0)和客户端(HTTP1.1)任意一端主动断开连接
- A发起断开连接,把TCP头部FIN位设置为1
- B返回ACK号表示收到断开信息
- 当B端应用程序向号协议栈求数据时(read),协议栈告知数据传输完毕,此时应用程序调用close结束连接,B也发送FIN为1的数据包
- A返回ACK表示收到B发送的断开信息
- 在TCP断开后,A中的套接字还会存在一段时间
- 因为如果A最后返回的ACK丢失了,B会再次发送FIN
- 如果此时旧的套接字已经删除,恰巧同一个端口绑定了新套接字,这个套接字会被错误地关闭
路由器和交换机
- 路由器是按照IP协议判断下一个路由器位置的设备
- 使用IP地址查找IP表来判断
- 路由器会在接受包后丢弃老的MAC地址,然后根据IP地址添加新的MAC地址
- 交换机是在子网中按照以太网协议把包传输到下一个路由器的设备
- 使用MAC地址查找MAC表来判断
- 上述描述是建立在以太网的基础上的,实际上还可能是无限局域网等别的网络,它们有各自的协议,但是功能是一样的
IP模块与包的收发
-
IP模块负责添加IP头部和MAC头部,然后传递给网卡发送出去
- 注意MAC头部也是由TCP/IP协议栈软件添加的,而不是网卡
-
IP头部
- 包含了IP消息长度,发送方和接受方IP地址,上层协议号等信息
- 例如TCP,UDP协议
- 包含了IP消息长度,发送方和接受方IP地址,上层协议号等信息
-
MAC头部
- 包含了接收方和发送方MAC地址,以及以太协议类型
- 例如IP协议,ARP协议
- 接收方MAC地址是通过IP地址使用ARP广播的方式得到的
- 因此接收方和发送方必须在同一子网中
- 包含了接收方和发送方MAC地址,以及以太协议类型
-
IP分片后,中间路由器是不会重组的,只有到目标主机处才重组
- 因为无法保证一个数据包分片后每个小包结果的路径是一样的
- 路由重组后还会再被分片
-
发送方网卡负责从IP模块获取包,并把包转换为电信号
- MAC模块添加报头以方便接收方的MAC模块察觉到包
- 在末尾添加FCS校验,接收方根据包的内容计算出FCS并与发送方的FCS比较,判断包是否损坏
- 由于TCP/UDP头部没有记录IP地址,所以接收方的TCP/UDP模块需要查看IP头部以确定应该把数据发送给哪个套接字
Chapter3: 从网线到网络设备
- 前两章的内容都是发生在计算机内部的,下面的内容展现了网络包在离开计算机后的传输过程
- 在子网中,计算机设备通过交换机连接,并由路由器负责连接外网
- 家用路由器集成了交换机
- 家用路由器集成了交换机
交换机
- 交换机和网卡的区别在与交换机没有MAC地址
- 如果让网卡接受所有网路包并通过软件转发,那么这台计算机就成了一个交换机
- 如果让网卡接受所有网路包并通过软件转发,那么这台计算机就成了一个交换机
- 当设备通过交换机发送过数据时,该设备MAC号自动加入交换机MAC表
- 因为设备可能会移动,所以MAC表需要定期删除
路由器
- 路由器由转发模块(相当于IP模块)和端口模块(相当于网卡)组成
- 路由器的各个端口都有有自己的MAC地址和IP地址
- 路由器的各个端口都有有自己的MAC地址和IP地址
- 路由表只匹配网络号,忽略主机号
- 即路由表目标地址一栏表示的是子网
- 交换机需要MAC地址完全一致才转发,而路由器只要匹配网络号即可
- 当然也可以匹配所有地址,此时该行表示一台计算机
- 有时路由表中的子网掩码和地址本事的子网掩码不同,这是路由聚合的结果
- 路由表中的interface表示端口IP地址,Gateway表示要转发到的IP地址
- 如果网关地址和端口地址相同,或者网关地址为空,则表示下一个地址即为目标地址,直接转发到IP头部的IP地址
- 第一行0.0.0.0为默认路由,所有地址都能匹配到这一行
Chapter4: 通过接入网进入互联网内部
- 光纤比电缆快的原因是什么?
- 光信号和电信号的传播速度是一样的
- 但是电信号在提高通信速率的同时衰减率会提高,会导致信号无法传到目的地
- 而光信号衰减率很低,可以尽量提高通信速率
Chapter5: 服务端的局域网中有什么玄机
防火墙
-
防火墙不会检查通信数据的内容,也就无法地域藏在通信数据中的攻击
-
防火墙的包过滤规则举例
- 行1允许外网访问指定服务器
- 行2禁止服务器主动访问外网
- 由于UDP不像TCP有建立连接的过程,因此无法只限制内网主动访问外网
- 所以一般的服务器禁止UDP流量,只允许访问外网DNS服务器的UDP流量
- 行3允许服务器应答外网
- 端口号限制服务器上的指定服务(如WEB服务)与外网通信
- 由于现在的防火墙也充当了连接外网的路由器,具有地址转换功能,因此无须设置禁止外网访问内网的规则
内容分发网络(CDN)
- 位于服务端的缓存服务器无法减少网络上的流量,也无法降低客户端延迟
- 位于客户端的缓存服务器不受服务端控制,对于新的数据无法缓存
- 可以把缓存服务器放在里客户端近的网络运营商处,这被称为CDN
- 现在的问题就在于如何找到离客户端最近的缓存服务器
- 方法一
- 客户端的本地DNS查找位于服务端的权威DNS服务器
- 权威DNS服务器根据本地DNS的IP地址确定其位置,并计算和所有缓存服务器距离
- 最后返回最近的缓存服务器的IP地址
- 这种方法的缺点在于客户端DNS和客户端不一定在一起
- 方法二
- 服务端DNS直接返回重定向服务器地址,客户端访问重定向服务器
- 重定向服务器直接根据客户端IP地址计算距离,返回缓存服务器IP地址
- 这种方法的优点在于客户位置精确,缺点在于多了一次请求过程
- 方法三
- 基于方法二,只不过服务端返回计算距离的脚本,由客户端计算最近缓存服务器
- 方法一
Chapter6: 请求到达Web服务器,响应返回浏览器
- web服务器一般通过80端口通信,也就是一个端口对应了很多套接字
- 所以为了确认客户端连接到了哪个套接字,需要通过服务器和客户端的IP和端口四元组确定
- 既然如此,为了还要通过描述符指代套接字呢,直接用四元组不就好了?
- 在套接字刚创立时,四元组信息还不全
- 描述符只有一种信息,更简洁
服务器接受包的过程
- 网卡
- 网卡监测、处理所有经过的包并校验,然后根据MAC头部决定接受不接受
- 此时CPU无感知,执行其他任务
- 接受后放入网卡缓冲区,并发出中断信号,让CPU根据MAC头部协议号交给对应的协议栈处理
- 网卡监测、处理所有经过的包并校验,然后根据MAC头部决定接受不接受
- IP模块
- 检查目标IP地址是否为自己
- 如果不是,普通服务器直接丢弃,路由器进行转发
- 检查是否分片,组装还原成原始包
- 检查头部协议号,交给上层处理
- 检查目标IP地址是否为自己
- TCP模块
- 对于连接包(STN位为1)
- 检查接收方端口号
- 把客户端IP,端口,序号初始值,窗口大小等写入对应套接字
- 这样套接字就保存了所有的通信状态
- 发送ACK号
- 对于数据包
- 找到对应套接字,检查序号
- 读取数据到缓冲区并拼接,等待应用程序读取
- 发送ACK号(可能会和后续应答包合并发送)
- 对于连接包(STN位为1)