1、tcp应用之HTTP服务器
1、接收HTTP请求2、给出一定的响应
# 静态网页处理器
# 采用循环的模式,无法满足客户端长连接
import socket
# 处理客户端请求
def handleClient(connfd):
request = connfd.recv(2048)
requestHeadlers = request.splitlines() # 按行分割
for line in requestHeadlers:
print(line)
try:
f = open('知乎.html', 'r')
except IOError:
response = 'HTTP/1.1 404 not found'
response += '\r\n'
response += '====sorry,file not find===='
else:
response = 'HTTP/1.1 200 OK'
response += '\r\n'
for i in f:
response += i
finally:
connfd.send(response.encode())
connfd.close()
# 流程控制
def main():
sockfd = socket.socket()
sockfd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sockfd.bind(('0.0.0.0', 8000))
sockfd.listen(10)
while True:
connfd, addr = sockfd.accept()
handleClient(connfd)
if __name__ == '__main__':
main()
2、IO input output
1、IO操作:
在内存中存在数据交换的操作两种操作:
内存和磁盘交换: 文件读写 打印
内存和网路的交换:recv send
2、IO密集型程序
程序中执行大量的IO操作,而较少需要CPU运算消耗CPU资源少,运行周期往往较长
3、CPU密集型程序
程序执行中需要大量的CPU运算,IO操作较少占用CPU多
3、IO的分类
1、阻塞IO (默认形态)
是效率最低的一种IO阻塞:因为某种条件达成再继续运行 如:accept recv input
处理IO时间的时候耗时较长也会产生阻塞 如:文件的读写过程,网络数据的传输过程
2、非阻塞IO
通过修改IO对象使其变为非阻塞状态(改变第一种阻塞状态)通用循环不断判断阻塞条件,需要消耗更多CPU但是一定程度上提高了IO效率
1、s.setblocking()
功能:设置套接字的阻塞状态
参数:
bool类型,默认为True,设置为false则为非阻塞
import socket as so
import time
s = so.socket(so.AF_INET, so.SOCK_STREAM, 0)
s.setsockopt(so.SOL_SOCKET, so.SO_REUSEADDR, 1)
print(s.getsockopt(so.SOL_SOCKET, so.SO_REUSEADDR))
s.bind(('127.0.0.1', 8888))
s.listen(5)
# 设置s是非阻塞状态
s.setblocking(False)
while True:
print('等待连接...')
try:
connfd, addr = s.accept()
except BlockingIOError:
time.sleep(2)
print(time.ctime())
continue
print('连接地址:', addr)
#设置非阻塞状态
connfd.setblocking(False)
while True:
data = connfd.recv(1024)
if not data:
break
print(data.decode('utf-8'))
connfd.send('来,确认下眼神!'.encode())
connfd.close()
soc.close()
2、超时等待(检测)
s.settimeout()功能:
设置套接字的超时监测
参数:
超时时间,单位秒
所谓超时时间监测既对原本阻塞的函数进行设置,时期不再始终阻塞,而是阻塞等待一定时间后自动返回。
在规定时间中如果正常结束阻塞则继续执行否则产生timeout异常
import socket as so
import time
import traceback
s = so.socket(so.AF_INET, so.SOCK_STREAM, 0)
s.setsockopt(so.SOL_SOCKET, so.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', 8888))
s.listen(5)
# 设置s的超时检测单位秒
s.settimeout(5)
while True:
print('等待连接...')
try:
connfd, addr = s.accept()
except Exception:
traceback.print_exc()
continue
print('连接地址:', addr)
#设置超时
connfd.settimeout(5)
while True:
data = connfd.recv(1024)
if not data:
break
print(data.decode('utf-8'))
connfd.send('来,确认下眼神!'.encode())
connfd.close()
soc.close()
^_^ traceback 更专业的打印异常信息
import traceback# 打印原始错误信息
traceback.print_exc()
3、IO多路复用
定义同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件,形成并发的效果
并发:多个事件同时发生同时处理
IO事件:
准备就绪:临界状态
select模块实现多路复用
select方法 ---->支持win Linux Unixpool方法 ---->支持 Linux Unix
epool方法 ---->支持 Linux Unix
1、select:
r,w,x=select(rlist,wlist,xlist,[timeout])功能:
监控IO事件,阻塞等待IO事件发生
参数:
rlist:
列表类型,存放我们要监控等待处理的IO
wlist:
列表类型,存放我们希望主动处理的IO
xlist:
列表类型,如果发生异常需要我们处理的IO
timeout:
数字,超时检测,默认一直阻塞
返回值(当任意一个列表中一个IO事件准备就绪就返回):
r:
列表形式 rlist当中准备就绪的IO
w:
列表形式 wlist当中准备就绪的IO
x:
列表形式 xlist当中准备就绪的IO
import sys
import socket
import select
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# s.bind(('172.60.11.118', 8888))
s.bind(('192.168.43.122',8888))
s.listen(10)
# 将关注的IO放入到rlist中
rlist = [s]
wlist = []
xlist = [s]
while True:
rs, ws, xs = select.select(rlist, wlist, xlist)
for r in rs:
# 套接字准备就绪
if r is s:
connfd, addr = r.accept()
print('Connect from', addr)
# 将新的套接字加入到关注列表
rlist.append(connfd)
else:
try:
data = r.recv(1024)
if not data:
rlist.remove(r)
r.close()
print('Received from',r.getpeername(),':', data.decode())
wlist.append(r)
except Exception:
pass
for w in ws:
w.send('>>> '.encode())
wlist.remove(w)
for x in xs:
if x is s:
s.close()
sys.exit()
IO多路复用注意点:
1、在处理IO过程中不应该发生死循环(某个IO单独占有服务器)2、IO多路复用是单进程程序,是一个并发程序
3、IO多路复用有较高的IO执行效率
2、pool
1、创建pool对象p = select.poll()
2、加入关注的IO
p = register(s)
3、使用poll函数监控
events = p.poll() # 阻塞函数,和select.poll()不同
功能:
阻塞等待register的事件只要有任意准备就绪及返回
返回值:
events为列表格式 [(fileno,event),(),()]
poll IO事件
POLLIN rlist
POLLOUT wlist
POLLUP 断开连接
POLLERR xlist
POLLPRI 紧急处理
POLLVAL 无效数据
s & POLLIN
4、处理发生的IO事件
示例:
import socket
import select
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# s.bind(('172.0.0.1', 8888))
s.bind(('0.0.0.0', 8888))
s.listen(10)
# 创建IO事件地图
fdmap = {s.fileno(): s}
# 创建poll对象
p = select.poll()
# 将套接字加入到关注
p.register(s, (select.POLLIN | select.POLLERR))
while True:
# 进行监控
events = p.poll()
print(events)
for fd, event in events:
if fd == s.fileno():
c, addr = fdmap[fd].accept()
print('connect from', addr)
p.register(c, select.POLLIN)
fdmap[c.fileno()] = c
elif event & select.POLLIN:
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())
3、epoll
1、效率上比poll和select稍微高2、只能用于Linux和Unix
3、支持边缘触发 select 和 poll 只支持水平触发