搜尋
首頁後端開發Python教學总结网络IO模型与select模型的Python实例讲解

网络I/O模型
人多了,就会有问题。web刚出现的时候,光顾的人很少。近年来网络应用规模逐渐扩大,应用的架构也需要随之改变。C10k的问题,让工程师们需要思考服务的性能与应用的并发能力。

网络应用需要处理的无非就是两大类问题,网络I/O,数据计算。相对于后者,网络I/O的延迟,给应用带来的性能瓶颈大于后者。网络I/O的模型大致有如下几种:

  • 同步模型(synchronous I/O)
  • 阻塞I/O(bloking I/O)
  • 非阻塞I/O(non-blocking I/O)
  • 多路复用I/O(multiplexing I/O)
  • 信号驱动式I/O(signal-driven I/O)
  • 异步I/O(asynchronous I/O)

网络I/O的本质是socket的读取,socket在linux系统被抽象为流,I/O可以理解为对流的操作。这个操作又分为两个阶段:

等待流数据准备(wating for the data to be ready)。
从内核向进程复制数据(copying the data from the kernel to the process)。
对于socket流而已,

第一步通常涉及等待网络上的数据分组到达,然后被复制到内核的某个缓冲区。
第二步把数据从内核缓冲区复制到应用进程缓冲区。
I/O模型:
举个简单比喻,来了解这几种模型。网络IO好比钓鱼,等待鱼上钩就是网络中等待数据准备好的过程,鱼上钩了,把鱼拉上岸就是内核复制数据阶段。钓鱼的人就是一个应用进程。

阻塞I/O(bloking I/O)
阻塞I/O是最流行的I/O模型。它符合人们最常见的思考逻辑。阻塞就是进程 "被" 休息, CPU处理其它进程去了。在网络I/O的时候,进程发起recvform系统调用,然后进程就被阻塞了,什么也不干,直到数据准备好,并且将数据从内核复制到用户进程,最后进程再处理数据,在等待数据到处理数据的两个阶段,整个进程都被阻塞。不能处理别的网络I/O。大致如下图:

2016627164407309.jpg (686×379)

这就好比我们去钓鱼,抛竿之后就一直在岸边等,直到等待鱼上钩。然后再一次抛竿,等待下一条鱼上钩,等待的时候,什么事情也不做,大概会胡思乱想吧。

阻塞IO的特点就是在IO执行的两个阶段都被block了
非阻塞I/O(non-bloking I/O)
在网络I/O时候,非阻塞I/O也会进行recvform系统调用,检查数据是否准备好,与阻塞I/O不一样,"非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 '被' CPU光顾"。

也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。

2016627164443436.jpg (780×410)

我们再用钓鱼的方式来类别,当我们抛竿入水之后,就看下鱼漂是否有动静,如果没有鱼上钩,就去干点别的事情,比如再挖几条蚯蚓。然后不久又来看看鱼漂是否有鱼上钩。这样往返的检查又离开,直到鱼上钩,再进行处理。

非阻塞 IO的特点是用户进程需要不断的主动询问kernel数据是否准备好。
多路复用I/O(multiplexing I/O)
可以看出,由于非阻塞的调用,轮询占据了很大一部分过程,轮询会消耗大量的CPU时间。结合前面两种模式。如果轮询不是进程的用户态,而是有人帮忙就好了。多路复用正好处理这样的问题。

多路复用有两个特别的系统调用select或poll。select调用是内核级别的,select轮询相对非阻塞的轮询的区别在于---前者可以等待多个socket,当其中任何一个socket的数据准好了,就能返回进行可读,然后进程再进行recvform系统调用,将数据由内核拷贝到用户进程,当然这个过程是阻塞的。多路复用有两种阻塞,select或poll调用之后,会阻塞进程,与第一种阻塞不同在于,此时的select不是等到socket数据全部到达再处理, 而是有了一部分数据就会调用用户进程来处理。如何知道有一部分数据到达了呢?监视的事情交给了内核,内核负责数据到达的处理。也可以理解为"非阻塞"吧。

2016627164501914.jpg (812×411)

对于多路复用,也就是轮询多个socket。钓鱼的时候,我们雇了一个帮手,他可以同时抛下多个钓鱼竿,任何一杆的鱼一上钩,他就会拉杆。他只负责帮我们钓鱼,并不会帮我们处理,所以我们还得在一帮等着,等他把收杆。我们再处理鱼。多路复用既然可以处理多个I/O,也就带来了新的问题,多个I/O之间的顺序变得不确定了,当然也可以针对不同的编号。

多路复用的特点是通过一种机制一个进程能同时等待IO文件描述符,内核监视这些文件描述符(套接字描述符),其中的任意一个进入读就绪状态,select, poll,epoll函数就可以返回。对于监视的方式,又可以分为 select, poll, epoll三种方式。
了解了前面三种模式,在用户进程进行系统调用的时候,他们在等待数据到来的时候,处理的方式不一样,直接等待,轮询,select或poll轮询,第一个过程有的阻塞,有的不阻塞,有的可以阻塞又可以不阻塞。当时第二个过程都是阻塞的。从整个I/O过程来看,他们都是顺序执行的,因此可以归为同步模型(asynchronous)。都是进程主动向内核检查。

异步I/O(asynchronous I/O)
相对于同步I/O,异步I/O不是顺序执行。用户进程进行aio_read系统调用之后,无论内核数据是否准备好,都会直接返回给用户进程,然后用户态进程可以去做别的事情。等到socket数据准备好了,内核直接复制数据给进程,然后从内核向进程发送通知。I/O两个阶段,进程都是非阻塞的。

2016627164611582.jpg (764×473)

比之前的钓鱼方式不一样,这一次我们雇了一个钓鱼高手。他不仅会钓鱼,还会在鱼上钩之后给我们发短信,通知我们鱼已经准备好了。我们只要委托他去抛竿,然后就能跑去干别的事情了,直到他的短信。我们再回来处理已经上岸的鱼。

同步和异步的区别
通过对上述几种模型的讨论,需要区分阻塞和非阻塞,同步和异步。他们其实是两组概念。区别前一组比较容易,后一种往往容易和前面混合。在我看来,所谓同步就是在整个I/O过程。尤其是拷贝数据的过程是阻塞进程的,并且都是应用进程态去检查内核态。而异步则是整个过程I/O过程用户进程都是非阻塞的,并且当拷贝数据的时是由内核发送通知给用户进程。

2016627164629348.jpg (868×463)

对于同步模型,主要是第一阶段处理方法不一样。而异步模型,两个阶段都不一样。这里我们忽略了信号驱动模式。这几个名词还是容易让人迷惑,只有同步模型才考虑阻塞和非阻塞,因为异步肯定是非阻塞,异步非阻塞的说法感觉画蛇添足。


Select 模型
同步模型中,使用多路复用I/O可以提高服务器的性能。
在多路复用的模型中,比较常用的有select模型和poll模型。这两个都是系统接口,由操作系统提供。当然,Python的select模块进行了更高级的封装。select与poll的底层原理都差不多。千呼万唤始出来,本文的重点select模型。
1.select 原理
网络通信被Unix系统抽象为文件的读写,通常是一个设备,由设备驱动程序提供,驱动可以知道自身的数据是否可用。支持阻塞操作的设备驱动通常会实现一组自身的等待队列,如读/写等待队列用于支持上层(用户层)所需的block或non-block操作。设备的文件的资源如果可用(可读或者可写)则会通知进程,反之则会让进程睡眠,等到数据到来可用的时候,再唤醒进程。

这些设备的文件描述符被放在一个数组中,然后select调用的时候遍历这个数组,如果对于的文件描述符可读则会返回改文件描述符。当遍历结束之后,如果仍然没有一个可用设备文件描述符,select让用户进程则会睡眠,直到等待资源可用的时候在唤醒,遍历之前那个监视的数组。每次遍历都是线性的。

2.select 回显服务器
select涉及系统调用和操作系统相关的知识,因此单从字面上理解其原理还是比较乏味。用代码来演示最好不过了。使用python的select模块很容易写出下面一个回显服务器:

import select
import socket
import sys

HOST = 'localhost'
PORT = 5000
BUFFER_SIZE = 1024

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)

inputs = [server, sys.stdin]
running = True

while True:
  try:
    # 调用 select 函数,阻塞等待
    readable, writeable, exceptional = select.select(inputs, [], [])
  except select.error, e:
    break

  # 数据抵达,循环
  for sock in readable:
    # 建立连接
    if sock == server:
      conn, addr = server.accept()
      # select 监听的socket
      inputs.append(conn)
    elif sock == sys.stdin:
      junk = sys.stdin.readlines()
      running = False
    else:
      try:
        # 读取客户端连接发送的数据
        data = sock.recv(BUFFER_SIZE)
        if data:
          sock.send(data)
          if data.endswith('\r\n\r\n'):
            # 移除select监听的socket
            inputs.remove(sock)
            sock.close()
        else:
          # 移除select监听的socket
          inputs.remove(sock)
          sock.close()
      except socket.error, e:
        inputs.remove(sock)

server.close()

运行上述代码,使用curl访问http://localhost:5000,即可看命令行返回请求的HTTP request信息。

下面详细解析上述代码的原理。

server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((HOST, PORT))
server.listen(5)

上述代码使用socket初始化一个TCP套接字,并绑定主机地址和端口,然后设置服务器监听。

inputs = [server, sys.stdin]

这里定义了一个需要select监听的列表,列表里面是需要监听的对象(等于系统监听的文件描述符)。这里监听socket套接字和用户的输入。

然后代码进行一个服务器无线循环。

try:
  # 调用 select 函数,阻塞等待
  readable, writeable, exceptional = select.select(inputs, [], [])
except select.error, e:
  break

调用了select函数,开始循环遍历监听传入的列表inputs。如果没有curl服务器,此时没有建立tcp客户端连接,因此改列表内的对象都是数据资源不可用。因此select阻塞不返回。

客户端输入curl http://localhost:5000之后,一个套接字通信开始,此时input中的第一个对象server由不可用变成可用。因此select函数调用返回,此时的readable有一个套接字对象(文件描述符可读)。

for sock in readable:
  # 建立连接
  if sock == server:
    conn, addr = server.accept()
    # select 监听的socket
    inputs.append(conn)

select返回之后,接下来遍历可读的文件对象,此时的可读中只有一个套接字连接,调用套接字的accept()方法建立TCP三次握手的连接,然后把该连接对象追加到inputs监视列表中,表示我们要监视该连接是否有数据IO操作。

由于此时readable只有一个可用的对象,因此遍历结束。再回到主循环,再次调用select,此时调用的时候,不仅会遍历监视是否有新的连接需要建立,还是监视刚才追加的连接。如果curl的数据到了,select再返回到readable,此时在进行for循环。如果没有新的套接字,将会执行下面的代码:

try:
  # 读取客户端连接发送的数据
  data = sock.recv(BUFFER_SIZE)
  if data:
    sock.send(data)
    if data.endswith('\r\n\r\n'):
      # 移除select监听的socket
      inputs.remove(sock)
      sock.close()
  else:
    # 移除select监听的socket
    inputs.remove(sock)
    sock.close()
except socket.error, e:
  inputs.remove(sock)

通过套接字连接调用recv函数,获取客户端发送的数据,当数据传输完毕,再把监视的inputs列表中除去该连接。然后关闭连接。

整个网络交互过程就是如此,当然这里如果用户在命令行中输入中断,inputs列表中监视的sys.stdin也会让select返回,最后也会执行下面的代码:

elif sock == sys.stdin:
  junk = sys.stdin.readlines()
  running = False

有人可能有疑问,在程序处理sock连接的是时候,假设又输入了curl对服务器请求,将会怎么办?此时毫无疑问,inputs里面的server套接字会变成可用。等现在的for循环处理完毕,此时select调用就会返回server。如果inputs里面还有上一个过程的conn连接,那么也会循环遍历inputs的时候,再一次针对新的套接字accept到inputs列表进行监视,然后继续循环处理之前的conn连接。如此有条不紊的进行,直到for循环结束,进入主循环调用select。

任何时候,inputs监听的对象有数据,下一次调用select的时候,就会繁返回readable,只要返回,就会对readable进行for循环,直到for循环结束在进行下一次select。

主要注意,套接字建立连接是一次IO,连接的数据抵达也是一次IO。

3.select的不足
尽管select用起来挺爽,跨平台的特性。但是select还是存在一些问题。
select需要遍历监视的文件描述符,并且这个描述符的数组还有最大的限制。随着文件描述符数量的增长,用户态和内核的地址空间的复制所引发的开销也会线性增长。即使监视的文件描述符长时间不活跃了,select还是会线性扫描。

为了解决这些问题,操作系统又提供了poll方案,但是poll的模型和select大致相当,只是改变了一些限制。目前Linux最先进的方式是epoll模型。

许多高性能的软件如nginx, nodejs都是基于epoll进行的异步。

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
Python的socket与socketserver怎么使用Python的socket与socketserver怎么使用May 28, 2023 pm 08:10 PM

一、基于TCP协议的socket套接字编程1、套接字工作流程先从服务器端说起。服务器端先初始化Socket,然后与端口绑定(bind),对端口进行监听(listen),调用accept阻塞,等待客户端连接。在这时如果有个客户端初始化一个Socket,然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束,使用以下Python代码实现:importso

怎么使用Spring Boot+Vue实现Socket通知推送怎么使用Spring Boot+Vue实现Socket通知推送May 27, 2023 am 08:47 AM

SpringBoot端第一步,引入依赖首先我们需要引入WebSocket所需的依赖,以及处理输出格式的依赖com.alibabafastjson1.2.73org.springframework.bootspring-boot-starter-websocket第二步,创建WebSocket配置类importorg.springframework.context.annotation.Bean;importorg.springframework.context.annotation.Config

详细讲解Python之Seaborn(数据可视化)详细讲解Python之Seaborn(数据可视化)Apr 21, 2022 pm 06:08 PM

本篇文章给大家带来了关于Python的相关知识,其中主要介绍了关于Seaborn的相关问题,包括了数据可视化处理的散点图、折线图、条形图等等内容,下面一起来看一下,希望对大家有帮助。

PHP+Socket系列之IO多路复用及实现web服务器PHP+Socket系列之IO多路复用及实现web服务器Feb 02, 2023 pm 01:43 PM

本篇文章给大家带来了关于php+socket的相关知识,其中主要介绍了IO多路复用,以及php+socket如何实现web服务器?感兴趣的朋友下面一起来看一下,希望对大家有帮助。

php socket无法连接怎么办php socket无法连接怎么办Nov 09, 2022 am 10:34 AM

php socket无法连接的解决办法:1、检查php是否开启socket扩展;2、打开php.ini文件,检查“php_sockets.dll”是否被加载;3、取消“php_sockets.dll”的注释状态即可。

PHP+Socket系列之实现客户端与服务端数据传输PHP+Socket系列之实现客户端与服务端数据传输Feb 02, 2023 am 11:35 AM

本篇文章给大家带来了关于php+socket的相关知识,其中主要介绍了什么是socket?php+socket如何实现客户端与服务端数据传输?感兴趣的朋友下面一起来看一下,希望对大家有帮助。

利用PHP和Socket实现实时文件传输技术研究利用PHP和Socket实现实时文件传输技术研究Jun 28, 2023 am 09:11 AM

随着互联网的发展,文件传输成为人们日常工作和娱乐中不可或缺的一部分。然而,传统的文件传输方式如邮件附件或文件分享网站存在一定的限制,无法满足实时性和安全性的需求。因此,利用PHP和Socket技术实现实时文件传输成为了一种新的解决方案。本文将介绍利用PHP和Socket技术实现实时文件传输的技术原理、优点和应用场景,并通过具体案例来演示该技术的实现方法。技术

C#中常见的网络通信和安全性问题及解决方法C#中常见的网络通信和安全性问题及解决方法Oct 09, 2023 pm 09:21 PM

C#中常见的网络通信和安全性问题及解决方法在当今互联网时代,网络通信已经成为了软件开发中必不可少的一部分。在C#中,我们通常会遇到一些网络通信的问题,例如数据传输的安全性、网络连接的稳定性等。本文将针对C#中常见的网络通信和安全性问题进行详细讨论,并提供相应的解决方法和代码示例。一、网络通信问题网络连接中断:网络通信过程中,可能会出现网络连接的中断,这会导致

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
2 週前By尊渡假赌尊渡假赌尊渡假赌
倉庫:如何復興隊友
4 週前By尊渡假赌尊渡假赌尊渡假赌
Hello Kitty Island冒險:如何獲得巨型種子
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

SAP NetWeaver Server Adapter for Eclipse

SAP NetWeaver Server Adapter for Eclipse

將Eclipse與SAP NetWeaver應用伺服器整合。

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具