本章内容
- Gevent协程
- Select、Poll、Epoll异步IO与事件驱动
- Paramiko模块
- RabbitMQ队列
- Redis、Memcached缓存
- Twsited网络框架
一、Gevent协程
协程:
协程,又称微线程,纤程。英文名Coroutine。一句话说明什么是线程:协程是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,回复先前保存的寄存器上下文和栈。因此:
协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所逻辑流的位置。
协程的好处:
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销方便切换控制流,简化编程模型
- “原子操作(atomic operation)是不需要synchronized”,所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间爱你不会有任何context switch(切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
- 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
- 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU的多个核用上,协程需要和进程配合才能运行在多CPU上,当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
使用yield实现协程操作例子
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# Author:Lyon
def consumer(name):
print('---->staring eating baozi...')
while True:
new_baozi = yield #直接返回 generator生成器
print("[%s] is eating baozi %s"%(name,new_baozi))
def producer():
r = con.__next__()
r = con2.__next__()
n = 0
while n <5:
n+=1
con.send(n)#唤醒生成器的同时传入一个参数 new_baozi=1
con2.send(n)
print("\033[32;1m[producer]\033[0m is making baozi %s"%n)
if __name__ =='__main__':
con = consumer("c1")
con2 = consumer('c2')
p = producer()
通过上面的例子我们并不能清晰的看出到底什么是协程,首先我们先给协程一个标准定义,即符合什么条件就能称为协程:
- 必须在只有一个单线程里实现并发
- 修改共享数据不需要加锁
- 用户程序里自己保存多个控制流的上下文栈
- 一个协程遇到IO操作自动切换到其他协程
基于上面这4点定义,我们刚才用yield实现的程并不能算是合格的线程,因为它有一点功能没有实现,看后面正真的协程,自己想吧。哈哈!不过有一点就是,yield这种方法就是用生成器来实现切换上下文的功能。。
Greenlet
greenlet是一个用C实现的协程模块,相比python自带的yield,它可以使你在任意函数之间随意切换,而不需要把这个函数先声明generator
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4
5 from greenlet import greenlet
6 def func1():
7 print(12)
8 gr2.switch()
9 print(34)
10 gr2.switch()
11
12 def func2():
13 print(56)
14 gr1.switch()
15 print(78)
16
17 #创建两个协程
18 gr1 = greenlet(func1)
19 gr2 = greenlet(func2)
20 gr1.switch() #手动切换
我们发现用起来至少比generator简单了,但是还有一个问题,就是如果遇到IO操作,自动切换怎么弄?
Gevent
Gevent是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet,它是以C扩展模块形式接入python的轻量级协程。Greenlet全部运行在主程序操作系统进程的内部,但他们被协作式的调度。
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4 import gevent
5
6 def func():
7 print("\033[31;1m我是第一个\033[0m")
8 gevent.sleep(2)
9 print("\033[32;1m我是第三个\033[0m")
10
11 def func2():
12 print("\033[33;1m我是第二个\033[0m")
13 gevent.sleep(2)
14 print("\033[34;1m我是第四个\033[0m")
15
16 gevent.joinall([
17 gevent.spawn(func),
18 gevent.spawn(func2),
19 #gevent.spawn(func3),
20 ])
看输出:
好看吧,贼好看。
同步与异步的性能区别
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4 import gevent
5
6 def task(pid):
7 gevent.sleep(0.5)
8 print("Task %s done"%pid)
9
10 def synchronous():
11 for i in range(1,10):
12 task(i)
13
14 def asynchronous():
15 threads = [gevent.spawn(task,i)for i in range(10)]
16 gevent.joinall(threads)
17
18 print("Synchronous:")
19 synchronous()
20 print("Asynchronous:")
21 asynchronous()
一运行你就发现,Synchronous(同步):运行起来明显慢一些。Asynchronous(异步):那则是相当的快。这是为什么?打个比方,有一项工作需要重复一个动作10次,同步呢,就是一个人做10次,而异步呢,就是10个人同时开始做一次,你说哪个快?当然是一个人做10次快呀(我反手就是一巴掌!!打屎你!!)
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。初始化的greenlet列表放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程孩子会在所有greenlet执行完后才会继续向下走。
遇到IO阻塞时会自动切换任务
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4 from gevent import monkey;monkey.patch_all()
5 import gevent
6 from urllib.request import urlopen
7
8 def f(url):
9 print("GET:%s"%url)
10 resp =urlopen(url)
11 data =resp.read()
12 print("%d bytes receibed from %s"%(len(data),url))
13 gevent.joinall([
14 gevent.spawn(f,'https://www.python.org/'),
15 gevent.spawn(f,'https://www.yahoo.com/'),
16 gevent.spawn(f,'https://github.com/'),
17 ])
通过gevent实现单线程下的多socket并发
server side
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4
5 import sys
6 import socket,time,gevent
7 from gevent import socket,monkey
8 monkey.patch_all()
9
10 def server(port):
11 s = socket.socket()
12 s.bind(('0.0.0.0',port))
13 s.listen(500)
14 while True:
15 cli,addr = s.accept()
16 gevent.spawn(handle_requset,cli)
17
18 def handle_requset(conn):
19 try:
20 while True:
21 data = conn.recv(1024)
22 print("recv:",data)
23 conn.send(data)
24 if not data:
25 conn.shutdown(socket.SHUT_WR)
26 except Exception as ex:
27 print(ex)
28 finally:
29 conn.close()
30 if __name__=='__main__':
31 server(8001)
client side
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4
5 import socket
6
7 HOST = 'localhost'
8 PORT = 8001
9 s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
10 s.connect((HOST,PORT))
11 while True:
12 msg = bytes(input(">>:"),encoding="utf-8")
13 s.sendall(msg)
14 data = s.recv(1024)
15 #print(data)
16 print('Received',repr(data))
17
18 s.close()
1 #!/usr/bin/env python
2 # -*- coding:utf-8 -*-
3 # Author:Lyon
4 import socket
5 import threading
6
7 def sock_conn():
8 client = socket.socket()
9 client.connect(("localhost",8001))
10 count = 0
11 while True:
12 client.send(("hello %s"%count).encode("utf-8"))
13 data = client.recv(1024)
14 print("[%s]recv from server:"%threading.get_ident(),data.decode())
15 count +=1
16 client.close()
17
18 for i in range(100):
19 t = threading.Thread(target=sock_conn)
20 t.start()
二、Select、Pol、Epoll异步IO与事件驱动
通常,我们写服务器处理模型的程序时,有以下几种模型:
(1)每收到一个请求,创建一个新的进程,来处理该请求;
(2)每收到一个请求,创建一个新的线程,来处理该请求;
(3)每收到一个请求,放入一个事件列表,让主进程通过非阻塞I/O方式来处理请求
上面的几种方式,个有千秋:
第(1)种方式,由于创建新的进程的开销比较大,所以,会导致服务器性能比较差,但实现比较简单。
第(2)种方式,由于要涉及到线程的同步,有可能会面临死锁等问题。
第(3)种方式,在写应用程序代码时,逻辑比前面两种都复杂。
综合考虑各方面因素,一般普遍认为第(3)种方式是大多数网络服务器采用的方式。
事件驱动模型
在UI编程中,昶昶要对鼠标点击进行相应,首先如何获得鼠标点击呢?
方式一:创建一个线程,该线程一直循环检测是否有鼠标点击,那么这个方式有以下几个缺点:
1.CPU资源浪费,可能鼠标点击的频率非常小,但是扫描线程还是会一直循环检测,这会造成很多的CPU资源浪费;如果扫描鼠标点击的接口是阻塞的呢?
2.如果是堵塞的,又会出现下面这样的问题,如果我们不但要扫描鼠标点击,还要扫描键盘是否按下,由于扫描鼠标时被堵塞了,那么可能永远不会去扫描键盘;
3.如果一个循环需要扫描的设备非常多,这又会引来响应时间的问题;
所以,该方式是非常不好的。
方式二:就是事件驱动模型
目前大部分的UI编程都是事件驱动模型,如很多UI平台都会提供onClick()事件,这个事件就代表鼠标按下事件。事件驱动模型大体思路如下:
1.有一个事件(消息)队列;
2.鼠标按下时,往这个队列中增加一个点击事件(消息);
3.有个循环,不断从队列取出事件,根据不容的事件,调用不同的函数,如onClick()、onKeyDown()等;
4.事件(消息)一般都各自保存各自的处理函数指针,这样,每个消息都有独立的处理函数;
事件驱动编程是一种编程范式,这是程序的执行流由外部事件来决定。它的特点是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。另外两种常用的编程范式是(单线程)同步以及多线程编程。
让我们用例子来比较和对比一下单线程、多线程以及事件驱动编程模型。下图展示了随着事件的推移,这三种模式下程序所做的工作。这个程序有3个任务需要完成,每个任务都在等待I/O操作时阻塞自身。阻塞在I/O操作上所花费的事件已经用灰色框标示出来了。
在单线程同步模型中,任务按照顺序执行。如果某个任务因为I/O而阻塞,其他所有的任务都必须等待,直到它完成之后他们才能一次执行。这种明确的执行顺序和串行化处理的行为是很容易推断得出的。如果任务之间并没有互相依赖的关系,但仍然需要互相等待的话这就使得程序不必要的降低了运行速度。
在多线程版本中,这3个任务分别在独立的线程中执行。这些线程由操作系统来管理,在多处理器系统上可以并行处理,或者在单处理器系统上交错执行。这使得当某个线程阻塞在某个资源的同时其他线程得以继续执行。与完成类似功能的同步程序相比,这种方式更有效率,但程序员必须写代码来保护共享资源,防止其被多个线程同时访问。多线程程序更加难以推断,因为这类程序不得不通过线程同步机制如锁、可重入函数、线程局部存储或者其他机制来处理线程安全问题,如果实现不当就会导致出现微妙且令人痛不欲生的bug。
在事件驱动版本的程序中,3个任务交错执行,但仍然在一个单独的线程控制中。当处理I/O或者其他昂贵的操作时,注册一个回调到事件循环中,然后当I/O操作完成时继续执行。回调描述了该如何处理某个事件。事件循环轮询所有的事件,当事件到来时将它们分配给等待处理事件的回调函数。这种方式让程序尽可能的得以执行而不需要用到额外的线程。事件驱动型程序比多线程程序更容易推断出行为,因为程序员不需要关心线程安全问题。
当我们面对如下的环境时,事件驱动模型通常是一个好的选择:
- 程序中有许多任务,而且...(前面才是重点不是么,而且我也不知道...有什么啊)
- 任务之间高度独立(因此它们不需要互相通信,或者等待彼此)而且...
- 在等待事件到来时,某些任务会阻塞。
当应用程序需要在任务间共享变数的数据时,这也是一个不错的选择,因为这里不需要采用同步处理。
网路应用程序通常都有上述这些特点,这使得它们能够很好的契合事件驱动编程模型。
在上面的事件驱动模型中,只要一遇到IO就注册一个事件,然后主程序就可以继续干其他的事情了,只到IO处理完毕后,继续回复之前中断的任务,这本质上是怎么实现的呢?
Select\Poll\Epoll异步IO
首先列一下,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()时便得到通知。
Python select
Python的select()方法直接调用操作系统的IO接口,它监控sockets,open files, and pipes(所有带fileno()方法的文件句柄)何时变成
readable 和writeable, 或者通信错误,select()使得同时监控多个连接变的简单,并且这比写一个长循环来等待和监控多客户端连接要高效,因为select直接通过操作系统提供的C的网络接口进行操作,而不是通过Python的解释器。
select多并发socket例子
1 #_*_coding:utf-8_*_
2 __author__ = 'Alex Li'
3
4 import select
5 import socket
6 import sys
7 import queue
8
9
10 server = socket.socket()
11 server.setblocking(0)
12
13 server_addr = ('localhost',10000)
14
15 print('starting up on %s port %s' % server_addr)
16 server.bind(server_addr)
17
18 server.listen(5)
19
20
21 inputs = [server, ] #自己也要监测呀,因为server本身也是个fd
22 outputs = []
23
24 message_queues = {}
25
26 while True:
27 print("waiting for next event...")
28
29 readable, writeable, exeptional = select.select(inputs,outputs,inputs) #如果没有任何fd就绪,那程序就会一直阻塞在这里
30
31 for s in readable: #每个s就是一个socket
32
33 if s is server: #别忘记,上面我们server自己也当做一个fd放在了inputs列表里,传给了select,如果这个s是server,代表server这个fd就绪了,
34 #就是有活动了, 什么情况下它才有活动? 当然 是有新连接进来的时候 呀
35 #新连接进来了,接受这个连接
36 conn, client_addr = s.accept()
37 print("new connection from",client_addr)
38 conn.setblocking(0)
39 inputs.append(conn) #为了不阻塞整个程序,我们不会立刻在这里开始接收客户端发来的数据, 把它放到inputs里, 下一次loop时,这个新连接
40 #就会被交给select去监听,如果这个连接的客户端发来了数据 ,那这个连接的fd在server端就会变成就续的,select就会把这个连接返回,返回到
41 #readable 列表里,然后你就可以loop readable列表,取出这个连接,开始接收数据了, 下面就是这么干 的
42
43 message_queues[conn] = queue.Queue() #接收到客户端的数据后,不立刻返回 ,暂存在队列里,以后发送
44
45 else: #s不是server的话,那就只能是一个 与客户端建立的连接的fd了
46 #客户端的数据过来了,在这接收
47 data = s.recv(1024)
48 if data:
49 print("收到来自[%s]的数据:" % s.getpeername()[0], data)
50 message_queues[s].put(data) #收到的数据先放到queue里,一会返回给客户端
51 if s not in outputs:
52 outputs.append(s) #为了不影响处理与其它客户端的连接 , 这里不立刻返回数据给客户端
53
54
55 else:#如果收不到data代表什么呢? 代表客户端断开了呀
56 print("客户端断开了",s)
57
58 if s in outputs:
59 outputs.remove(s) #清理已断开的连接
60
61 inputs.remove(s) #清理已断开的连接
62
63 del message_queues[s] ##清理已断开的连接
64
65
66 for s in writeable:
67 try :
68 next_msg = message_queues[s].get_nowait()
69
70 except queue.Empty:
71 print("client [%s]" %s.getpeername()[0], "queue is empty..")
72 outputs.remove(s)
73
74 else:
75 print("sending msg to [%s]"%s.getpeername()[0], next_msg)
76 s.send(next_msg.upper())
77
78
79 for s in exeptional:
80 print("handling exception for ",s.getpeername())
81 inputs.remove(s)
82 if s in outputs:
83 outputs.remove(s)
84 s.close()
85
86 del message_queues[s]
1 #_*_coding:utf-8_*_
2 __author__ = 'Alex Li'
3
4
5 import socket
6 import sys
7
8 messages = [ b'This is the message. ',
9 b'It will be sent ',
10 b'in parts.',
11 ]
12 server_address = ('localhost', 10000)
13
14 # Create a TCP/IP socket
15 socks = [ socket.socket(socket.AF_INET, socket.SOCK_STREAM),
16 socket.socket(socket.AF_INET, socket.SOCK_STREAM),
17 ]
18
19 # Connect the socket to the port where the server is listening
20 print('connecting to %s port %s' % server_address)
21 for s in socks:
22 s.connect(server_address)
23
24 for message in messages:
25
26 # Send messages on both sockets
27 for s in socks:
28 print('%s: sending "%s"' % (s.getsockname(), message) )
29 s.send(message)
30
31 # Read responses on both sockets
32 for s in socks:
33 data = s.recv(1024)
34 print( '%s: received "%s"' % (s.getsockname(), data) )
35 if not data:
36 print(sys.stderr, 'closing socket', s.getsockname() )
简单的介绍一下,等我以后把这个完全弄透再写自己的。详细请看下:
http://www.cnblogs.com/alex3714/p/4372426.html
番外篇 http://www.cnblogs.com/alex3714/articles/5876749.html
selectors模块
此模块允许高层次和高效率的I / O复用,建立在选择模块原语。用户被鼓励使用这个模块代替,除非他们想要精确控制操作系统级原语使用。
1 import selectors
2 import socket
3
4 sel = selectors.DefaultSelector()
5
6 def accept(sock, mask):
7 conn, addr = sock.accept() # Should be ready
8 print('accepted', conn, 'from', addr)
9 conn.setblocking(False)
10 sel.register(conn, selectors.EVENT_READ, read)
11
12 def read(conn, mask):
13 data = conn.recv(1000) # Should be ready
14 if data:
15 print('echoing', repr(data), 'to', conn)
16 conn.send(data) # Hope it won't block
17 else:
18 print('closing', conn)
19 sel.unregister(conn)
20 conn.close()
21
22 sock = socket.socket()
23 sock.bind(('localhost', 10000))
24 sock.listen(100)
25 sock.setblocking(False)
26 sel.register(sock, selectors.EVENT_READ, accept)
27
28 while True:
29 events = sel.select()
30 for key, mask in events:
31 callback = key.data
32 callback(key.fileobj, mask)
三、Paramiko模块
Paramiko模块基于SSH用于连接远程服务器并执行相关操作
基于用户名密码连接:
1 import paramiko
2
3 # 创建SSH对象
4 ssh = paramiko.SSHClient()
5 # 允许连接不在know_hosts文件中的主机
6 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
7 # 连接服务器
8 ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', password='123')
9
10 # 执行命令
11 stdin, stdout, stderr = ssh.exec_command('df')
12 # 获取命令结果
13 result = stdout.read()
14
15 # 关闭连接
16 ssh.close()
基于公钥密钥连接:
1 import paramiko
2
3 private_key = paramiko.RSAKey.from_private_key_file('/home/auto/.ssh/id_rsa')
4
5 # 创建SSH对象
6 ssh = paramiko.SSHClient()
7 # 允许连接不在know_hosts文件中的主机
8 ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
9 # 连接服务器
10 ssh.connect(hostname='c1.salt.com', port=22, username='wupeiqi', key=private_key)
11
12 # 执行命令
13 stdin, stdout, stderr = ssh.exec_command('df')
14 # 获取命令结果
15 result = stdout.read()
16
17 # 关闭连接
18 ssh.close()
四、RabbitMQ队列
模块安装
实现最简单的队列通信
send端
1 #!/usr/bin/env python
2 import pika
3
4 connection = pika.BlockingConnection(pika.ConnectionParameters(
5 'localhost'))
6 channel = connection.channel()
7
8 #声明queue
9 channel.queue_declare(queue='hello')
10
11 #n RabbitMQ a message can never be sent directly to the queue, it always needs to go through an exchange.
12 channel.basic_publish(exchange='',
13 routing_key='hello',
14 body='Hello World!')
15 print(" [x] Sent 'Hello World!'")
16 connection.close()
receive端
1 #_*_coding:utf-8_*_
2 __author__ = 'Alex Li'
3 import pika
4
5 connection = pika.BlockingConnection(pika.ConnectionParameters(
6 'localhost'))
7 channel = connection.channel()
8
9
10 #You may ask why we declare the queue again ‒ we have already declared it in our previous code.
11 # We could avoid that if we were sure that the queue already exists. For example if send.py program
12 #was run before. But we're not yet sure which program to run first. In such cases it's a good
13 # practice to repeat declaring the queue in both programs.
14 channel.queue_declare(queue='hello')
15
16 def callback(ch, method, properties, body):
17 print(" [x] Received %r" % body)
18
19 channel.basic_consume(callback,
20 queue='hello',
21 no_ack=True)
22
23 print(' [*] Waiting for messages. To exit press CTRL+C')
24 channel.start_consuming()
更多有关RabbitMQ,点击我
五、Redis、Memcached缓存
Memcached
Memcached 是一个高性能的分布式内存对象缓存系统,用于动态Web应用以减轻数据库负载。它通过在内存中缓存数据和对象来减少读取数据库的次数,从而提高动态、数据库驱动网站的速度。Memcached基于一个存储键/值对的hashmap。其守护进程(daemon )是用C写的,但是客户端可以用任何语言来编写,并通过memcached协议与守护进程通信。
操作及更多,--->>(memcached)
redis使用
这我还是不写吧,写了也是白写,还不如直接看--->>(redis)
六、Twsited网络框架
Twisted是一个事件驱动的网络框架,其中包含了诸多功能,例如:网络协议、线程、数据库管理、网络操作、电子邮件等。
保持着整理笔记的原则,等完全理解,再写自己的东西。
同样,方便反复学习:http://www.cnblogs.com/alex3714/articles/5248247.html
Twisted深入
http://krondo.com/an-introduction-to-asynchronous-programming-and-twisted/
http://blog.csdn.net/hanhuili/article/details/9389433
注:本文仅为学习笔记、摘要。
详细来源:http://www.cnblogs.com/alex3714/articles/5248247.html
http://www.cnblogs.com/wupeiqi/articles/5095821.html