前情回顾
tcp套接字
recv 函数从缓冲区取内容
send sendall 两者均可发送
粘包 :一次接收到多次发送的内容
处理粘包:加结束标志,构建消息结构,发送延迟
udp 套接字
无连接的不需要listen 和 accept
发送接收消息 sendto recvfrom
套接字属性
fileno() getpeername() setsockopt()
广播:一方发送多方接收
HTTP协议
请求类别 : GET POST PUT HEAD DELETE
响应情况 : 200 404
基础的http服务器
1.接收HTTP请求
2.给出一定的响应
01_HttpServer.py
from socket import *
#处理客户请求,返回响应
def handleClient(connfd):
#接收从浏览器发送的request请求
request = connfd.recv(4096)
# print(“")
# print(request)
# print("”)
#列表=按行进行分割
requestHeadlers = request.splitlines()
for line in requestHeadlers:
print(line)
try:
f = open("01_index.html",'r')
except IOError:
#添加响应行
response = "HTTP/1.1 404 not found\r\n"
response += '\r\n' #空行
response += '====网页没找到====' #相应体
else:
response = "HTTP/1.1 200 OK\r\n"
response += '\r\n'
for i in f:
response += i
finally:
connfd.send(response.encode())
#基础配置,功能函数的调用
def main():
#创建流式套接字
sockfd = socket()
#设置端口可重用
sockfd.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
#绑定IP和端口
sockfd.bind((“0.0.0.0”,8000))
#设置监听
sockfd.listen(10)
#
while True:
print(“Listen to the port 8000…”)
#等待接收连接请求
connfd,addr = sockfd.accept()
#调用handleClient函数处理请求
handleClient(connfd)
connfd.close()
if name == “main”:
main()
tarena@tedu:~$ python3 01_HttpServer.py
Listen to the port 8000…
浏览器测试:0.0.0.0:8000
IO input output
在内存中存在数据交换的操作都可以认为是输入输出
比如:
内存和磁盘交互 对文件的读写操作
内存和网络交互 recv send
IO密集型程序:程序执行中大量的IO操作,而较少的cpu运算。消耗cpu较少,运行时间长
CPU密集型程序(计算密集型):程序中大量的操作都需要cpu运算,IO操作较少。消耗cpu大,运行速度快
IO 分类
阻塞IO 非阻塞IO IO多路复用 事件IO 异步IO。。
阻塞IO : 默认形态 效率很低的一种IO情形
阻塞情况 :* 因为某种条件没有达成造成的阻塞
e.g. accept recv input
* 处理IO事件的时候耗时比较长形成阻塞
e.g. 文件的读写过程,网络数据发送过程
非阻塞IO : 通过修改IO事件的属性,使其变为非阻塞的状态。
(改变了第一种阻塞的状况)
通常和循环搭配使用,不断检测阻塞条件是否已经满足。
s.setblocking()
功能: 将套接字设置为非阻塞状态
参数: bool 设置为False则表示设置为非阻塞
02_block_server.py
from socket import *
from time import sleep,ctime
s = socket()
s.bind((‘127.0.0.1’,9999))
s.listen(5)
#将s设置为非阻塞
s.setblocking(False)
while True:
print(“waiting for connect…”)
try:
connfd,addr = s.accept()
except BlockingIOError:
sleep(2)
print(ctime())
continue
print("Connect from",addr)
#connfd.setblocking(False) #可以将recv设为非阻塞
while True:
data = connfd.recv(1024).decode()
if not data:
break
print(data)
connfd.sendall(ctime().encode())
connfd.close()
s.close()
tarena@tedu:~$ python3 02_block_server.py
waiting for connect…
Thu Aug 2 10:42:50 2018
waiting for connect…
Thu Aug 2 10:43:10 2018
waiting for connect…
Thu Aug 2 10:43:12 2018
waiting for connect…
Connect from (‘127.0.0.1’, 58760)
hello
nihao
waiting for connect…
Thu Aug 2 10:43:25 2018
waiting for connect…
Thu Aug 2 10:43:27 2018
waiting for connect…
Connect from (‘127.0.0.1’, 58762)
hello
#tcp_client.py
from socket import *
#创建套接字
sockfd = socket(AF_INET,SOCK_STREAM)
#发起连接 地址:服务端地址
sockfd.connect((‘127.0.0.1’,9999))
while True:
data = input(‘发送>>’)
if not data:
break
#将内容变为bytes格式发送
sockfd.send(data.encode())
data = sockfd.recv(1024).decode()
print(“收到消息:”,data)
sockfd.close()
tarena@tedu:~$ python3 02_tcp_client.py 127.0.0.1 8888
发送>>hello
收到消息: Thu Aug 2 10:43:16 2018
发送>>nihao
收到消息: Thu Aug 2 10:43:20 2018
发送>>
tarena@tedu:~$ python3 02_tcp_client.py 127.0.0.1 8888
发送>>hello
收到消息: Thu Aug 2 10:43:32 2018
发送>>
超时检测
将原本阻塞的函数,设置一个阻塞的最长时间,在规定时间内如果条件
达到则正常执行,如果仍然阻塞则抛出异常
s.settimeout(sec)
功能 : 设置套接字超时时间
参数 : 设置的时间
03_timeout.py
from socket import *
from time import sleep,ctime
s = socket()
s.bind((‘127.0.0.1’,9999))
s.listen(5)
#设置s的超时时间
s.settimeout(5)
while True:
print(“waiting for connect…”)
try:
connfd,addr = s.accept()
except timeout:
sleep(2)
print(ctime())
continue
print("Connect from",addr)
while True:
data = connfd.recv(1024).decode()
if not data:
break
print(data)
connfd.sendall(ctime().encode())
connfd.close()
s.close()
IO多路复用
定义 : 同时监控多个IO事件,当哪个IO事件准备就绪就执行哪个IO事件。
以此形成,多个IO事件都可以操作,不必诸个等待执行的效果。
准备就绪:IO事件即将发生的临界状态
import select
select ----》 windows linux unix
poll —》 linux unix
epoll --》 linux unix
In [1]: import select
In [2]: dir(select) # 查看模块下所有包含函数
In [3]: help(select.select) # 查看函数说明文档
r, w, x = select(rlist, wlist, xlist[, timeout])
功能:监控IO事件,阻塞等待IO事件的发生
参数:rlist 列表 存放我们监控等待处理的IO事件
wlist 列表 存放我们要主动处理的IO事件
xlist 列表 存放如果发生异常需要我们处理的
timeout 数字 超时时间
返回值:r 列表 rlist当中准备就绪的IO
w 列表 wlist当中准备就绪的IO
x 列表 xlist当中准备就绪的IO
注意事项 :
1.在处理IO过程中不应该发生死循环(某个IO单独占有服务器)
2.IO多路复用行了一种并发的效果,效率较高
04_select_server.py
from socket import *
from select import select
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind((“127.0.0.1”,8888))
s.listen(5)
rlist = [s]
wlist = []
xlist = [s]
while True:
#提交关注的IO时间,等待处理
print(“等待IO返回”)
rs,ws,xs = select(rlist,wlist,xlist)
for r in rs:
if r is s:
connfd,addr = r.accept()
print(“Connect from”,addr)
rlist.append(connfd)
else:
data = r.recv(1024)
if not data:
rlist.remove®
r.close()
else:
print(“Receive from”,r.getpeername(),
“:”,data.decode())
#将客户端套接字放到wlist中
wlist.append®
for w in ws:
w.send(“这是一条回复消息”.encode())
wlist.remove(w)
for x in xs:
if x is s:
s.close()
练习 : 写一个select服务端 同时关注客户端端的连接,客户端的发送
和终端的输入。将客户端发送的内容和终端输入的内容均写入到
一个文件中
05_write_file.py
from socket import *
from select import select
import sys
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind((“127.0.0.1”,8888))
s.listen(5)
rlist = [s,sys.stdin]
wlist = []
xlist = []
f = open(‘write_file’,‘w’)
while True:
rs,ws,xs = select(rlist,wlist,xlist)
for r in rs:
if r is s:
c,addr = r.accept()
rlist.append©
elif r is sys.stdin:
data = r.readline()
f.write(data)
else:
data = r.recv(1024)
if not data:
rlist.remove®
else:
f.write(data.decode())
位运算
按照二进制位进行操作运算
&(按位与) |(按位或) ^(按位异或)
<<(左移) >>(右移)
11 1011
14 1110
& 1010 一0则0
| 1111 一1则1
^ 0101 相同为0不同为1
11 << 2 ==> 44 右侧补0
14 >> 2 ==> 3 挤掉低位的数字
使用 : 1. 在做底层硬件的寄存器操作
2. 在做标志位过滤时
poll
- 创建poll对象
p = select.poll() - 添加关注对象
p.register(s,POLLIN | POLLERR)
p.unregister(s)
poll IO事件类型分类
POLLIN POLLOUT POLLERR POLLHUP POLLPRI POLLVAL
rlist wlist xlist 断开 紧急处理 无效数据
- 进行监控
events = p.poll()
功能: 阻塞等待register的事件发生
返回值 : events 是一个列表,列表中每个元素表示准备就绪需要处理的IO
[(fileno,event),(),()]
描述符 具体什么就绪了
描述符地图{s.fileno(): s}
4. 处理IO事件
06_poll_server.py
from socket import *
from select import *
s = socket()
s.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
s.bind((“127.0.0.1”,8888))
s.listen(5)
#创建POLL对象
p = poll()
建立通过fileno查找IO对象的地图
fdmap = {s.fileno()?}
#添加关注
p.register(s,POLLIN | POLLERR)
while True:
#进行监控
events = p.poll()
for fd,event in events:
if fd == s.fileno():
c,addr = fdmap[fd].accept()
print(“Connect from”,addr)
#注册新的套接字
p.register(c,POLLIN)
#维护地图更新
fdmap[c.fileno()] = c
elif event & 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())
作业 : 1. 熟练 写出 select server代码
2. 能够描述IO多路复用的执行原理和内部机制
3. 巩固HTTPserver的代码—》理解HTTP服务器执行流程