深入理解计算机网络(3)

138 阅读10分钟

IP协议

IP协议是网络层协议,主要负责寻址,路由,分片。

  • 寻址:设备之间根据IP地址寻址,IP地址又分为网络号和主机号(无分类地址CIDR)。
    • 网络号用于标识该IP地址属于哪个子网,主机号负责标识同一个子网下的不同主机。
    • 使用子网掩码可计算出IP地址的网络号和主机号,或在IP地址后使用/直接标注网络号的位数。另外,子网掩码还可以用来划分子网。
    • 注意IP地址不是根据主机台数来配置的,而是根据网卡,路由器设备拥有两个以上的网卡。
    • IP地址全1地址表示广播,全0地址表示本网络。IPV4地址有32位,IPV6地址有128位。
  • 路由:实际场景中两台设备通过很多路由器,交换机等设备连接,会形成很多路径,当数据包到达一个网络节点,需要通过路由表和路由算法决定下一步怎么走。
    • 路由表中记录目标IP地址与下一跳路由器地址,IP包到达时到路由器根据路由算法和路由表计算出下一跳地址,然后转发给对应路由器。
    • 动态路由算法主要包括距离向量算法(RIP,BGP,与邻居路由器交换路由信息)和链路状态算法(OSPF,定期传播链路信息给所有路由器)
  • 分片:若IP报文超过MTU(1500字节)就会进行分片

IP报文头部

image.png

主要包括首部长度,总长度,标识,标志位,片偏移,TTL,源IP地址,目标IP地址。其中标志位表示是否能分片,后面还有无分片;片偏移表示当前报文在整组分片包中的位置;标识表示当前报文属于哪个分组。

IP层相关协议

  • DHCP协议:为主机动态分配IP地址。
    • 客户端先发起DHCP发现报文,服务器响应DHCP响应报文,客户端再发送DHCP请求报文,最后服务端用DHCP ACK报文应答。此后客户端就能够使用DHCP服务器分配给他的IP地址。在整个DHCP交互过程中都使用UDP广播通信。
  • NAT协议:网络地址转换,将私有IP转换为公有IP
    • 普通的NAT相当于N个私有地址对应N个公有地址,没什么意义,但是NAPT网络地址与端口转换技术则将IP地址和端口一起进行转换,即两个私有IP地址不同,但是端口号相同,NAT将其转化为一个相同的公有地址,但是以不同端口号做区分。
  • ICMP协议:确认IP包是否成功到达目标地址,报告发送过程中IP包被丢弃的原因等。分为查询报文类型和差错报文类型。是ping命令底层实现。

ping的工作原理

源主机ping目标主机

  • 源主机先构建一个ICMP回送请求消息数据包(类型,序号,时间)
  • 将数据包交给IP层构建一个IP数据包(源IP地址,目标IP地址)再加上MAC头发出去
  • 目标主机收到后,先检查MAC地址,再交给IP层,提取出ICMP消息后交给ICMP协议
  • 目标主机构建ICMP回送响应消息包(类型,序号,时间)再发送给源主机
  • 如果规定时间内源主机没收到ICMP应答包则说明目标主机不可达,否则可达

另外,断网了也能ping通127.0.0.1,因为这是回环地址,都不会从网卡发出去。而ping localhost会将localhost解析为回环地址(域名解析),ping 0.0.0.0会失败,因为这是无效目标地址,表示广播。

MAC协议

MAC协议是数据链路层的协议,它会将数据封装成帧,并定义了如何在两个物理节点间传递数据。MAC地址是全球唯一的,通常被硬编码在网络接口卡(NIC)上。两个节点发送数据时,发送方通过ARP协议寻找接收方的MAC地址,ARP协议以广播发送,若对方与自己同处于一个子网中对方会直接回应(ARP中有缓存,不用每次都广播),若目标路由器不在本子网内,则由本子网内的路由器发送出去直到目标路由器。

Linux收发网络包

收包

Linux接收网络的数据包分为两个过程:1.数据准备阶段,2.数据拷贝阶段

  • 数据准备阶段
    • 网卡收到数据包,通过DMA技术将数据写到内存的环形缓冲区。(DMA技术指无需CPU干预实现IO设备与存储器之间批量数据传送)
    • 网卡向CPU发起硬中断(暂时屏蔽中断,发起软中断,恢复屏蔽的中断)
    • 内核线程ksoftirqd收到软中断后,从环形缓冲区获取一个数据帧sk_buff,sk_buff负责在网络协议栈中对数据包逐层处理(通过data指针移动)
    • 根据IP地址和端口号将数据放到Socket的接收缓冲区
  • 数据拷贝阶段
    • 应用层程序调用Socket接口,将内核的Socket接收缓冲区的数据拷贝到应用层缓冲区(内核态到用户态),然后唤醒用户进程。

发包

  • 数据拷贝阶段
    • 应用程序调用socket发送数据包的接口,从用户态陷入内核态的socket层,内核申请一个sk_buff内存,将要发送的数据拷贝到sk_buff内存,将其加入socket发送缓冲区。
  • 数据准备阶段
    • 网络协议栈从socket发送缓冲区取出sk_buff,移动data指针逐层加上包头(这样逐层传递时不用拷贝),注意如果是TCP还会拷贝一个sk_buff副本(需要重传时使用,收到ACK后才删除)
    • 将sk_buff放到网络设备子系统(位于网络层与链路层之间的一个系统)的发送队列,触发软中断
    • 网卡驱动从发送队列中读取sk_buff,将其放入内存环形缓冲区(网卡驱动中),再将数据映射到网卡可访问的DMA区域,网卡发送数据,最后网卡触发硬中断释放内存(主要是sk_buff和环形缓冲区)

不同的IO模式

  • 基本概念:

    1. Linux收数据包分为两个阶段:1.数据准备阶段,2.数据拷贝(读写)阶段。
    2. 阻塞指的是数据准备阶段阻塞,同步指的是数据读写阶段是否是同步操作(由线程自己完成拷贝操作,也表现为阻塞)。
    3. Socket对象也是一个文件,Linux通过文件描述符fd对其操作
    4. Socket对象包括发送缓冲区,接收缓冲区,等待队列。
    5. OS会将IO阻塞的进程(的引用)(或与进程有关的对象)挂在对应socket的等待队列中,此时进程进入阻塞态
    6. socket准备好数据后,OS将socket等待队列上的进程唤醒并重新放回OS的工作队列(用于进程调度)
    7. Unix的IO模型与Java的网络IO模型概念并不是一一对应,但是有相通之处,以下按照Java的网络IO模型来分类
  • 同步阻塞IO(BIO)

    • 数据准备阶段阻塞和数据读写阶段都会阻塞
    • 线程读取数据时,如果当前fd的数据没有准备好,线程会阻塞等待。 所以如果服务端使用单线程,则每次只能处理一个请求。如果服务端使用多线程,则需要对每个请求开启一个线程处理
  • 同步非阻塞(NIO)

    • 数据准备阶段非阻塞,数据读写阶段阻塞
    • NIO时,服务端将accept返回的fd都加入fds集合,每次轮询fds,看哪些fd准备好了数据就返回。由于没有读写事件的fd也要轮询,所以效率不高。故引出了IO多路复用的方式。
  • IO多路复用

    • IO多路复用实际上也属于NIO(因为数据读写阶段也会阻塞)。IO多路复用指服务端使用单线程通过select/epoll等系统调用获取fds中有读写事件的fd(避免轮询所有fd),对这些fd做读写操作。IO多路复用有select,poll,epoll三种实现方式。
    • select
      1. 将已连接的socket存储为一个文件描述符集合fds中
      2. 调用select函数将fds拷贝到内核中,让内核遍历fds检查哪个fd有读写事件发生并做好标记,再将fds拷贝回用户态
      3. 用户态再次遍历fds找到可读可写的socket对其进行处理。
      4. 整个过程需要两次拷贝和两次遍历,所以效率不高。
    • poll
      • 与select相比只是没有fd个数的限制,其他都一样
    • epoll
      1. 进程调用epoll_create方法创建一个eventepoll对象(作为进程与socket的中介)。eventepoll对象中包括等待队列,就绪队列(rdlist),红黑树。
        • 红黑树跟踪进程所有待检测的文件描述符,即把监控的socket通过epoll_ctl函数加入红黑树(select只能每次把socket集合拷贝给内核)
        • 等待队列保存阻塞在epoll函数上的用户进程
        • 就绪队列保存就绪的socket,进程被唤醒后可以直接遍历就绪队列获得有事件的socket。
      2. 使用epoll_ctl添加要监听的socket到红黑树中,内核会将一个等待事件对象(即epitem)添加到监听的socket的等待队列中(并注册回调函数)。
        • epitem中保存了socekt的fd,并且保存了rdlist和红黑树中的socket的引用
      3. socket收到数据后通过回调函数通知eventpoll对象(而不直接操作进程),把对应socket引用插入rdlist。
      4. 当进程执行epoll_wait,若rdlist中有socket则直接返回,否则阻塞进程(将进程放入eventepoll等待队列,而不是将进程放入所有socket的等待队列)。
    • epoll比select好的地方在于:
      • 使用rdlist存放就绪的socket,进程可以直接知道哪个socket有数据,而不用遍历
      • 使用了红黑树便于快速找到有事件的socket
      • 阻塞时只将进程放入eventepoll的等待队列中,不用放到所有socket的等待队列中(唤醒同理),socket方面通过它自己等待队列中的epitem与epollevent联系(通过回调函数)
    • epoll的LT模式与ET模式
      • epoll有LT和ET两种触发模式,LT即水平触发(默认),ET是边缘触发。
      • LT模式下,只要这个fd还有数据可读,每次epoll_wait都会返回它的事件,提醒用户程序去操作。
      • ET模式下,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无论fd中是否还有数据可读。 所以在ET模式下,read一个fd的时候一定要把它的buffer读完,或者遇到EAGAIN错误
    • epoll的应用:redis,nginx

image.png

  • 异步IO(AIO)
    • 数据准备阶段和数据读写阶段都不阻塞
    • 所谓异步是指线程需要读取数据时,通知内核去操作,内核将数据拷贝到线程的缓冲区(用户态),在此期间线程可以去做其他事,拷贝完成后内核通知线程。异步一般是调用内核中的aio接口。