select浅析

一、sellect、poll、epoll三者的区别

select

select最早于1983年出现在4.2BSD中,它通过一个select()系统调用来监视多个文件描述符的数组,当select()返回后,该数组中就绪的文件描述符便会被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操作。

select目前几乎在所有的平台上支持,其良好跨平台支持也是它的一个优点,事实上从现在看来,这也是它所剩不多的优点之一。

select的一个缺点在于单个进程能够监视的文件描述符的数量存在最大限制,在Linux上一般为1024,不过可以通过修改宏定义甚至重新编译内核的方式提升这一限制。

另外,select()所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制的开销也线性增长。同时,由于网络响应时间的延迟使得大量TCP连接处于非活跃状态,但调用select()会对所有socket进行一次线性扫描,所以这也浪费了一定的开销。

poll

poll在1986年诞生于System V Release 3,它和select在本质上没有多大差别,但是poll没有最大文件描述符数量的限制。

poll和select同样存在一个缺点就是,包含大量文件描述符的数组被整体复制于用户态和内核的地址空间之间,而不论这些文件描述符是否就绪,它的开销随着文件描述符数量的增加而线性增大。

另外,select()和poll()将就绪的文件描述符告诉进程后,如果进程没有对其进行IO操作,那么下次调用select()和poll()的时候将再次报告这些文件描述符,所以它们一般不会丢失就绪的消息,这种方式称为水平触发(Level Triggered)。

epoll

直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。

epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂。

epoll同样只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调用时复制的开销。

另一个本质的改进在于epoll采用基于事件的就绪通知方式。在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

二、select基本流程

Python的select()方法能够直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成readable 和writeable, 或者出现通信错误(exceptional),select使同时监视多个连接变得简单,也比使用socket timouts写一个轮询来监视更加高效,因为select的监视发生在操作系统的网络层,而不是通过解释器。

下面使用echo server的例子简要了解select如何通过单进程实现同时处理多个非阻塞的socket连接:

服务器端:

首先,建立一个非阻塞的TCP/IP连接,并配置监听地址和端口。

import select
import socket
import sys
import Queue

# Create a TCP/IP socket
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0) #如果不设置这个,那么多余的请求会一直处于等待状态,设置了setblocking之后,相当于排好顺序来等待

# Bind the socket to the port
server_address = (‘localhost‘, 10000)
print >>sys.stderr, ‘starting up on %s port %s‘ % server_address
server.bind(server_address)

# Listen for incoming connections
server.listen(5)

select方法监控3个包含联络通道的列表,第一个是要被读取的输入数据(incoming data to be read)的对象信息,第二个是可以接收server端输出数据的对象信息,第三个是监控通信错误的对象信息(通常是前两个的组合)。

接下来创建2个要传递给select的包含输入源和输出目标信息的列表。

# Sockets from which we expect to read
inputs = [ server ]

# Sockets to which we expect to write
outputs = [ ]

所有客户端的进来的连接和数据将会被server的主循环程序放在上面的list中处理,进行列表的添加或删除。我们的server端一直等待直到某个socket变为可写的(writable),才会向外发送数据(而不是立刻发送回应),输出的数据都要先被放到一个用作缓存的queue中,然后又select取出来,再发送给客户端。

# Outgoing message queues (socket:Queue)
message_queues = {}

下面是程序的主循环。select方法接收inputs、outputs、excetional(这里使用inputs代替)列表,返回3个新的列表,并分别赋给readable、writable、exceptional。

其中,readable中的socket连接代表可以被读取,writable中的socket代表可以向其写入数据,exceptional中接收的是通信过程中放生了错误的socket(实际的exceptional 情况视平台而定)。

Readable中的socket可能有3中不同的情况:

第一种情况,socket是main “server” socket,它是被用来监听连接的,如果它出现在readable中,代表它一直阻塞并监听,等待客户端的请求;同时,只要有新的连接请求,就添加新的socket到inputs中进行监听,这样,实现了客户端socket的非阻塞。

# Handle inputs
for s in readable:
    if s is server:
        # A "readable" server socket is ready to accept a connection
        connection, client_address = s.accept()
        print >>sys.stderr, ‘new connection from‘, client_address
        connection.setblocking(0)
        inputs.append(connection)

        # Give the connection a queue for data we want to send
        message_queues[connection] = Queue.Queue() 

第二种情况,socket是已经建立的连接,客户端把数据发了过来,这个时候服务端使用recv()方法读取数据,然后把数据放到queue中,以便后面通过socket把数据传客户端。

else:
     data = s.recv(1024)
     if data:
         # A readable client socket has data
         print >>sys.stderr, ‘received "%s" from %s‘ % (data, s.getpeername())
         message_queues[s].put(data)
         # Add output channel for response
         if s not in outputs:
             outputs.append(s)

第三种情况,这个客户端已经断开了,所以服务端通过recv()接收到的数据就为空了,所以这个时候就可以把这个跟客户端的连接关闭了。

else:
    # Interpret empty result as closed connection
    print >>sys.stderr, ‘closing‘, client_address, ‘after reading no data‘
    # Stop listening for input on the connection
    if s in outputs:
        outputs.remove(s)  #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的连接对象还在outputs列表中,就把它删掉
    inputs.remove(s)    #inputs中也删除掉
    s.close()           #把这个连接关闭掉

    # Remove message queue
    del message_queues[s]  #连接已经断开,缓冲中的数据没用了,所以删除掉

writable list中的socket,也有几种状态,如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,表明这个连接不处于准备发送数据的状态。

# Handle outputs
for s in writable:
    try:
        next_msg = message_queues[s].get_nowait()
    except Queue.Empty:
        # No messages waiting so stop checking for writability.
        print >>sys.stderr, ‘output queue for‘, s.getpeername(), ‘is empty‘
        outputs.remove(s)
    else:
        print >>sys.stderr, ‘sending "%s" to %s‘ % (next_msg, s.getpeername())
        s.send(next_msg)

另外,如果在跟某个socket连接通信过程中出了错误,就把这个连接对象在inputs\outputs\message_queue中都删除,再把连接关闭掉。

# Handle "exceptional conditions"

for s in exceptional:
    print >>sys.stderr, ‘handling exceptional condition for‘, s.getpeername()
    # Stop listening for input on the connection
    inputs.remove(s)
    if s in outputs:
        outputs.remove(s)
    s.close()

    # Remove message queue
    del message_queues[s]

客户端:

下面的这个是客户端程序示例展示了如何通过select()对socket进行管理并与多个连接同时进行交互。

import socket
import sys

messages = [ ‘This is the message. ‘,
             ‘It will be sent ‘,
             ‘in parts.‘,
             ]
server_address = (‘localhost‘, 10000)

# Create a TCP/IP socket
socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
          socket.socket(socket.AF_INET, socket.SOCK_STREAM),
          ]

# Connect the socket to the port where the server is listening
print >>sys.stderr, ‘connecting to %s port %s‘ % server_address
for s in socks:
    s.connect(server_address)

接下来使用循环通过每个socket连接给server发送和接收数据。

for message in messages:

    # Send messages on both sockets
    for s in socks:
        print >>sys.stderr, ‘%s: sending "%s"‘ % (s.getsockname(), message)
        s.send(message)

    # Read responses on both sockets
    for s in socks:
        data = s.recv(1024)
        print >>sys.stderr, ‘%s: received "%s"‘ % (s.getsockname(), data)
        if not data:
            print >>sys.stderr, ‘closing socket‘, s.getsockname()       s.close()

服务器端完整代码:

#!/usr/bin/env python
#--*-- coding:utf-8 --*--
__author__ = ‘shl‘
import select
import socket
import sys
import Queue

#Create a TCP/IP socket
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.setblocking(0)

#Bind the socket to the port
server_address =(‘localhost‘,10000)
print >>sys.stderr,‘starting up on  %s port %s‘ %server_address
server.bind(server_address)

#Listen for incoming connections
server.listen(5)

#Sockets from which we expect to read
inputs = [server]

#Sockets to which we expect to write
outputs = []

#Outgoing messages queues (socket:Queue)
message_queues = {}

while inputs:

    #Waiting for at least one of sockets to be ready for processing
    print >>sys.stderr,‘\nwaiting for the next event...‘
    readable,writable,exceptional = select.select(inputs,outputs,inputs)

    #Handle inputs
    for s in readable:
        if s is server:
            # A "readable" server socket is ready to accept a connection
            connection,client_address = s.accept()
            print >>sys.stderr,‘new connection from ‘,client_address
            connection.setblocking(0)
            inputs.append(connection)

            #Give the connection a queue for data we want to send
            message_queues[connection] = Queue.Queue()
        else:
            data = s.recv(1024)
            if data:
                #A readable client socket has data
                print >>sys.stderr,‘received %s from %s‘ %(data,s.getpeername())
                message_queues[s].put(data)
                #Add output channel for response
                if s not in outputs:
                    outputs.append(s)
            else:
                #Interpret empty result as closed connection
                print >>sys.stderr,‘closing‘,client_address,‘after reading no data‘
                #Stop listening for input on the connection
                if s in outputs:
                    outputs.remove(s) #既然客户端都断开了,我就不用再给它返回数据了,所以这时候如果这个客户端的链接对象还在outputs列表中,就把它删掉
                inputs.remove(s)    #inputs也删掉
                s.close()

                #Remove message queue
                del message_queues[s]    #连接已经断开,缓冲中的数据没用了,所以删除掉

    #Handle outputs:
    for s in writable:
        try:
            next_message = message_queues[s].get_nowait()
        except Queue.Empty:
            #No messages waiting so stop checking for writability
            print >>sys.stderr,‘output queue for‘,s.getpeername(),‘is empty...‘
            outputs.remove(s)
        else:
            print >>sys.stderr,‘sending %s to %s‘ %(next_message,s.getpeername())
            s.send(next_message)
    #Handle "exceptional conditions"
    for s in exceptional:
        print >>sys.stderr,‘handing exceptional condition for‘,s.getpeername()
        #Stop listening for input on the connection
        inputs.remove(s)
        if s in outputs:
            outputs.remove(s)
        s.close()

        #Remove message queue
        del message_queues[s]
        

select_server

客户端完整代码:

#!/usr/bin/env python
# --*--coding:utf-8--*--
__author__ = ‘demon‘
import socket
import sys

messages = [‘This is the message!‘,
            ‘It will be sent!‘,
            ‘in parts!‘,
            ]
server_address = (‘localhost‘,10000)

#Create a TCP/IP socket
socks = [socket.socket(socket.AF_INET,socket.SOCK_STREAM),
         socket.socket(socket.AF_INET,socket.SOCK_STREAM),
        ]

#Connect the socket to the port where the server is listening
print >>sys.stderr,‘connecting to %s port %s‘ %server_address
for s in socks:
    s.connect(server_address)

for message in messages:
        #Send messages on both sockets
        for s in socks:
            print >>sys.stderr,‘%s:sending "%s"‘ %(s.getsockname(),message)
            s.send(message)

        #Read responses on both sockets
        for s in socks:
            data = s.recv(1024)
            print >>sys.stderr,‘%s:received "%s"‘ %(s.getsockname(),data)
            if not data:
                print >>sys.stderr,‘closing socket‘,s.getsockname()
                s.close()

select_client

在window上执行代码,在一个窗口执行server,另一个窗口里执行client,注意结果是在不同的端口上执行的。

服务器端结果:

starting up on  localhost port 10000

waiting for the next event...
new connection from  (‘127.0.0.1‘, 51468)

waiting for the next event...
new connection from  (‘127.0.0.1‘, 51469)

waiting for the next event...
received This is the message! from (‘127.0.0.1‘, 51468)

waiting for the next event...
sending This is the message! to (‘127.0.0.1‘, 51468)

waiting for the next event...
output queue for (‘127.0.0.1‘, 51468) is empty...

waiting for the next event...
received This is the message! from (‘127.0.0.1‘, 51469)

waiting for the next event...
sending This is the message! to (‘127.0.0.1‘, 51469)

waiting for the next event...
received It will be sent! from (‘127.0.0.1‘, 51468)
received It will be sent! from (‘127.0.0.1‘, 51469)
sending It will be sent! to (‘127.0.0.1‘, 51469)

waiting for the next event...
output queue for (‘127.0.0.1‘, 51469) is empty...
sending It will be sent! to (‘127.0.0.1‘, 51468)

waiting for the next event...
output queue for (‘127.0.0.1‘, 51468) is empty...

waiting for the next event...
received in parts! from (‘127.0.0.1‘, 51468)
received in parts! from (‘127.0.0.1‘, 51469)

waiting for the next event...
sending in parts! to (‘127.0.0.1‘, 51468)
sending in parts! to (‘127.0.0.1‘, 51469)

waiting for the next event...
output queue for (‘127.0.0.1‘, 51468) is empty...
output queue for (‘127.0.0.1‘, 51469) is empty...

waiting for the next event...
closing (‘127.0.0.1‘, 51469) after reading no data
closing (‘127.0.0.1‘, 51469) after reading no data

waiting for the next event...

客户端结果:

C:\Users\Administrator\PycharmProjects\s12day3\s08day7>python select_client.py
connecting to localhost port 10000
(‘127.0.0.1‘, 51468):sending "This is the message!"
(‘127.0.0.1‘, 51469):sending "This is the message!"
(‘127.0.0.1‘, 51468):received "This is the message!"
(‘127.0.0.1‘, 51469):received "This is the message!"
(‘127.0.0.1‘, 51468):sending "It will be sent!"
(‘127.0.0.1‘, 51469):sending "It will be sent!"
(‘127.0.0.1‘, 51468):received "It will be sent!"
(‘127.0.0.1‘, 51469):received "It will be sent!"
(‘127.0.0.1‘, 51468):sending "in parts!"
(‘127.0.0.1‘, 51469):sending "in parts!"
(‘127.0.0.1‘, 51468):received "in parts!"
(‘127.0.0.1‘, 51469):received "in parts!"

C:\Users\Administrator\PycharmProjects\s12day3\s08day7>

参考原文:https://pymotw.com/2/select/#module-select

时间: 2024-10-11 17:39:00

select浅析的相关文章

windows和linux套接字中的select机制浅析

先来谈谈为什么会出现select函数,也就是select是解决什么问题的? 平常使用的recv函数时阻塞的,也就是如果没有数据可读,recv就会一直阻塞在那里,这是如果有另外一个连接过来,就得一直等待,这样实时性就不是太好. 这个问题的几个解决方法:1. 使用ioctlsocket函数,将recv函数设置成非阻塞的,这样不管套接字上有没有数据都会立刻返回,可以重复调用recv函数,这种方式叫做轮询(polling),但是这样效率很是问题,因为,大多数时间实际上是无数据可读的,花费时间不断反复执行

select 1浅析

今天看到项目代码里有这条语句,不懂select 1 from XXXXXXX里的1是何意,查了一番才知道: 1.select 1 from mytable;与select anycol(目的表集合中的任意一行) from mytable;与select * from mytable 作用上来说是没有差别的,都是查看是否有记录,一般是作条件用的.select 1 from 中的1是一常量,查到的所有行的值都是它,但从效率上来说,1>anycol>*,因为不用查字典表. 2.查看记录条数可以用se

Select For update语句浅析 (转)

Select … for update语句是我们经常使用手工加锁语句.通常情况下,select语句是不会对数据加锁,妨碍影响其他的DML和DDL操作.同时,在多版本一致读机制的支持下,select语句也不会被其他类型语句所阻碍. 借助for update子句,我们可以在应用程序的层面手工实现数据加锁保护操作.本篇我们就来介绍一下这个子句的用法和功能. 下面是采自Oracle官方文档<SQL Language Reference>中关于for update子句的说明:(请双击点开图片查看) 从f

【转】浅析JQuery获取和设置Select选项的常用方法总结

1.获取select 选中的 text: $("#cusChildTypeId").find("option:selected").text(); $("#cusChildTypeId option:selected").text() 2.获取select选中的 value: $("#ddlRegType ").val(); 3.获取select选中的索引:      $("#ddlRegType ").g

Mysql查询优化器浅析

--Mysql查询优化器浅析 -----------------------------2014/06/11 1 定义 Mysql查询优化器的工作是为查询语句选择合适的执行路径.查询优化器的代码一般是经常变动的,这和存储引擎不太一样.因此,需要理解最新版本的查询优化器是如何组织的,请参考相应的源代码.整体而言,优化器有很多相同性,对mysql一个版本的优化器做到整体掌握,理解起mysql新版本以及其他数据库的优化器都是类似的. 优化器会对查询语句进行转化,转化等价的查询语句.举个例子,优化器会将

浅析STM32之usbh_def.H

[温故而知新]类似文章浅析USB HID ReportDesc (HID报告描述符) 现在将en.stm32cubef1\STM32Cube_FW_F1_V1.4.0\Middlewares\ST\STM32_USB_Host_Library\Core\Inc\usbh_def.H /** ****************************************************************************** * @file usbh_def.h * @aut

SQL Server 中WITH (NOLOCK)浅析

原文:SQL Server 中WITH (NOLOCK)浅析 概念介绍 开发人员喜欢在SQL脚本中使用WITH(NOLOCK), WITH(NOLOCK)其实是表提示(table_hint)中的一种.它等同于 READUNCOMMITTED . 具体的功能作用如下所示(摘自MSDN): 1: 指定允许脏读.不发布共享锁来阻止其他事务修改当前事务读取的数据,其他事务设置的排他锁不会阻碍当前事务读取锁定数据.允许脏读可能产生较多的并发操作,但其代价是读取以后会被其他事务回滚的数据修改.这可能会使您的

三层浅析及示例分析

什么是三层结构?所谓三层结构,不是物理上的三层划分,也不是简单的模块划分,而是逻辑上的三层,是在客户端和数据库访问之间加入了一个中间层,形成逻辑三层结构. 三层都是哪三层?它们的作用是什么?三层结构包含:表示层UI,业务逻辑层BLL,数据访问层DAL.1 显示层,就是软件的显示部分,主要是客户端,通常表现为WEB或窗体.主要功能:接受用户输入信息.显示系统输出信息.为用户提供一个交互界面. 2 业务逻辑层,系统主要功能部分,主要处理软件的业务逻辑,处理数据. 3 数据访问层,用于对数据库的操作,

Web服务之Nginx浅析

一.Nginx 简介: nginx [engine x]是Igor Sysoev编写的一个高性能的HTTP和反向代理服务器,另外它也可以作为邮件代理服务器. 在大多数情况下都是用来做静态web服务器和反向代理服务器,在作为反向代理服务器的时候,Nginx可以对后端的real server做负载均衡,基于应用层的负载均衡,但是他仅支持一些常见的协议,如:http.mysql.ftp.smtp. 特性: Nginx是一款面向性能设计的HTTP服务器,相较于Apache.lighttpd具有占有内存少