您的位置:首页 > 编程语言 > Python开发

Python处理多个客户端连接---多路复用选择服务器

2016-05-22 22:32 513 查看

多路复用

到目前为止,我们已经看到如何用分支进程和派生线程来同时处理多个客户端,以及一个封装了这两个方案的库类。在这两种方法下,所有的客户端处理程序似乎都是彼此并行运行(即在同一时间内)运行的,所以在接受新的请求或处理长期运行的客户端处理程序时,服务器未被阻塞。

不过从技术上讲,线程和进程并不是真正并行运行的,除非你足够幸运,机器有多个cpu。相反,你的操作系统可以执行一个变戏法的操作—它在所有活动任务之间分配计算机的处理能力。它先运行其中的某个任务的一部分,然后再运行另一个任务的一部分,就这样继续下去。所有任务看上去好像是并行运行的,但只是因为操作系统在任务切换的焦点如此之快,以至于你通常注意不到。这个由操作系统完成,在任务之间切换的进程有时也被称为时间片,在更多情况下,它被称为多路复用。

当我们派生线程和进程时,我们依靠操作系统来控制运行状态的任务,所以没有任务急需计算资源,尤其是主服务器调度循环。然而,没有理由可以任务python脚本不能这么做。例如一个脚本可能把任务分成多个步骤,先运行一个任务的某个步骤,然后再运行另一个任务的某个步骤,如此继续下去,直到所有的任务都完成。改脚本只需知道如何把注意力划分到多个活动任务中,以便在其自身采用多路复用。

select

服务器可以应用这种技术产生的另一种方式来一次处理多个客户端。改方式既不需要线程也不需要分支。通过多路复用客户端连接和拥有select系统调用的主调用程序,一个单一的事件循环就可以处理多个客户端,并可以并行接受新的客户端。这样的服务器有时也称为异步,在异步服务器上,一个单一的主循环在一个单独的进程和线程中运行,决定每次哪些客户端应该得到关注。如果已经准备好回话,那么客户端请求和主调用循环都会获得服务器小片的注意。

该服务器背后是操作系统select调用,在所有主要的平台上的python标准select模块中都可以获得。select让我们把注意力直接放到可以随时回话的套接字上,以避免阻塞对那些没有准备好的套接字的调用。也就是说,当select是套接字,我们可以确定套接字调用,如accept,recv和send通过select应用到返回对象时,不会阻塞服务器。正因为如此,使用select的单循环服务器不需要暂停与某个客户端的会话或等待新的客户端,而其他客户端则在焦急等待服务器的关注。

因为这种类型的服务器不需要启动线程或者进程,所以当与客户端的事务相对短暂时,这种类型的服务器可以发挥很大的作用。然而,它也要求这些事务是快速的;如果这些事务不够快,在等待某个特定时客户端会话结束时,它仍然有阻塞的风险,除非增加长时间运行会话的线程或者分支。

基于select的响应服务器

//服务器:使用select并行处理多个客户端。使用select模块手动在套接字之间多重通道传输:接收新的客户端连接的主套接字,并输入套接字到接收的客户端;select可以采用可选的第四个参数0来轮询,用n.m等待n.m秒,或者忽略等待直到任意套接字准备好了,可以处理
__author__ = 'JianqingJiang'
# -*- coding: utf-8 -*-
import sys,time
from socket import select
from socket import socket,AF_INET,SOCK_STREAM
def now: return time.ctime(time.time())
myHost = ''                                                # server machine, '' means local host
myPort = 50007                                             # listen on a non-reserved port number
if len(sys.argv) == 3:                                     # allow host/port as cmdline args too
myHost,myPort = sys.argv[1:]
numPortSocks = 2                                           # number of ports for client connects

# make main sockets for accepting new client requests

mainsocks,readsocks,writesocks = [],[],[]
for i in range(numPortSocks):
portsock=socket(AF_INET,SOCK_STREAM)                   # make a TCP/IP socket object
portsock.bind((myHost,myPort))                         # bind it to server port number
portsock.listen(5)                                     # listen,allow 5 pending connects
mainsocks.append(portsock)                             # add to main list to identify
readsocks.append(portsock)                             # bind on consecutive ports
myPort += 1

print('select-server loop starting')
while True:
#print(readsocks)
readable,writeables,exceptions = select(readsocks,writesocks,[])
for sockobj in readable:
if sockobj in mainsocks:                           # for ready input sockets
#port socket:accept new client
newsock,address = sockobj.accept()             # accept should not block
print('Connect:',address,id(sockobj))          # newsock is a new socket
readsocks.append(newsock)                      # add to select list,wait
else:
#client socket: read next line
data=sockobj.recv(1024)                        # recv should not block
print('\tgot',data,'on',id(sockobj))
if not data:                                   # if closed by the clients
data=sockobj.close()                       # close here and remv from
readsocks.remove(sockobj)                  # del list else reselected
else:
#this may block:should really select for writes too
reply = 'Echo=>%s at %s' % (data,now())
sockobj.send(reply.encode())


这个脚本的大部分是其最后的while循环,用于调用select,找出哪些套接字已经准备好处理;这些包括两个主要的端口套接字,在这两个套接字上,客户端可以连接并打开客户端连接,然后遍历所有这些准备好的套接字,在主要的端口套接字上接受连接,在任何准备好输入的客户端套接字上读取和响应输入。此代码中的accept和recv调用,在select返回之后,可以保证不阻塞服务器进程;结果,该服务器可以迅速回到循环顶部,处理新来的客户端请求和已连接的客户端输入。其效果是,以伪并行形式服务所有新的请求客户端。

select调用细节

从形式上看,select可以调用可选择对象的三种列表(输入源,输出源和特殊条件源),加一个可选的timeout。timeout参数可能是真正几秒内的等待期限(使用浮点数来表示秒的小数部分),0值意味着简单轮询,并立即返回,或省略意味着等待,直到至少有一个对象准备好。调用返回三个准备阿訇的对象—前三个参数的子集—如果在源准备好之前,timeout超时,任何或所有的对象可能都是空的

select的可移植性

类似线程,但不像分支。从技术上讲select调用仅适用于windows上的套接字,但适用于unix和macintosh上的文件和管道,当然对于在互联网上运行的服务器,我们感兴趣的主要设备是套接字。

非阻塞状态的套接字

select可以让我们肯定,套接字调用(如accept和recv)讲不会阻塞调用者,但是它一般也可能使python套接字处于非阻塞状态。调用套接字对象的setblocking方法把套接字设置成阻塞或非阻塞模式例如,假定一个像sock,setblocking(flag)的调用,如果改flag(标示)为0,套接字sock设置为非阻塞模式,否则设置为阻塞模式。所有套接字最初在阻塞模式下启动,因此套接字调用可能总是让调用者等待。

然而,当在非阻塞模式时,如果某个recv套接字调用没有找到任何数据,或某个send调用不能立即传递数据,就会引起一个socket.error异常。脚本可以捕获这个异常,以确定套接字是否准备好进行处理了。在阻塞模式下,这些调用一直处于阻塞状态,直到它们可以继续下去。当然,有可能存在比数据传输更多的方法,可以处理客户端请求,所以非阻塞的套接字一般不能保证服务器停滞。它们仅仅是另一种多路复用服务器的方式。和select一样,在客户端请求可以迅速等到服务时,它们更合适。

asyncore模块框架

如果你乐于使用select,可能也会对检查python标准库中的asyncore.py模块感兴趣。它实现了一种基于类的回调模型,其通过预编码的select事件循环调度,输入和输出回调到类方法。因此,它可以在没有线程或分支的情况下建立服务器,这就是一个基于select的,可选择的socketserver模块的线程和分支模块,对于一般的服务器类型来说,在处理过程较为简单时,asyncore是最好的—它所描述的是”I/O密集型”,而不是”CPU计算密集型”的程序,其中的后者仍然需要线程或者分支。

Twisted

对于其他服务器选项,也可以参阅开源的Twisted系统,Twisted是用Python编写的,是一个支持tcp,udp,组播,ssl/tsl,串行通信及更多的异步网络框架。它同时支持客户端和服务器,包括一些常用的网络服务的实现,如网络服务器,irc聊天服务器,邮件服务器,关系数据库接口和对象处理。

总结:选择一个服务器计划

那么,你何时应该使用select代替线程或者分支,去建立一个服务器呢?每个应用程序有不同的需求,当然,正如前面所提到的,当客户端处理过程相对来说比较简单,或者不是CPU密集型时,基于select调用服务器一般都表现非常好。如果它们不是简单的处理方法,那么线程或者分支用于传递数据的套接字调用之上或者之外,长期运行处理过程,线程和分支就特别有用,然而,组合使用也是有可能的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: