在计算机中,如果你的程序在等待,通常是因为以下两个原因:
可以用很多方法来实现任务队列。对单机来说,标准库的multiprocessing模块有一个Queue函数。接下来模拟一个洗盘子的人和多个烘干进程,我们使用一个中间队列dish_queue。
守护进程(Daemon Process),也就是通常说的 Daemon 进程(精灵进程),是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
守护进程是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。
Linux 的大多数服务器就是用守护进程实现的。比如,Internet 服务器 inetd,Web 服务器 httpd 等。
import multiprocessing as mp
import time
def washer(dishes, output):
for dish in dishes:
print('Washing', dish, 'dish')
time.sleep(5)
# 把洗好的碟子放在等待烘干队列上
output.put(dish)
def dryer(input):
while True:
# 如果队列中没有任务可以get,该进程应该会陷入阻塞
dish = input.get()
print('Drying', dish, 'dish')
time.sleep(10)
# 完成get到的任务
input.task_done()
# 需要if __name__ == '__main__'的原因:https://stackoverflow.com/questions/18204782/runtimeerror-on-windows-trying-python-multiprocessing
if __name__ == '__main__':
dish_queue = mp.JoinableQueue()
# 其实可以产生多个dryer进程,这里只生成了一个dryer进程
dryer_proc = mp.Process(target=dryer, args=(dish_queue,))
# 以守护进程的形式运行该进程
dryer_proc.daemon = True
dryer_proc.start()
dishes = ['salad', 'bread', 'entree', 'dessert']
washer(dishes, dish_queue)
# 表示所有碟子都洗完了
dish_queue.join()
结果:
Washing salad dish
Washing bread dish
Drying salad dish
Washing entree dish
Washing dessert dish
Drying bread dish
Drying entree dish
Drying dessert dish
线程运行在进程内部,可以访问进程的所有内容,有点像多重人格。multiprocessing模块有一个兄弟模块threading,后者用线程来代替进程。multiprocessing和threading的区别之一就是threading没有terminate()函数。很难终止一个正在运行的线程,因为这可能会引起代码和失控连续性上的各种问题。
import threading, queue
import time
def washer(dishes, dish_queue):
for dish in dishes:
print('Washing', dish)
time.sleep(5)
dish_queue.put(dish)
def dryer(dish_queue):
while True:
dish = dish_queue.get()
print('Drying', dish)
time.sleep(10)
dish_queue.task_done()
dish_queue = queue.Queue()
# 其实可以产生多个dryer线程,这里只生成了一个dryer线程
dryer_thread = threading.Thread(target=dryer, args=(dish_queue,))
dryer_thread.start()
dishes = ['salad', 'bread', 'entree', 'desert']
washer(dishes, dish_queue)
dish_queue.join()
结果:
Washing salad
Washing bread
Drying salad
Washing entree
Washing desert
Drying bread
Drying entree
Drying desert
# 开两个线程
for n in range(2):
dryer_thread = threading.Thread(target=dryer, args=(dish_queue,))
dryer_thread.start()
结果:
Washing salad
Drying salad
Washing bread
Washing entree
Drying bread
Washing desert
Drying entree
Drying desert
注意更多:
在Python中,线程不能加速受CPU限制的任务,原因是标准Python系统中使用了全局解释器锁(GIL)。GIL的作用是避免Python解释器中线程问题,但是实际上会让多线程程序运行速度比对应但线程版本甚至是多进程版本更慢。
对于Python有以下建议:
在IO操作的过程中,当前线程被挂起,而其他需要CPU执行的代码就无法被当前线程执行了。
因为一个IO操作就阻塞了当前线程,导致其他代码无法执行,所以我们必须使用多线程或者多进程来并发执行代码,为多个用户服务。每个用户都会分配一个线程,如果遇到IO导致线程被挂起,其他用户的进程或者线程不受影响。
多线程和多进程的模型虽然解决了并发问题,但是系统不能无上限地增加线程。由于系统切换线程的开销也很大,所以,一旦线程数量过多,CPU的时间就花在线程切换上了,真正运行代码的时间就少了,结果导致性能严重下降。
由于我们要解决的问题是CPU高速执行能力和IO设备的龟速严重不匹配,多线程和多进程只是解决这一问题的一种方法。
另一种解决IO问题的方法是异步IO。当代码需要执行一个耗时的IO操作时,它只发出IO指令,并不等待IO结果,然后就去执行其他代码了。一段时间后,当IO返回结果时,再通知CPU进行处理。
消息模型是如何解决同步IO必须等待IO操作这一问题的呢?
异步IO模型需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程。
1. 当遇到IO操作时,代码只负责发出IO请求,不等待IO结果,然后直接结束本轮消息处理,进入下一轮消息处理过程。
2. 当IO操作完成后,主线程将收到一条“IO完成”的消息,处理该消息时就可以直接获取IO操作结果。
在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。
这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。
关于如何提高Web服务端并发效率的异步编程技术
阻塞非阻塞:调用者的角度
同步异步:被调用者的角度
例子:
老张爱喝茶,废话不说,煮开水。
出场人物:老张,水壶两把(普通水壶,简称水壶;会响的水壶,简称响水壶)。
1.老张把水壶放到火上,立等水开。(同步阻塞)
老张觉得自己有点傻
2.老张把水壶放到火上,去客厅看电视,时不时去厨房看看水开没有。(同步非阻塞)
老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~的噪音。
3.老张把响水壶放到火上,立等水开。(异步阻塞)
老张觉得这样傻等意义不大
4.老张把响水壶放到火上,去客厅看电视,水壶响之前不再去看它了,响了再去拿壶。(异步非阻塞)
老张觉得自己聪明了。所谓同步异步,只是对于水壶而言。
普通水壶,同步;响水壶,异步。虽然都能干活,但响水壶可以在自己完工之后,提示老张水开了。这是普通水壶所不能及的。同步只能让调用者去轮询自己(情况2中),造成老张效率的低下。
所谓阻塞非阻塞,仅仅对于老张而言。立等的老张,阻塞;看电视的老张,非阻塞。情况1和情况3中老张就是阻塞的,媳妇喊他都不知道。
虽然3中响水壶是异步的,可对于立等的老张没有太大的意义。所以一般异步是配合非阻塞使用的,这样才能发挥异步的效用。
事件驱动模型就是一种非阻塞IO的具体实现(利用中间层来协调IO操作和CPU的操作)。
事件处理的机制里应该有个事件处理器,事件处理器位于元素和事件处理方法的中间位置,我们在定义事件的时候就是等于在事件处理器里定义元素和事件处理方法的关系。
当这种对应关系定义好后,事件处理器就会启动一个死循环,这个循环反复检测元素的状态变化,当事件处理器发现某个状态产生了变化,处理器就会找到对应的事件处理方法,然后执行这个方法。
事件驱动编程实现的核心技术就是能让方法变成对象能在事件处理的流程里传递,方法得到事件管理器的指令后在合适的位置上被促发,这就是回调函数,而JavaScript语言里函数可以当做对象传递,也就保证了事件驱动编程上升到语言层级变成了可能,我想这就是nodejs作者使用google的V8引擎设计出nodejs的重要原因之一。
程序中有许多任务,而且任务之间高度独立(因此它们不需要互相通信,或者等待彼此),而且在等待事件到来时,某些任务会阻塞。
当应用程序需要在任务间共享可变的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。
更多: 基于线程与基于事件的并发编程之争
http://blog.csdn.net/kobejayandy/article/details/11856735
https://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/001432090171191d05dae6e129940518d1d6cf6eeaaa969000
http://python.jobbole.com/86481/
http://blog.csdn.net/qq910894904/article/details/41699541
https://www.zhihu.com/question/20511233
http://blog.leiqin.info/2012/12/02/%E8%BF%9B%E7%A8%8B-%E7%BA%BF%E7%A8%8B%E5%92%8C%E5%8D%8F%E7%A8%8B%E7%9A%84%E7%90%86%E8%A7%A3.html
一种技术的出现必然是为了解决某种问题,gevent是为了解决什么问题呢,设想下面这种情况。
你要做一个千人在线的Web聊天室,聊天室需要能够实时来收发消息。但是,HTTP是无状态的,也就是说,服务器没有直接把消息发给浏览器的能力。所以你往服务器发送数据之后,服务器没法把你的消息推送给其他聊天室的人,但有若干方案可以解决这个问题。
这里假设我们采用常见的长轮询的方案,即客户端请求服务端获取最新的消息,服务器有消息就返回数据,否则将一直保持连接直到超时。这时候,如果千人在线的话,就需要保持1000个连接,如果连接是进程模式或者线程模式,那就要开对应个数的进程或者线程,1000个进程或者线程的切换开销会消耗太多的资源。
你仔细分析这个聊天室的代码执行情况,会发现这么多的进程或者线程大部分时间都是闲的,它们在等浏览器发消息,啥事都没干。针对这个问题,你可以想到,要一个进程在闲的时候去干其他的事情,等这边消息到了再回来处理就好了。gevent把这个功能实现了,切换开销大大降低,系统性能飙升。总结起来就一句话,如果系统资源过的消耗在进程线程切换上面,用gevent!
gevent就是一个基于事件的很棒的库:你只需要编写普通的代码,gevent会神奇地把它们转换成协程。协程就像可以互相通信的生成器,它们会记录自己的位置。gevent可以修改许多Python的标准对象,比如socket (import gevent from gevent import socket gevent.socket.x),从而使用它自己的机制来代替阻塞。如果遇到阻塞普通线程的情况,gevent会把控制权切换到另一个绿色进程。
协程无法处理C写成的Python扩展代码,比如一些数据库驱动程序。
gevent还不能完全兼容Python 3
使用gevent还有一个潜在的危险。对于基于事件的系统来说,执行的每段代码都应该尽可能快。尽管不会阻塞,执行复杂任务的代码还是会很慢。就是即使其中一段代码运行时间很长也要搞下去的意思,不能中途运行另外一段代码,而[在多线程中,一个线程运行太久,优先级比较低,就会被其他线程所抢占]。(http://blog.csdn.net/jason_cuijiahui/article/details/77101693)
除了使用gevent版本的socket之外,你可以使用猴子补丁(monkey-patching)函数。这个函数会修改标准模块,比如socket,直接让她们使用绿色线程而不是调用gevent版本。如果想在整个程序中应用gevent,这种方法非常有用,即使那些你无法直接接触到的代码也会被改变。
在程序的开头,添加下面的代码:
# 把程序中所有普通socket都修改成gevent版本,即使是标准库也不例外
# 这个改动只对Python代码有效,对C写成的库无效
from gevent import monkey
monkey.patch_socket()
# 不只是socket
from gevent import monkey
monkey.patch_all()
另外两个流行的事件驱动框架是tornado和gunicorn。它们都使用了底层事件处理和高速Web服务器。如果你想使用传统的Web服务器(比如Apache)来构建高速网站,整两个框架非常值得一看。
twisted是一个异步事件驱动的网络框架。你可以把函数关联到事件(比如数据接收或者连接关闭)上,当事情发生时这些函数会被调用。这种设计被称为回调(callback),如果你以前用过JavaScript,那一定不会陌生。
如果是第一次见到回调,可能会觉得它有点过时。对于有些开发者来说,基于回调的代码在应用规模变大之后会很难维护。
和gevent一样,twisted还没有兼容Python 3
twisted很大,支持很多基于TCP和UDP的网络协议。
一个系列
asyncio模块在Python 3.4中首次出现。目前,它提供一种通用的时间循环,可以兼容twisted、gevent和其它异步方法。目标是一种一种标准、简洁、高性能的异步API。
更多
我们之前的洗盘子示例代码,无论使用的是进程还是线程,都运行在一台机器上。下面我们使用另一种方法来实现队列,让它可以既支持单机又支持网络。有时候用了进程和线程,单机仍然无法满足需求。
在第8章中,Redis的角色是数据库,而这里指的是它的并发特征。
可以使用Redis列表快速创建一个队列。Redis服务器部署在一台机器上;客户端可以部署在同一台机器上也可以部署在不同机器上,通过网络通信。无论是哪种情况,客户端都是通过使用TCP和服务器通信,因此它们是网络化。
一个或多个生产者客户端向列表的一端压入消息,一个或多个工人客户端通过阻塞弹出操作从列表中获得需要洗的盘子。如果列表为空,它们就会闲置。如果有一条消息,第一个空闲工人就会去处理。
和之前的基于进程和线程的示例一样,redis_washer.py会生成一个盘子序列:
# redis_washer.py
# 循环会生成是个包含盘子名称的消息,最后一条消息是“退出”(quit)。程序会把所有消息都添加到Redis服务器上的dishes列表中,就像添加到Python列表一样。
import redis
conn = redis.Redis()
print('Washer is starting')
dishes = ['salad', 'bread', 'entree', 'dessert']
for dish in dishes:
msg = dish.encode('utf-8')
conn.rpush('dishes', msg)
print('Washed', num)
conn.rpush('dishes', 'quit')
print('Washer is done')
# redis_dryer.py
# 这段代码会等待第一个令牌是dishes的消息,并打印出烘干的盘子。如果遇到“退出”消息就终止循环
import redis
conn = redis.Redis()
print('Dryer is starting')
while True:
# 等待有对象可以取出
msg = conn.blpop('dishes')
if not msg:
break
val = msg[1].decode('utf-8')
if val == 'quit':
break
print('Dried', val)
print('Dishes a dried')
启动烘干工人,然后启动清洗工人。在命令结尾加上&,让第一个程序在后台运行;它会一直运行下去,但是不会监听键盘。接着我们正常(在前台)启动清洗进程。你可以看到两个进程是混在一起的输出。
$ python redis_dryer.py &
$ python redis_washer.py
下面对redis_dryer.py进行修改:
# redis_dryer2.py
def dryer():
import redis
import os
import time
conn = redis.Redis()
pid = os.getpid()
timeout = 20
print('Dryer process %s is starting' % pid)
while True:
# 若20秒未能取出元素,则msg返回为None,触发下面的if判断
msg = conn.blpop('dishes', timeout)
if not msg:
break
val = msg[1].decode('utf-8')
if val == 'quit':
break
print('%s: dried %s' % (pid, val))
time.sleep(0.1)
print('Dryer process %s is done' % pid)
import multiprocessing
DEYEARS=3
for num in range(DRYERS):
p = multiprocessing.Process(target=dryer)
p.start()
加入的功能越多,流水线就越有可能出问题。此时,有三种技术可供你使用。
以下有一些基于Python的队列包添加了额外的控制层(有些使用的是Redis)
在讨论并发时,主要讨论的是时间:单机解决方案(进程、线程和绿色线程)。还简单介绍了网络化的解决方案(Redis、ZeroMQ)。现在,我们来单独介绍一下网络化,也就是跨空间的分布式计算。
发布-订阅并不是队列,而是广播。一个或多个进程发布信息,每个订阅进程声明自己感兴趣的消息类型,然后每个消息都会被复制一份发给感兴趣的订阅进程。因此,一个消息可能只被处理一次,也可能多于一次,还可能完全不被处理。每个发布者只负责广播,并不知道谁(如果有人的话)在监听。
你可以使用Redis来快速搭建一个发布-订阅系统。发布者会发出包含话题和值的消息,订阅者会声明它们对什么话题感兴趣。
下面是发布者,redis_pub.py
import redis
import random
conn = redis.Redis()
cats = ['siamese', 'persian', 'maine coon', 'norwegian forest']
hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']
for msg in range(10):
cat = random.choice(cats)
hat = random.choice(hats)
# 每个话题是猫的一个品种,每个消息的值是帽子的一种类型
print('Publish: %s wears a %s' % (cat, hat))
conn.publish(cat, hat)
下面是一个订阅者,redis_sub.py
import redis
conn = redis.Redis()
topics = ['maine coon', 'persian']
sub = conn.pubsub()
sub.subscribe(topics)
# listen()方法会返回一个字典,如果它的类型是'message',那就是由发布者发出的消息
for msg in sub.listen():
if msg['type'] == 'message':
# channel键是话题(猫)
cat = msg['channel']
# data键包含消息的值(帽子)
hat = msg['data']
print('Subscribe: %s wears a %s' % (cat, hat))
$ python redis_sub.py
$ python redis_pub.py
可以使用任意数量的订阅者(和发布者)。如果一个消息没有订阅者,那它会从Redis服务器中消失。然而,如果有订阅者,消息会停留在服务器中,直到所有的订阅者都获取完毕。
ZMQ是是个类似于Socket的一系列接口,他跟Socket的区别是:普通的socket是端到端的(1:1的关系),而ZMQ却是可以N:M 的关系,人们对BSD套接字的了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而ZMQ屏蔽了这些细节,让你的网络编程更为简单。ZMQ用于node与node间的通信,node可以是主机或者是进程。
引用官方的说法:“ZMQ(以下ZeroMQ简称ZMQ)是一个简单好用的传输层,像框架一样的一个socket library,他使得Socket编程更加简单、简洁和性能更高。是一个消息处理队列库,可在多个线程、内核和主机盒之间弹性伸缩。ZMQ的明确目标是“成为标准网络协议栈的一部分,之后进入Linux内核”。现在还未看到它们的成功。但是,它无疑是极具前景的、并且是人们更加需要的“传统”BSD套接字之上的一 层封装。ZMQ让编写高性能网络应用程序极为简单和有趣。”
更多讨论
ZeroMQ没有核心服务器,因此每个发布者都会发送给所有订阅者。我们来使用ZeroMQ重写一下上面的猫-帽子示例。
发布者为zmq_pub.py,内容如下所示:
# 注意代码时如何用UTF-8来编码话题和值字符串的
import zmq
import random
import time
host = '*'
port = 6789
ctx = zmq.Context()
pub = ctx.socket(zmq.PUB)
pub.blind('tcp://%s:%s' % (host, port))
cats = ['siamese', 'persian', 'maine coon', 'norwegian forest']
hats = ['stovepipe', 'bowler', 'tam-o-shanter', 'fedora']
time.sleep(1)
for msg in range(10):
cat = random.choice(cats)
cat_byte = cat.encode('utf-8')
hat = random.choice(hats)
hat_bytes = hat.encode('utf-8')
print('Publish: %s wears a %s' % (cat, hat))
pub.send_mulitpart([cat_bytes, hat_bytes])
下面是订阅者zmq_sub.py:
import zmq
host = '127.0.0.1'
port = 6789
ctx = zmq.Context()
sub = ctx.socket(zmq.SUB)
sub.connect('tocp://%s:%s' % (host, port))
# 若想订阅所有话题,则topics = ''
topics = ['maine coon', 'persian']
for topic in topics:
sub.setsockopt(zmq.SUBSCRIBE, topic.encode('utf-8'))
while True:
cat_bytes, hat_bytes = sub.recv_multipart()
cat = cat_bytes.decode('utf-8')
hat = hat_bytes.decode('utf-8')
print('Subscribe: %s wears a %s' % (cat, hat))
$ python zmq_sub.py
$ python zmq_pub.py
因特网是基于规则的,这些规则定义了如何创建链接、交换数据、终止连接、处理超时等。这些规则被称为协议,它们分布在不同的层中。分层的目的是兼容多种实现方法。
最底层处理的是电信号,其余层都是基于下面的层构建而成。在大约中间的位置是IP层,这层规定了网络位置和地址映射方法以及数据包(块)的传输方式。IP的上一层有两个协议描述了如何在两个位置之间移动比特。
在协议栈中,传输层位于网络层之上,传输层协议为不同主机上运行的进程提供逻辑通信,而网络层协议为不同主机提供逻辑通信。
在网络层,数据传输的基本单位是数据包(也称为分组)。在发送方,传输层的报文到达网络层时被分为多个数据块,在这些数据块的头部和尾部加上一些相关控制信息后,即组成了数据包(组包)。数据包的头部包含源结点和目标结点的网络地址(逻辑地址)。
在接收方,数据从低层到达网络层时,要将各数据包原来加上的包头和包尾等控制信息去掉(拆包),然后组合成报文,送给传输层。
最底层的网络编程使用的是套接字,源于C语言和Unix操作系统。套接字层的编程时非常繁琐的。使用类似于ZeroMQ的库会简单很多(ZeroMQ 的主要部分是套接字 Socket,不过它并未直接使用传统的套接字,而是在传统的套接字 API上提供了一个抽象层,这让用户从复杂和重复的编程任务中解脱出来。ZeroMQ 支持多种类型的套接字 (类型被定义为套接字自身的一个属性)。发送端和接收端的套接字类型组合造就了多种不同的通信模式),但是了解一下底层的工作原理还是非常有用的。举例来说,网络发生错误时出现的错误信息通常是和套接字相关的。
我们来编写一个非常简单的客户端-服务器通信示例。客户端发送一个包含字符串的UDP数据报给服务器,服务器会返回一个包含字符串的数据包。
# udp_server.py
from datetime import datetime
import socket
server_address = ('localhost', 6789)
max_size = 4096
print('Start the server at', datetime.now())
print('Waiting for a client to call.')
# 服务器必须用socket包中的两个方法来建立网络连接:socket.socket和bind
# socket.socket会创建一个套接字。AF_INET表示要创建一个因特网(IP)套接字。(还有其他类型的Unix域套接字,不过那些只能在本地运行。)SOCK_DGRAM表示我们要发送和接收数据报,换句话说,我们要使用UDP。
server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# bind会绑定(监听到达这个IP地址和端口的所有数据)到这个套接字上。
server.bind(server_address)
# 服务器会等待数据包到达(recvfrom),收到数据报后,服务器会被唤醒并获取数据和客户端的信息。client变量包含客户端的地址和端口,用于给客户端发送数据
data, client = server.recvfrom(max_size)
print('At', datetime.now(), client, 'said', data)
server.sendto(b'Are you talking to me?', client)
# 关闭连接
server.close()
# udp_client.py
import socket
from datetime import datetime
server_address = ('localhost', 6789)
max_size = 4096
print('Starting the client at', datetime.now())
client = socket.socket(socket.AF_INEF, socket.SOCK_DGRAM)
# 客户端需要知道服务器的地址和端口号,但是并不需要指定自己的端口号。它的端口号由系统自动分配。
client.sentto(b'Hey!', server_address)
data, server = client.recvfrom(maxsize)
print('At', datetimenow(), server, 'said', data)
client.close()
由于UDP不可靠,我们准备使用TCP(传输控制协议)。TCP用来进行长时间连接,比如Web。TCP按照发送的顺序传输数据。如果出现任何问题,它会尝试重新传输。
# tcp_server.py
from datetime import datetime
import socket
address = ('localhost', 6789)
maxsize = 1000
print('Starting the server at', datetime.now())
print('Waiting for a client to call.')
# SOCK_STREAM 流协议TCP
server = socket.socket(socket.AF_INEF, socket.SOCK_STREAM)
server.bind(address)
# 表示最多可以和5个客户端连接,超过5个就会拒绝
server.listen(5)
# 表示接受第一个到达的消息
client, addr = server.accept()
# 指定最大的可接受消息长度为1000字节
data = client.recv(max_size)
print('At', datetime.now(), client, 'said', data)
client.sendall(b'Are you talking to me?')
client.close()
server.close()
tcp_client.py
import socket
from datetime import datetime
address = ('localhost', 6789)
max_size = 1000
print('Starting the client at', datetime.now())
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 使用connect()来建立流。使用UDP时不需要这么做,因为每个数据报都是直接暴露在互联网中。
client.connect(address)
client.sendall(b'Hey')
data = client.recv(max_size)
print('At', datetime.now(), 'someone replied', data)
# 关闭连接client,某个套接字
client.close()
# 关闭服务器
server.close()
python套接字编程教程
ZeroMQ是一个库,有时候被称为打了激素的套接字(sockets on steroids),ZeroMQ套接字实现了很多你需要但是普通套接字没有的功能:
ZeroMQ就像乐高积木,我们都知道用很少的乐高积木就能搭建出很多东西。在本例中,你可以用很少几个套接字类型和模型来构建网络。下面这些“乐高积木”是ZeroMQ套接字类型,看起来很像之前说过的网络模型:
首先安装Python的ZeroMQ库:
$ pip install pyzmq
最简单的模式是一个请求-响应对。这是同步的:一个套接字发送请求,另一个发送响应。
# zmq_server.py
# 这个示例代码会从一个发送者接收请求并发送响应。消息可以非常长——ZeroMQ会处理这些细节
import zmq
host = '127.0.0.1'
port = 6789
# 创建一个Context对象:这是一个能够保存状态的ZeroMQ对象。
context = zmq.Context()
# 创建一个REP(用于响应)类型的ZeroMQ套接字
server = context.socket(zmq.REP)
# 调用bind(),让它监听特定的IP地址和端口
server.bind("tcp://%s:%s" % (host, port))
while True:
# 等待客户端的下一个请求
request_bytes = server.recv()
request_str = request_bytes.decode('utf-8')
pirnt("That voice in my head says: %s" % request_str)
reply_str = "Stop saying: %s" % request_str
reply_bytes = bytes(reply_str, 'utf-8')
server.send(reply_bytes)
# zmq_client.py
import zmq
host = '127.0.0.1'
port = 6789
context = zmq.Context()
client = context.socket(zmq.REQ)
client.connect("tcp://%s:%s" % (host, port))
for num in range(1, 6):
request_str = "message #%s" % num
request_bytes = request_str.encode('utf-8')
client.send(request_bytes)
reply_bytes = client.recv()
reply_str = reply_bytes.decode('utf-8')
print("Send %s, received %s" % (request_str, reply_str))
现在是时候启动它们了。和普通套接字不同一点是,你可以用任何顺序启动服务器和客户端(因为有重连机制?)。
$ python zmq_server.py &
$ python zmq_client.py
注意:
消息需要用字节字符串形式发送,所以需要把示例中的字符串用UTF-8格式编码。你可以发送任意类型的消息,只要把它转换成bytes就行。我们的消息是简单的文本字符串,所以encode()和decode()可以实现文本字符串和字节字符串的转换。如果你的消息包含其他数据类型,可以使用类似MessagePack的库来处理。
由于任何数量的REQ客户端都可以connect()到一个REP服务器,即使是基础的请求-响应模式也可以实现一些有趣的通信模式。服务器是同步的,一次只能处理一个请求,但是并不会丢弃这段时间到达的其他请求。ZeroMQ会在触发某些限制之前一直缓存这些消息,知道它们被处理;这就是ZeroMQ中的Q的意思。Q表示队列,M表示消息,Zero表示不需要任何消息发布者。
虽然ZeroMQ不需要任何核心分发者(中间人),但是如果需要,你可以搭建一个。可以使用DEALER和ROUTER套接字异步连接到多个源和/或目标。
多个REQ套接字可以连接到一个ROUTER(异步响应,监听请求事件)。就像很多浏览器连接到一个代理服务器,后者连接到一个Web服务器群。你可以根据需要添加任意数量的客户端和服务器。
REQ套接字只能和ROUTER套接字连接;DEALER可以和后面的多个REP套接字连接。(?) ZeroMQ会处理具体的细节部分,确保请求的负载均衡并把响应发送给正确的目标。
另一种网络化模式被称为通风口,使用PUSH套接字来发送异步任务,使用PULL套接字来收集结果。(?)
最后一个需要介绍的ZeroMQ特性是它可以实现扩展和收缩,只要改变创建的套接字连接类型即可:
http://blog.csdn.net/kobejayandy/article/details/20294909
https://stackoverflow.com/questions/10156388/what-are-the-alternatives-to-zeromq-for-moving-protocol-buffer-payloads-around
http://blog.csdn.net/maixia24/article/details/38392457
http://www.infoq.com/cn/news/2013/11/netty4-twitter
http://grokbase.com/t/zeromq/zeromq-dev/1447cdh85e/yahoo-shows-big-performance-improvement-by-switching-storm-from-zeromq-to-netty
http://blog.csdn.net/wzhg0508/article/details/42012637
https://stackoverflow.com/questions/23613654/can-netty-be-integrated-with-zeromq
https://github.com/spotify/netty-zmtp
有时候你需要深入网络流中处理字节。你可以想要调试Web API或者追踪一些安全问题。scapy库是一个优秀的Python数据包分析工具,比编写和调试C程序简单很多。实际上,它是一门简单的用来构建和分析数据包的语言。
注意:
标准库中有以下这些Email模块:
如果你想编写自己的Python SMTP服务器,可以试试smtpd
Lamson是一个纯Python的SMTP服务器,可以在数据库中存储邮件,甚至可以过滤垃圾邮件。
标准的ftplib模块可以使用文件传输协议(FTP)来发送字节。
最简单的API是一个Web接口,可以提供类似JSON或者XML的结构化数据,而不是纯文本或者HTML。API既可以做的非常简单也可以是一套成熟的RESTful API。(普通Api和RESTful Api的区别
)
很多网站API需要通过token访问。网站可以通过token来判断谁在获取数据,也可以用它来限制请求频率。
本书中的很多示例都是介绍如何在同一台机器上调用Python代码,通常还是在同一个进程中。但是Python的能力远不止这些,你可以调用其他机器上的代码,就像它们在本地一样。通过网络连接的一组计算机可以让你操作更多进程和/或线程。
远程过程调用(RPC)看起来和普通的函数一样,但其实运行在通过网络连接的远程机器上。
RPC面向的是函数,而RESTFUL面向的是资源。RESTful API需要通过URL编码参数或者请求体来调用,但是RPC函数是在自己的机器上调用。(更多)
客户端:
远程机器:
RPC是一种非常流行的技术,有很多种实现方式。
标准库中包含一种RPC实现,xmlrpc,使用XML作为传输格式。
# xmlrpc_server.py
from xmlrpc.server import SimpleXMLRPCServer
def double(num):
return num*2
# 服务器的一个地址和端口
server = SimpleXMLRPCServer(("localhost", 6789))
# 注册函数
server.register_function(double, "double")
# 启动服务器并等待
server.serve_forever()
# xmlrpc_client.py
import xmlrpc.client
# 客户端通过ServerProxy和服务器连接且利用http作为传输方式
proxy = xmlrpc.client.ServerProxy("http://localhost:6789/")
num = 7
result = proxy.double(num)
print("Double %s is %s" % (num, result))
常用的传输方式是HTTP和ZeroMQ。(例子1用HTTP)
除了XML外,JSON、Protocol Buffers和MessagePack也是常用的编码方式。(例子1用XML)
有许多基于JSON的Python RPC包,但是它们要么不支持Python3,要么太难用。
这里我们使用MessagePack自己的Python RPC实现
$ pip install msgpack-rpc-python
这条命令还会安装tornado,这是一个基于事件的Python Web服务器,会被这个库用于传送数据。
# msgpack_server.py
from msgpackrpc import Server, Address
class Servers():
def double(self, num):
return num*2
# Services类把它的方法暴露为RPC服务
server = Server(Services())
server.listen(Address("localhost", 6789))
server.start()
# msgpack_client.py
from msgpackrpc import Client, Address
client = Client(Address("localhost", 6789)
num = 8
result = client.call('double', num)
print("Double %s is %s" % (num, result))
使用ZeroMQ代替HTTP来进行内部服务的RPC调用,好处如下:
更多与实现
fabric包可以运行远程或本地命令、上传或者下载文件、用sudo权限运行命令。
这个包使用安全shell(SSH:加密文本协议,基本上已经代替了telnet)来运行远程程序。
你需要把(Python)函数写入一个fabric文件并声明它们应该在远程还是本地执行。之后,使用时需要用fabric程序来运行,需要指定目标远程机器和目标函数。它比RPC简单舵。
fabric的作者正在合并一些和Python3相关的改动
$ pip install fabric
可以不使用SSH,直接用fabric运行本地Python代码。
# fab1.py
# 本地python代码
def iso():
from datetime import date
print(date.today().isoformat())
$ fab -f fab1.py -H localhost iso
注意:
它的工作原理和之前的RPC有点像。
可以不使用SSH,直接用fabric运行本地shell命令。
# fab2.py
from fabric.api import local
def iso():
loacl('date -u')
$ fab -f fab1.py -H localhost iso
要在远程运行外部程序,被访问机器必须运行SSH服务器。在Unix类系统中,服务器是sshd;service sshd status可以检查服务器是否启动,如果需要service sshd start来启动它。
# fab3.py
# 该文件在调用者机器上
from fabric.api import run
def iso():
run('date -u')
fabric在遇到run()时会使用SSH连接命令行中-H指定的主机,并在该主机上运行fab3.py中的iso函数。
调用者
$ fab -f fab3.py -H hostip iso
可以将密码写入fabric文件:
# fab4.py
from fabric.api import run
from fabric.context_managers import env
env.password = "your password goes here"
def iso():
run('date -u')
把密码放在代码中非常不安全。更好的方法是使用公钥和密钥配置SSH,可以使用ssh-keygen。S
可以在本机上测试外部运行程序的逻辑:
$ fab -f fab3.py -H localhost iso
Salt最初的目的是实现远程运行,但是后来变成了一个完整的系统管理平台。它是基于ZeroMQ开发的,不是基于SSH,因此可以扩展到上千台服务器。
Salt还没有兼容Python 3。
salt和ansible都包含了fabric的功能,可以进行初始化配置、部署和远程执行。
着重点在于计算
Hadoop流就像Unix的管道一样,把每一步产生的数据流直接传输给下一步,这样就可以免于中间数据存储到磁盘。
Python Hadoop框架教程
Spotify开源了自己处理Hadoop流的Python部件Luigi,不过现在还不兼容Python 3
Spark的目标是相比Hadoop大大加快运行速度。它可以读取和处理所有Hadoop的数据结构和格式。
Disco使用Python来完成MapReduce过程,使用Erlang完成通信部分。不过,只可惜无法使用pip来安装。
分布式计算的八大误解: