网络编程

Socket Programming

基本概念梳理

  • IP:计算机网络的唯一标识。

NetFilter机制

Linux 系统提供的许多网络能力,如数据包过滤、封包处理(设置标志位、修改 TTL 等)、地址伪装、网络地址转换、透明代理、访问控制、基于协议类型的连接跟踪,带宽限速,等等,都是在 Netfilter 基础之上实现的。围绕网络层(IP 协议)的周围,埋下了五种Hook,每当有数据包流到网络层,经过这些钩子时,就会自动触发由内核模块注册在这里的回调函数(可以配置多个),程序代码就能够通过回调来干预 Linux 的网络通信。钩子有:

image.png
  • PREROUTING:来自设备的数据包进入协议栈后立即触发此钩子。在进入 IP 路由之前触发,这意味着只要接收到的数据包,无论是否真的发往本机,都会触发此钩子。一般用于目标网络地址转换(Destination NAT,DNAT)。

  • INPUT:报文经过 IP 路由后,如果确定是发往本机的,将会触发此钩子,一般用于加工发往本地进程的数据包。

  • FORWARD:报文经过 IP 路由后,如果确定是发往本机的,将会触发此钩子,一般用于处理转发到其他机器的数据包。

  • OUTPUT:从本机程序发出的数据包,在经过 IP 路由前,将会触发此钩子,一般用于加工本地进程的输出数据包。

  • POSTROUTING:从本机网卡出去的数据包,无论是本机的程序所发出的,还是由本机转发给其他机器的,都会触发此钩子,一般用于源网络地址转换(Source NAT,SNAT)。

Reactor 模型

主要分为三个角色:

  • Reactor:把 IO 事件分配给对应的 handler 处理

  • Acceptor:处理客户端连接事件

  • Handler:处理非阻塞的任务

异步情况下(Proactor),当回调handler时,表示I/O操作已经完成;同步情况下(Reactor),回调handler时,表示I/O设备可以进行某个操作(can read 或 can write)。

C语言

image.png

连接属性

keep-alive

  • TCP保活机制。其可以一定时间间隔发送探测报文轮询客户端,若连续几个报文无响应,可判定连接结束。默认关闭。在此不多介绍。

  • 也是应用层的机制。HTTP1.1默认开启,机制参考xv6的ping-pong

HTTP 长连接:只要任意一端没有明确提出断开连接,则保持 TCP 连接状态。 (短连接:请求-响应一次后就断开)

IO 多路复用

只使用一个进程来维护多个 Socket, 进程可以通过一个系统调用函数从内核中获取多个事件

事件轮询 API 就是 Java 语言里面的 NIO 技术。

select

select 是最简单的事件轮询API。输入是读写文件描述符集合 read_fds & write_fds,输出是与之对应的可读可写事件。同时还提供了一个 timeout 参数,如果没有任何事件到来,那么就最多阻塞等待 timeout 时间。(如果timeout 设置为 0,select将一直阻塞到某个文件描述符上发生了事件)

比如在网络编程中,可将已连接的 Socket 都放到一个文件描述符集合,调用 select 函数将文件描述符集合拷贝到内核里,让内核来检查是否有网络事件产生,检查的方式很粗暴,就是通过遍历检查文件描述符集合的方式,对有事件产生的 Socket 标记为可读或可写, 接着再把整个文件描述符集合拷贝回用户态里,然后用户态再通过遍历的方法找到可读或可写的 Socket,然后再对其处理。

poll

poll 不像select用 BitMap 来存储所关注的文件描述符,取而代之用动态数组,以链表形式来组织,突破了 select 的文件描述符个数(FD_SETSIZE,默认1024)限制,而是受到系统最大文件描述符限制。二者都是使用「线性结构」存储进程关注的 Socket 集合,因此都需要遍历文件描述符集合来找到可读或可写的 Socket,时间复杂度为 O(n),而且也需要在用户态与内核态之间拷贝文件描述符集合

epoll

epoll 通过两个方面,很好解决了 select/poll 的问题。

  1. epoll 在内核里使用红黑树来跟踪进程所有待检测的文件描述符,把需要监控的 socket 通过 epoll_ctl() 函数加入内核中的红黑树里,这样就不需要像 select/poll 每次操作时都传入整个 socket 集合,只需要传入一个待检测的 socket,减少了内核和用户空间大量的数据拷贝和内存分配。

  2. epoll 使用事件驱动的机制,内核里维护了一个链表来记录就绪事件,当某个 socket 有事件发生时,通过回调函数内核会将其加入到这个就绪事件列表中,当用户调用 epoll_wait() 函数时,只会返回有事件发生的文件描述符的个数,不需要像 select/poll 那样轮询扫描整个 socket 集合,大大提高了检测的效率。

epoll 支持两种事件触发模式,分别是边缘触发(edge-triggered,ET)和 水平触发(level-triggered,LT,默认)。一般来说,边缘触发的效率比水平触发的效率要高,因为边缘触发可以减少 epoll_wait 的系统调用次数。

  • 使用边缘触发模式时,当被监控的 Socket 描述符上有可读事件发生时,服务器端只会从 epoll_wait 中苏醒一次一般和非阻塞 I/O 搭配使用。因为进程只苏醒一次,需要保证一次性将内核缓冲区的数据读取完,可能会循环从文件描述符读写数据;

  • 使用水平触发模式时(默认),当被监控的 Socket 上有可读事件发生时,服务器端不断地从 epoll_wait 中苏醒,直到内核缓冲区数据被 read 函数读完才结束,目的是告诉我们有数据需要读取。

结合IO多路复用技术,又因为 Redis 的大部分操作都在内存中完成,且无线程竞争造成的性能损失,Redis的QPS吞吐量可达到10万/秒,是MySQL的十倍。在 Redis 6.0 版本之后,也采用了多个 I/O 线程来处理网络请求这是因为随着网络硬件的性能提升,Redis 的性能瓶颈有时会出现在网络 I/O 的处理上

性能监控

通常是以 4 个指标来衡量网络的性能,分别是带宽、延时、吞吐率、PPS(Packet Per Second),它们表示的意义如下:

  • 带宽,表示链路的最大传输速率,单位是 b/s (比特 / 秒),带宽越大,其传输能力就越强。

  • 延时,表示请求数据包发送后,收到对端响应,所需要的时间延迟。不同的场景有着不同的含义,比如可以表示建立 TCP 连接所需的时间延迟,或一个数据包往返所需的时间延迟。

  • 吞吐率,表示单位时间内成功传输的数据量,单位是 b/s 或 B/s,吞吐受带宽限制,带宽越大,吞吐率的上限才可能越高。

  • PPS,全称是 Packet Per Second(包 / 秒),表示以网络包为单位的传输速率,一般用来评估系统对于网络的转发能力。

要想知道网络的配置和状态,我们可以使用 ifconfig 或者 ip 命令来查看,得到 IP 地址、子网掩码、MAC 地址、网关地址、MTU 大小、网口的状态以及网络包收发的统计信息等。

我们可以使用 netstat 或者 ss(推荐后者),这两个命令查看 socket、网络协议栈、网口以及路由表的信息,得到 socket 的状态(State)、接收队列(Recv-Q)、发送队列(Send-Q)、本地地址(Local Address)、远端地址(Foreign Address)、进程 PID 和进程名称(PID/Program name)等。其中:

  • 当 socket 状态处于 Established时:

    • Recv-Q 表示 socket 缓冲区中还没有被应用程序读取的字节数;

    • Send-Q 表示 socket 缓冲区中还没有被远端主机确认的字节数。

  • 当 socket 状态处于 Listen 时:

    • Recv-Q 表示全连接队列的长度;

    • Send-Q 表示全连接队列的最大长度。

可以使用 sar 命令当前网络的吞吐率和 PPS。


参考:小林coding (xiaolincoding.com)

最后更新于

这有帮助吗?