设为首页 收藏本站
查看: 2335|回复: 0

[经验分享] python socket编程之select

[复制链接]
累计签到:1 天
连续签到:1 天
发表于 2015-9-25 09:02:41 | 显示全部楼层 |阅读模式
一、select,poll,epoll三者区别 QQ截图20150925090226.png



二、select 服务端和客户端基本步骤引用:Python socket编程 http://blog.sina.com.cn/s/blog_523491650100hikg.html

1)建立服务器连接需要六个步骤。第1步,是创建socket对象。调用socket构造函数。
socket=socket.socket(familly,type)
family的值可以是AF_UNIX(Unix域,用于同一台机器上的进程间通讯),也可以是AF_INET(对于IPV4协议的TCP和 UDP),
至于type参数,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。


第2步,则是将socket绑定(指派)到指定地址上。
socket.bind((host,port))

第3步,绑定后,必须准备好套接字,以便接受连接请求。
socket.listen(backlog)
acklog指定了最多连接数,至少为1,接到连接请求后,这些请求必须排队,如果队列已满,则拒绝请求。


第4步,服务器套接字通过socket的accept方法等待客户请求一个连接:
connection,address=socket.accept()
调用accept方法时,socket会进入'waiting'(或阻塞)状态。客户请求连接时,方法建立连接并返回服务器。
accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址。

第5步是处理阶段,服务器和客户通过send和recv方法通信(传输数据)。
服务器调用send,并采用字符串形式向客户发送信息。send方法返回已发送的字符个数。服务器使用recv方法从客户接受信息。
调用recv时,必须指定一个整数来控制本次调用所接受的最大数据量。recv方法在接受数据时会进入'blocket'状态,最后返回一个字符串,用它来表示收到的数据。如果发送的量超过recv所允许,数据会被截断。多余的数据将缓冲于接受端。以后调用recv时,多余的数据会从缓冲区删除。

第6步,传输结束,服务器调用socket的close方法以关闭连接
2)建立一个简单客户连接则需要4个步骤。
第1步,创建一个socket以连接服务器 socket=socket.socket(family,type)

第2步,使用socket的connect方法连接服务器 socket.connect((host,port))

第3步,客户和服务器通过send和recv方法通信。

第4步,结束后,客户通过调用socket的close方法来关闭连接。


三、select代码总体说明3.1 使用select在python中,select函数是一个对底层操作系统的直接访问的接口。它用来监控sockets、files和pipes,等待IO完成(Waiting for I/O completion)。当有可读、可写或是异常事件产生时,select可以很容易的监控到。
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。
3.2 select server端代码说明一、select代码总结:

只要客户端发起一个新的socket过来,通过select监听这个句柄。
但是监听了之后,就用单线程处理了。
server端没有用到多线程。是监听有句柄(socket)过来(有连接活动过来)之后,
就用当前的这个处理了。它之所以看到的是异步的效果,是因为我们用了消息队列(Queue)减少阻塞。
但是所有的连接都是server端用单个线程处理的。


二、server端步骤:
1、该程序主要是利用socket进行通信,接收客户端发送过来的数据,然后再发还给客户端。
2、首先建立一个TCP/IP socket,并将其设为非阻塞,然后进行bind和listen。
3、通过select函数获取到三种文件列表,分别对每个列表的每个元素进行轮询,对不同socket进行不同的处理,最外层循环直到inputs列表为空为止
4、如果从客户端的一个socket接收到数据,设置个字典变量(message_queues)就把这个socket作为key,value为一个队列,以便装这个socket的数据。
5、如果可以从活跃客户端的socket里读取数据:就往队列里put(从客户端socket接收到)的数据。
   如果能可以把数据写入到客户端,就从队列里get_nowait(),无阻塞方式读取队列里的数据,并发送给客户端的socket。
5、当设置timeout参数时,如果发生了超时,select函数会返回三个空列表。 并从message_queues字典里删除这个socket。关闭连接。



三、详细步骤:
inputs:从客户端读取(readable)的活跃的socket列表,先默认添加server
outputs:可以写入客户端(writable)的活跃的socket列表

while True:
readable, writable, exceptional = select.select(inputs, outputs, inputs,timeout)#第一次循环的时候inputs列表里已经有server
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。

1)循环处理reatable列表:
情况1)server ready to connect client:准备建立连接--标识:
readable列表里有 server(因为inputs之前已经有server,如果,循环readable里有server的话,那么说明准备和一个客户端建立连接。)
connection, client_address = s.accept()
accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址
设成非阻塞模式:
connection.setblocking(0)
把inputs添加这个connection,也就是添加一个活跃可以读取数据的客户端socket。
把这个socket对象(connection),建一个我们以后可以发送数据的队列(queue)。
message_queues[connection] = Queue.Queue()#message_queues本是个空字典
情况2)server has connected:已经建立连接
接收数据
如果从可读取的客户端socket接收到数据的话:
我们就往队列里放个数据
message_queues.put(data),#这里的s其实就是情况1准备建立连接的connection。情况1的时候,已经建立好队列了。
假如客户端socket不在可读取的列表里,就加入到列表了。
    情况3)就是这个客户端已经断开了,所以你再通过recv()接收到的数据就为空了,所以这个时候你就可以把这个跟客户端的连接关闭了。
            并且,移除这个socket队列
2)循环处理writable列表:
对于writable list中的socket,也有几种状态:
如果这个客户端连接在跟它对应的queue里有数据,就把这个数据取出来再发回给这个客户端,否则就把这个连接从output list中移除,这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态

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

3.3 select client端代码说明Client端创建多个socket进行server链接,用于观察使用select函数的server端如何进行处理。
循环每个socket连接,给server发送和接收数据。



四、服务端完整代码及注释代码,参见本地目录:E:\Python-10期\s10day7-biji\select_socket下的文件。
引用:Python Select 解析:http://www.cnblogs.com/alex3714/p/4372426.html
运行方法:在windows下运行server端程序,再开个窗口运行client端程序。然后观察server端和client端的输出。

#!/usr/bin/env python
#_*_coding:utf-8_*_
__author__ = 'WangQiaomei'


import select
import socket
import sys
import Queue

# Create a TCP/IP socket
'''
第1步是 创建socket对象。调用socket构造函数。
socket=socket.socket(familly,type)
family的值可以是AF_UNIX(Unix域,用于同一台机器上的进程间通讯),也可以是AF_INET(对于IPV4协议的TCP和 UDP),
至于type参数,SOCK_STREAM(流套接字)或者 SOCK_DGRAM(数据报文套接字),SOCK_RAW(raw套接字)。
'''
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.setblocking(0)   #设置为非阻塞模式
'''
第2步,则是将socket绑定(指派)到指定地址上。
socket.bind((host,port))
'''

# 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)
'''
inputs:从客户端读取(readable)的活跃的socket列表,先默认添加server
outputs:可以写入客户端(writable)的活跃的socket列表
'''
inputs = [server ]
outputs = []
message_queues = {}

while inputs:
    print >>sys.stderr, '\nwaiting for the next event'
    timeout = 1
    '''
    #第一次循环的时候inputs列表里已经有server
select.select(rlist, wlist, xlist[, timeout]) 传递三个参数,一个为输入而观察的文件对象列表,一个为输出而观察的文件对象列表和一个观察错误异常的文件列表。
第四个是一个可选参数,表示超时秒数。其返回3个tuple,每个tuple都是一个准备好的对象列表,它和前边的参数是一样的顺序。下面,主要结合代码,简单说说select的使用。
    '''
    readable,writable,exceptional = select.select(inputs, outputs, inputs,timeout)
    # Handle inputs
    #超时情况
    if not(readable or writable or exceptional):
        print >>sys.stderr, 'timed out , do something else here...'
        continue
    for s in readable:
        '''
        情况1)server ready to connect client:准备建立连接--标识:
            readable列表里有 server(因为inputs之前已经有server,如果,循环readable里有server的话,那么说明准备和一个客户端建立连接。)
      '''
        if s is server:
            # A "readable" server socket is ready to accept a connection
            #accept()返回:第一个元素(connection)是新的socket对象,服务器通过它与客户通信;第二个元素(address)是客户的internet地址
            connection, client_address = s.accept()
            print >>sys.stderr, 'new connection from', client_address
            connection.setblocking(0)  #设成非阻塞模式:
            inputs.append(connection)  #把inputs添加这个connection,也就是添加一个活跃可以读取数据的客户端socket。

            # Give the connection a queue for data we want to send
            '''
            #把这个socket对象(connection),建一个我们以后可以发送数据的队列(queue)。
            #message_queues本是个空字典
          '''
            message_queues[connection] = Queue.Queue()
        #情况2)server has connected:已经建立连接
        else:
            data = s.recv(1024) #接受数据
            if data:    #如果从可读取的客户端socket接收到数据的话:
                # A readable client socket has data
                print >>sys.stderr, 'received "%s" from %s' % (data, s.getpeername())
                '''
                我们就往队列里放个数据
                message_queues.put(data),#这里的s其实就是情况1准备建立连接的connection。
                情况1的时候,已经建立好队列了。
              '''
                message_queues.put(data)
                # Add output channel for response
                #假如客户端socket不在可读取的列表里,就加入到列表了。
                if s not in outputs:
                    outputs.append(s)
            #情况3)就是这个客户端已经断开了,所以你再通过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列表中,就把它删掉
                    outputs.remove(s) #如果存在,就从活跃可写入的socket列表里删除
                print 'wriatable---before::',writable
                if s in writable:
                    writable.remove(s) #如果存在,就从写入的列表里删除
                inputs.remove(s)       #活跃可读的socket列表里删除
                s.close()               #把连接关闭

                # Remove message queue
                del message_queues   #移除这个socket队列
    print message_queues
    print 'wriatable:',writable
    # Handle outputs
    '''
    循环处理可写socket列表。
     如果有数据则发送回客户端,如果没数据则就把这个连接从output list中移除,outputs是活跃可写的socket列表。
     这样下一次循环select()调用时检测到outputs list中没有这个连接,那就会认为这个连接还处于非活动状态
     '''
    for s in writable:
        try:
            '''
            q.get([block[, timeout]]) 获取队列,timeout等待时间
            q.get_nowait() 相当q.get(False) 非阻塞
          '''
            next_msg = message_queues.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)   #活跃可写socket列表删除此socket。
        else:                   #假如队列有数据
            print >>sys.stderr, 'sending "%s" to %s' % (next_msg, s.getpeername()) #客户端socket实例.getpeername()获取客户端的ip和端口
            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() #客户端socket实例.getpeername()获取客户端的ip和端口
        # Stop listening for input on the connection
        inputs.remove(s)        #活跃可读socket列表删除此socket。
        if s in outputs:
            outputs.remove(s)   #活跃可写socket列表删除此socket。
        s.close()               #连接关闭

        # Remove message queue
        del message_queues   #从队列里删除。


'''
Python Select 解析:http://www.cnblogs.com/alex3714/p/4372426.html
'''

五、客户端完整代码及注释#!/usr/bin/env python
#_*_coding:utf-8_*_

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
    '''
    s.getpeername()
    返回连接套接字的远程地址。返回值通常是元组(ipaddr,port)。
    s.getsockname()
    返回套接字自己的地址。通常是一个元组(ipaddr,port)
    '''
    for s in socks:
        print >>sys.stderr, '%s: sending "%s"' % (s.getsockname(), message)     #s.getsockname() 返回socket自己的地址,发送消息
        s.send(message)

    # Read responses on both sockets
    for s in socks:
        data = s.recv(1024)     #默认最大文件描述符1024
        print >>sys.stderr, '%s: received "%s"' % (s.getsockname(), data)       #接受消息
        if not data:
            print >>sys.stderr, 'closing socket', s.getsockname()
            s.close()   #关闭链接。



运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.yunweiku.com/thread-118450-1-1.html 上篇帖子: 使用python 的paramiko制作堡垒机 下篇帖子: 自定义python startup脚本使交互解释器支持tab和删除键 python
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表