1 #事件驱动之鼠标点击事件注册 2 3 4 5 6 7执行结果:Title 8 9 10 11 12点我呀
13 14 15 20 21 22
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #Author: nulige 4 5 import socket 6 7 sk=socket.socket() 8 9 sk.bind(("127.0.0.1",8080)) 10 11 sk.listen(5) 12 13 while 1: 14 conn,addr=sk.accept() 15 16 while 1: 17 conn.send("hello client".encode("utf8")) 18 data=conn.recv(1024) 19 print(data.decode("utf8"))client.py
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 #Author: nulige 4 5 import socket 6 7 sk=socket.socket() 8 9 sk.connect(("127.0.0.1",8080)) 10 11 while 1: 12 data=sk.recv(1024) 13 print(data.decode("utf8")) 14 sk.send(b"hello server")当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。所以,blocking IO的特点就是在IO执行的两个阶段都被block了。2、non-blocking IO(非阻塞IO)原理图:
1 import time 2 import socket 3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 sk.bind(('127.0.0.1',6667)) 5 sk.listen(5) 6 sk.setblocking(False) #设置成非阻塞状态 7 while True: 8 try: 9 print ('waiting client connection .......') 10 connection,address = sk.accept() # 进程主动轮询 11 print("+++",address) 12 client_messge = connection.recv(1024) 13 print(str(client_messge,'utf8')) 14 connection.close() 15 except Exception as e: #捕捉错误 16 print (e) 17 time.sleep(4) #每4秒打印一个捕捉到的错误客户端:
1 import time 2 import socket 3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 5 while True: 6 sk.connect(('127.0.0.1',6667)) 7 print("hello") 8 sk.sendall(bytes("hello","utf8")) 9 time.sleep(2) 10 break缺点:1、发送了太多系统调用数据2、数据处理不及时3、IO multiplexing(IO多路复用)IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。IO多路复用的三种方式:1、select--->效率最低,但有最大描述符限制,在linux为1024。2、poll ---->和select一样,但没有最大描述符限制。3、epoll --->效率最高,没有最大描述符限制,支持水平触发与边缘触发。IO多路复用的优势:同时可以监听多个连接,用的是单线程,利用空闲时间实现并发。
1 #server.py 2 3 import socket 4 import select 5 sk=socket.socket() 6 sk.bind(("127.0.0.1",9904)) 7 sk.listen(5) 8 9 while True: 10 # sk.accept() #文件描述符 11 r,w,e=select.select([sk,],[],[],5) #输入列表,输出列表,错误列表,5: 是监听5秒 12 for i in r: #[sk,] 13 conn,add=i.accept() 14 print(conn) 15 print("hello") 16 print('>>>>>>')client.py
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",9904)) 6 7 while 1: 8 inp=input(">>").strip() 9 sk.send(inp.encode("utf8")) 10 data=sk.recv(1024) 11 print(data.decode("utf8"))IO多路复用中的两种触发方式:水平触发:如果文件描述符已经就绪可以非阻塞的执行IO操作了,此时会触发通知.允许在任意时刻重复检测IO的状态, 没有必要每次描述符就绪后尽可能多的执行IO.select,poll就属于水平触发。边缘触发:如果文件描述符自上次状态改变后有新的IO活动到来,此时会触发通知.在收到一个IO事件通知后要尽可能 多的执行IO操作,因为如果在一次通知中没有执行完IO那么就需要等到下一次新的IO活动到来才能获取到就绪的描述 符.信号驱动式IO就属于边缘触发。epoll:即可以采用水平触发,也可以采用边缘触发。1、水平触发只有高电平或低电平的时候才触发1-----高电平---触发0-----低电平---不触发示例:server服务端
1 #水平触发 2 import socket 3 import select 4 sk=socket.socket() 5 sk.bind(("127.0.0.1",9904)) 6 sk.listen(5) 7 8 while True: 9 r,w,e=select.select([sk,],[],[],5) #input输入列表,output输出列表,erron错误列表,5: 是监听5秒 10 for i in r: #[sk,] 11 print("hello") 12 13 print('>>>>>>')client客户端
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",9904)) 6 7 while 1: 8 inp=input(">>").strip() 9 sk.send(inp.encode("utf8")) 10 data=sk.recv(1024) 11 print(data.decode("utf8"))2、边缘触发1---------高电平--------触发0---------低电平--------触发IO多路复用优势:同时可以监听多个连接示例:select可以监控多个对象服务端
1 #优势 2 import socket 3 import select 4 sk=socket.socket() 5 sk.bind(("127.0.0.1",9904)) 6 sk.listen(5) 7 inp=[sk,] 8 9 while True: 10 r,w,e=select.select(inp,[],[],5) #[sk,conn],5是每隔几秒监听一次 11 12 for i in r: #[sk,] 13 conn,add=i.accept() #发送系统调用 14 print(conn) 15 print("hello") 16 inp.append(conn) 17 # conn.recv(1024) 18 print('>>>>>>')客户端:
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",9904)) 6 7 while 1: 8 inp=input(">>").strip() 9 sk.send(inp.encode("utf8")) 10 data=sk.recv(1024) 11 print(data.decode("utf8"))多了一个判断,用select方式实现的并发示例:实现并发聊天功能 (select+IO多路复用,实现并发)服务端:
1 import socket 2 import select 3 sk=socket.socket() 4 sk.bind(("127.0.0.1",8801)) 5 sk.listen(5) 6 inputs=[sk,] 7 while True: #监听sk和conn 8 r,w,e=select.select(inputs,[],[],5) #conn发生变化,sk不变化就走else 9 print(len(r)) 10 #判断sk or conn 谁发生了变化 11 for obj in r: 12 if obj==sk: 13 conn,add=obj.accept() 14 print(conn) 15 inputs.append(conn) 16 else: 17 data_byte=obj.recv(1024) 18 print(str(data_byte,'utf8')) 19 inp=input('回答%s号客户>>>'%inputs.index(obj)) 20 obj.sendall(bytes(inp,'utf8')) 21 22 print('>>',r)客户端:
1 import socket 2 sk=socket.socket() 3 sk.connect(('127.0.0.1',8801)) 4 5 while True: 6 inp=input(">>>>") 7 sk.sendall(bytes(inp,"utf8")) 8 data=sk.recv(1024) 9 print(str(data,'utf8'))执行结果:先运行服务端,再运行多个客户端,就可以聊天啦。(可以接收多个客户端消息)
1 #server 2 >> [4、Asynchronous I/O(异步IO)] 3 1 4 hello 5 回答1号客户>>>word 6 >> [ ] 7 1 8 9 #clinet 10 >>>>hello 11 word
select_module.py 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) #conn绑定的是read 11 12 def read(conn, mask): 13 try: 14 data = conn.recv(1000) # Should be ready 15 if not data: 16 raise Exception 17 print('echoing', repr(data), 'to', conn) 18 conn.send(data) # Hope it won't block 19 except Exception as e: 20 print('closing', conn) 21 sel.unregister(conn) #解除注册 22 conn.close() 23 24 sock = socket.socket() 25 sock.bind(('localhost', 8090)) 26 sock.listen(100) 27 sock.setblocking(False) 28 #注册 29 sel.register(sock, selectors.EVENT_READ, accept) 30 print("server....") 31 32 while True: 33 events = sel.select() #监听[sock,conn1,conn2] 34 print("events",events) 35 #拿到2个元素,一个key,一个mask 36 for key, mask in events: 37 # print("key",key) 38 # print("mask",mask) 39 callback = key.data #绑定的是read函数 40 # print("callback",callback) 41 callback(key.fileobj, mask) #key.fileobj=sock,conn1,conn2client.py
1 import socket 2 3 sk=socket.socket() 4 5 sk.connect(("127.0.0.1",8090)) 6 while 1: 7 inp=input(">>>") 8 sk.send(inp.encode("utf8")) #发送内容 9 data=sk.recv(1024) #接收信息 10 print(data.decode("utf8")) #打印出来执行结果:先运行select_module.py,再运行clinet.py
1 #server 2 3 server.... 4 events [(SelectorKey(fileobj=6、I/O多路复用的应用场景(1)当客户处理多个描述字时(一般是交互式输入和网络套接口),必须使用I/O复用。(2)当一个客户同时处理多个套接口时,而这种情况是可能的,但很少出现。(3)如果一个TCP服务器既要处理监听套接口,又要处理已连接套接口,一般也要用到I/O复用。(4)如果一个服务器即要处理TCP,又要处理UDP,一般要使用I/O复用。(5)如果一个服务器要处理多个服务或多个协议,一般要使用I/O复用。'''与多进程和多线程技术相比,I/O多路复用技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。'''最后,再举几个不是很恰当的例子来说明这四个IO Model:有A,B,C,D四个人在钓鱼:A用的是最老式的鱼竿,所以呢,得一直守着,等到鱼上钩了再拉杆;【阻塞】B的鱼竿有个功能,能够显示是否有鱼上钩(这个显示功能一直去判断鱼是否上钩),所以呢,B就和旁边的MM聊天,隔会再看看有没有鱼上钩,有的话就迅速拉杆;【非阻塞】C用的鱼竿和B差不多,但他想了一个好办法,就是同时放好几根鱼竿,然后守在旁边,一旦有显示说鱼上钩了,它就将对应的鱼竿拉起来;【同步】, fd=312, events=1, data= ), 1)] 5 accepted from ('127.0.0.1', 57638) 6 events [(SelectorKey(fileobj= , fd=376, events=1, data= ), 1)] 7 echoing b'hello' to 8 events [(SelectorKey(fileobj= , fd=312, events=1, data= ), 1)] 9 accepted from ('127.0.0.1', 57675) 10 events [(SelectorKey(fileobj= , fd=324, events=1, data= ), 1)] 11 echoing b'uuuu' to 12 events [(SelectorKey(fileobj= , fd=324, events=1, data= ), 1)] 13 closing 14 events [(SelectorKey(fileobj= , fd=312, events=1, data= ), 1)] 15 accepted from ('127.0.0.1', 57876) 16 events [(SelectorKey(fileobj= , fd=324, events=1, data= ), 1)] 17 echoing b'welcome' to 18 19 #clinet (启动两个client) 20 >>>hello 21 hello 22 23 >>>welcome 24 welcome