Python网络编程(http协议,IO多路复用、select内核监听)
前言:
什么是IO?
补充昨天HTTP:
from socket import * # 接收请求 # 查看请求 # 返回客户端段请求内容 def handleClient(connfd): request = connfd.recv(4096) # print("***********") # print(request) # print("************") # 按照行切割请求 request_lines = request.splitlines() for line in request_lines: print(line.decode()) try: f = open('index.html') except IOError: response = "HTTP/1.1 303 Not Found\r\n" response += "\r\n" # 空行 response += ''' ************************** Sorry, not found the page. ************************** ''' else: response = "HTTP/1.1 200 OK\r\n" response += '\r\n' response += f.read() finally: # 发送给浏览器 connfd.send(response.encode()) # 创建套接字,调用handleClient完成功能 def main(): # 创建tcp套接字 sockfd = socket() sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sockfd.bind(('0.0.0.0', 8000)) sockfd.listen() while True: print("Listen the port 8000...") connfd, addr = sockfd.accept() # 处理浏览器发来的请求 handleClient(connfd) connfd.close() if __name__ == "__main__": main()
S.splitlines
拆分文件字符串按行分隔
在内存中存在数据交换的操作可以认为是IO操作(输入输出)
例如:
内存与磁盘数据交换:文件读写、数据库更新
内存和终端数据交换:input、print、sys.stdout、sys.stdin、 sys.stder
内存和网络数据交换:网络连接、recv、send、recvfrom
IO秘集程型序:
程序执行中有大量的IO操作,而比较少的CPU运算操作
消耗CPU较少,IO运行时间长
cpu(计算)密集形程序:
程序存在大量的CPU运算,IO操作相对较少
CPU消耗大
IO分类:
1.阻塞IO:
程序运行中遇到IO条件没有达成
或传输情况较慢的情况下会出现阻塞状态
阻塞IO是IO最简单的逻辑情形,也是默认状态
阻塞IO是效率很低的IO状态
阻塞情况:
1.因为IO条件没有达成
IO阻塞函数(input、print、recv、recvfrom)
2.处理IO耗时较长形参阻塞
文件读写、网络数据发送过程
2.非阻塞IO:
在程序运行中遇到IO操作不让其产生阻塞
实现手段:
改变IO事件的属性,使其变为非阻塞
通常会和循环一起使用 进行条件的循环监控
3.IO多路复用
定义:
通过一个监测,可以同时监控多个IO事件的行为,
当那个IO可以执行,让这个IO事件发生
同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件
此时形成多个IO时间都可以操作的现象,不必逐个等待执行
IO准备就绪:
IO事件即将发生时的临界状态是不可逆转的
在程序中存在的IO事件中选择要监测的事件
创建监测,将监测的IO事件注册
等待监测的IO事件发生,判断是什么事件
处理相应的IO
事件驱动IO
异步IO
...
s.setblocking(False)
功能:
将套接字设置为非阻塞状态
参数:
默认为阻塞, 设置为False为非阻塞状态
超时检测:
将原本阻塞的IO设置一个最长阻塞等待时间,
在规定时间内如果达到条件则正常执行,
如果时间到仍未达到条件则结束阻塞。
s.settimeout(sec)
功能:
设置套接字的超时时间
参数:
时间(秒)
select模块
from select import
select 支持:Windows、Linux、Unix
poll 支持:Linux、Unix
epoll 支持:Linux、Unix
rs, ws, xs = select(rlist, wlist, xlist[, timeout])
功能:
监控IO事件 ,阻塞等待监控的IO事件发生
参数:
rlist 列表:
表示存放我们需要等待处理的IO
wlist 列表:
表示存放我们想要主动处理的IO
xlist 列表:
表示存放出错希望去处理的IO
timeout: 超时检测
返回值:
rs 列表:
准备就绪的IO
ws 列表:
准备就绪的IO
xs 列表
准备就绪的IO
* 在处理IO时不要形成死循环,会让一个客户端单独占有服务端
* IO多路复兴形成一种可以同时处理多个IO的效果,效率较高
位运算:
按照二进制位进行位运算操作
& 按为与 | 按位或 ^ 按位异或
<< 左异 >>右移
11 1011
14 1110
& 1010 有0得0
| 1111 有1得1
^ 0101 相同为0不同为1
11 << 2 == 44 右侧补零(乘2乘2次)
14 >> 2 == 3 挤掉右侧的数字(地板除2除2次)
使用:
1.在低层硬件时操作寄存器
2.做标志位的过滤
poll方法实现IO多路复用:
1.创建poll对象:
p = select.poll
2.注册关注的IO:
p.register(s, POLLIN | PLLERR)
不关注:
p.unregister(s)
事件类别:
POLLIN POLLOUT POLLERR POLLHUP POLLPRI
rlist wlist xlist 断开 紧急处理
3.监控IO:
events = p.poll()
功能:监控关注的IO事件
返回值:
返回发生IO事件
events是一个列表[(fileno, evnet), (), ()....]
每个就绪IO对应一个元组(描述符,就绪事件)
IO地图:{s.fileno():s}
4.处理IO事件
poll方法实现IO多路复用:
from socket import * from select import * s = socket() s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) s.bind(('0.0.0.0',8888)) s.listen(5) #创建poll对象 p = poll() #创建地图 fdmap = {s.fileno():s} #添加关注 p.register(s,POLLIN | POLLERR) while True: #进行IO监控 #[(fileno,evnet),...] events = p.poll() for fd,event in events: if fd == s.fileno(): #从地图中找到fd对应的对象 c,addr = fdmap[fd].accept() print("Connect from",addr) #注册新的IO 维护地图 p.register(c,POLLIN) fdmap[c.fileno()] = c else: data = fdmap[fd].recv(1024) if not data: p.unregister(fd) #从关注移除 fdmap[fd].close() del fdmap[fd] #从地图删除 else: print(data.decode()) fdmap[fd].send('收到了'.encode())
select IO多路复用服务器端:
from socket import * from select import select # 创建套接字 s = socket() # 设置端口重用 s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 绑定地址 s.bind(('0.0.0.0', 8888)) # 设置队列 s.listen(5) # 注册监听套接字 rlist = [s] wlist = [] xlist = [s] while True: print("等待IO发生") rs, ws, xs = select(rlist, wlist, xlist) # 循环遍历rs准备就绪列表 for r in rs: # 如果有新的客户端链接请求 if r is s: # 链接客户端并返回客户端套接字 connfd, addr = r.accept() # r==s 原套接字 print("Connect from", addr) # 将绑定客户端套接字加入监听列表 rlist.append(connfd) # 表示客户端连接套接字准备就绪 else: # 如果是客户端套接字发送数据 则接收 data = r.recv(1024) if not data: # 如果客户端断开链接 # 从关注列表移除connfd rlist.remove(r) r.close() # 关闭套接字 else: print("Receive:", data.decode()) # 讲客户端套接字放入wlist wlist.append(r) # wlist列表会直接返回 # 循环遍历ws准备就绪列表 for w in ws: # 消息回复 w.send("这是一条回复消息".encode()) # 删除并取消监听已处理消息 wlist.remove(w) # xs列表:待处理异常 for x in xs: if x is s: s.close()
sys.stdin
客户端:
from socket import * # 创建套接字 sockfd = socket() # 发起连接 sockfd.connect(('127.0.0.1', 8888)) while True: # 消息收发 msg = input("Msg>>") if not msg: break sockfd.sendall(msg.encode()) data = sockfd.recv(1024) print(data.decode()) sockfd.close()
应用 :
select服务端,同时关注客户端连接
客户端发送和终端输入。将客户端发送和终端输入的内容全都写入到一个文件中
# myserver.py # 应用 : # select服务端,同时关注客户端连接 # 客户端发送和终端输入。将客户端发送和终端输入的内容全都写入到一个文件中 from socket import * from select import * from sys import stdin sock = socket() sock.getsockopt(SOL_SOCKET, SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 6666)) sock.listen(5) rlist = [sock, stdin] wlist = [] xlist = [] file = open("selectdemo.txt", "w+b") while True: rs, ws, xs, = select(rlist, wlist, xlist) for r in rs: if r == sock: connfd, addr = r.accept() print("已连接......") rlist.append(connfd) elif r == stdin: data = stdin.readline() file.write(data.encode()) file.flush() else: data = r.recv(4096) if not data: rlist.remove(r) r.close() else: file.write(data + "\n".encode()) file.flush() print("已经接收内容并写如select>>>.txt文件内\n 内容:", data.decode()) r.send("接收成功!".encode()) file.close()
from socket import * # 创建套接字 sockfd = socket() # 发起连接 sockfd.connect(('127.0.0.1', 6666)) while True: # 消息收发 msg = input("Msg>>") if not msg: break sockfd.sendall(msg.encode()) data = sockfd.recv(1024) print(data.decode()) sockfd.close()