目录
一、术语扫盲
1、CS架构与BS架构
CS即Client/Server(客户机/服务器)结构
BS即Browser/Server(浏览器/服务器)结构
开发维护成本:cs开发维护成本高于bs
安全性:cs安全性高于bs
客户端负载:cs客户端负载大于bs
响应速度:cs相应速度高于bs
2、网络通信
网络=物理链接介质+互联网通信协议
网络存在的意义就是跨地域数据传输===>称之为通信
3、OSI七层协议
五层协议
应用层
传输层
网络层
数据链路层
物理层
协议:规定数据的组织格式(头部+数据部分)
3.1物理层
一组数据称为:位(电信号010101)
单纯的电信号毫无意义,必须对其进行分组
3.2数据链路层:ethernet以太网协议
一组数据称为:帧=头(源mac地址与目标mac地址)+数据(网络层发过来的整体内容)
mac地址:但凡接入互联网的主机必须有一块网卡,每块网卡在出厂时都烧制好全世界独一无二的地址
计算机通信基本靠吼,即以太网协议的工作方式是广播
3.3网络层:IP协议
一组数据称为:包=头(源IP地址与目标IP地址)+数据(传输层发过来的整体内容)
要达到的目的:
划分广播域
每一个广播域但凡要接通外部,一定要有一个网关帮内部的计算机转发包到网上
网关与外界通信走的是路由协议
一个合法的ipv4地址=ip地址/子网掩码地址
3.4传输层:tcp\udp协议
一组数据称为:段=头(源port与目标port)+数据(应用层发过来的整体内容)
基于端口,端口范围0-65535,0-1023为系统占用端口
ip+port ===> 标识全世界范围内独一无二的一个基于网络通信的应用程序
基于tcp协议通信之前:必须建立一个双向通信的链接
三次握手,四次挥手
tcp是可靠传输的,发送数据必须等到对方确认后才算完成,才会将自己内存中的数据清除
ps:当服务端大量处于TIME_WAIT状态时意味着服务端正在经历高并发
3.5应用层
可以自定义协议=》头部(对数据的描述信息发给谁数据的类型长度等)+数据部分
http https ftp
总结:
mac => 标识局域网内独一无二的一台计算机
ip + mac => 标识全世界范围内独一无二的一台计算机
ARP协议通过ip地址解析mac地址:
两台计算机在同一局域网内(源ip与目标ip网络地址一样),目标mac地址就是计算机2的mac地址
两台计算机不在同一局域网内(源ip与目标ip网络地址不一样),目标mac地址就是网关的mac地址
ip => 标识全世界范围内独一无二的一台计算机
ip + port => 标识全世界范围内独一无二的一台计算机上的一个基于网络通信的应用程序
应用层 数据
传输层 tcp
网络层 ip
数据链路层 ethernet
物理层
(原mac地址,目标mac地址) (源ip地址,目标ip地址) (源port,目标port) 数据
在经由物理层转换为电信号
二、网络编程
1、套接字通信socket
1.1、基于tcp协议
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",8080))
server.listen(5)
# 链接循环
while True:
conn,addr=server.accept()
# 通信循环
while True:
try:
data=conn.recv(1024)
conn.send(data.upper())
except Exception:
break
conn.close()
import socket
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8080))
while True:
msg=input("请输入信息:").strip()
if msg=="":continue
client.send(msg.encode("utf-8"))
data=client.recv(1024)
print(data.decode("utf-8"))
client.close()
1.2、基于udp协议
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(("127.0.0.1",8080))
while True:
data,addr=server.recvfrom(1024)
server.sendto(data.upper(),addr)
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg=input("请输入:").strip()
server.sendto(msg.encode("utf-8"),("127.0.0.1",8080))
res=server.recvfrom(1024)
print(res)
2、粘包问题
import socket
import os
import json
import struct
server=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
server.bind(("127.0.0.1",8080))
server.listen(5)
conn,addr=server.accept()
while True:
try:
path=conn.recv(1024)
path=path.decode("utf-8")
filename=os.path.basename(path)
size=os.path.getsize(filename)
# 制作头:包含数据的一些信息
header={
"filename":filename,
"size":size
}
header_str=json.dumps(header)
header_bytes=header_str.encode("utf-8")
# 先发送头的长度:4个字节
x=struct.pack("i", len(header_bytes))
conn.send(x)
# 再发送头
conn.send(header_bytes)
# 再发送真实数据
with open(path,"rb") as f:
while True:
msg = f.read(1024)
if len(msg)==0:
break
conn.send(msg)
except Exception:
break
conn.close()
'''
粘包问题出现的原因
1、tcp是流式协议,数据像水流一样粘在一起没有边界区分
2、收数据没有收干净,残留部分会和下一次结果混在一起
ps:
1、udp协议是报式协议,数据一报一报的一个recv对应一个send
2、收不干净就丢掉,所以没有粘包问题
解决粘包问题的思路:每次都收干净就行
1、拿到数据的总大小total_size
2、recv_size+=循环接收的数据,直至recv_size=total_size
'''
import socket
import struct
import json
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8080))
while True:
msg=input("请输入文件路径:").strip()
if msg=="":continue
client.send(msg.encode("utf-8"))
# 先接收4个字节,获取头的长度
x=client.recv(4)
header_len=struct.unpack("i",x)[0]
# 根据头的长度,再接收头
header_bytes=client.recv(header_len)
header_str=header_bytes.decode("utf-8")
header=json.loads(header_str)
# 根据头的内容,再接收真实数据
total_size=header["size"]
recv_size=0
while recv_size<total_size:
msg=client.recv(1024)
try:
print(msg.decode("utf-8"),end="")
except Exception:
pass
recv_size+=len(msg)
else:
print()
client.close()
3、套接字实现并发socketserver
3.1、基于tcp协议
import socketserver
class MyRequestHandler(socketserver.BaseRequestHandler):
# 通信循环:启动一个线程进行通信循环
def handle(self):
# self.request => 链接对象
# self.client_address => 客户端ip、port
while True:
try:
data = self.request.recv(1024)
self.request.send(data.upper())
except Exception:
break
self.request.close()
# 链接循环:从半链接池中取出链接请求,与其建立双向链接
s=socketserver.ThreadingTCPServer(("127.0.0.1",8080),MyRequestHandler)
s.serve_forever()
import socket
client=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(("127.0.0.1",8080))
while True:
msg=input("请输入信息:").strip()
if msg=="":continue
client.send(msg.encode("utf-8"))
data=client.recv(1024)
print(data.decode("utf-8"))
client.close()
3.2、基于udp协议
import socketserver
class MyRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
# self.request => (b'hello', <socket.socket fd=360, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
# self.client_address => ('127.0.0.1', 52491)
client_data=self.request[0]
server=self.request[1]
client_addr=self.client_address
server.sendto(client_data.upper(),client_addr)
s=socketserver.ThreadingUDPServer(("127.0.0.1",8080),MyRequestHandler)
s.serve_forever()
import socket
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg=input("请输入:").strip()
server.sendto(msg.encode("utf-8"),("127.0.0.1",8080))
res=server.recvfrom(1024)
print(res)
4、linux安装python
cd /home
wget https://www.python.org/ftp/python/3.7.6/Python-3.7.6.tgz
tar -xvf Python-3.7.6.tgz
cd Python-3.7.6
./configure --prefix=/usr/local/python3
make && make install
vim /etc/profile
三、并发编程
1、操作系统发展史
参考博客:操作系统的发展史 - JasonJi - 博客园 (cnblogs.com)
2、多道技术(单核实现并发的效果)
并发与并行
- 并发:看起来像同时运行的
- 并行:真正意义上的同时执行
如何实现
- 空间上的复用:多个程序共用一套计算机硬件
- 时间上的复用:切换(一个程序遇到IO操作、长时间占用cpu)+保存状态
多道技术图解
四、进程
1、进程理论
程序与进程
- 程序:躺在硬盘的一堆代码,是静态的。(程序想要被计算机运行,它的代码必须要先由硬盘读到内存,之后CPU取指令再执行)
- 进程:程序的执行过程,是动态的。(创建进程就是在内存中申请一块空间,将需要运行的代码丢进去,一个进程对应一块独立的内存空间)
进程的调度
- 先来先服务调度算法:对长作业有利,对短作业无利
- 短作业优先调度算法:对短作业有利,对长作业无利
- 时间片轮转法+多级反馈队列:
同步与异步(描述的是任务的提交方式)
- 同步:任务提交后,原地等待任务的返回结果
- 异步:任务提交之后,不原地等待任务的返回结果,任务的返回结果会有一个异步回调机制自动处理
阻塞与非阻塞 (描述的是程序的运行状态)
- 阻塞:阻塞态
- 非阻塞:就绪态、运行态
时间片轮转法+多级反馈队列
进程运行的三状态图
2、开启进程的两种方式
from multiprocessing import Process
import time
def task(name):
print("{} is running".format(name))
time.sleep(3)
print("{} is over".format(name))
if __name__ == '__main__':
# 1、创建一个进程
p=Process(target=task,args=("Cang",))
# 2、开启进程
p.start()
print("主")
from multiprocessing import Process
import time
class MyProcess(Process):
def run(self):
print("aaa")
time.sleep(3)
print("bbb")
if __name__ == '__main__':
p=MyProcess()
p.start()
print("主")
3、进程对象的方法
from multiprocessing import Process,current_process
import os
import time
def func():
# print("进程:",current_process().pid)
print("子进程:",os.getpid()) # 查看当前进程的pid
print("主进程:",os.getppid()) # 查看当前进程的父进程的pid
if __name__ == '__main__':
p=Process(target=func)
p.start()
# p.terminate() # 杀死当前进程,是告诉操作系统帮你杀死进程,但是需要一定的时间
# print(p.is_alive()) # 判断当前进程是否存活
p.join() # 等待子进程运行结束之后,再继续运行主进程,不影响其它子进程的运行
4、进程间数据相互隔离(通过第三方模块交互)
4.1、队列
import queue
q=queue.Queue(3) # 括号内可以传数字,标示生成的队列最大可以同时存放的数据量
q.put(111)
q.put(222)
q.put(333)
# q.put(444) # 当队列为满时,等待存值
# print(q.empty()) # 判断队列是否为空
# print(q.full()) # 判断队列是否为满
v1=q.get()
v2=q.get()
v3=q.get()
# v4=q.get() # 当队列为空时,等待取值
# v4=q.get(timeout=3) # 只等3s,若还没有值报错queue.Empty
# v4=q.get_nowait() # 等都不等,没值直接报错queue.Empty
import queue
q=queue.LifoQueue(3)
q.put(111)
q.put(222)
q.put(333)
print(q.get())
print(q.get())
print(q.get())
import queue
q=queue.PriorityQueue(3)
q.put((3,111))
q.put((5,222))
q.put((-1,333))
print(q.get())
print(q.get())
print(q.get())
4.2、IPC机制
from multiprocessing import Process, Queue
def producer(q):
q.put(111)
def consumer(q):
print(q.get())
if __name__ == '__main__':
q=Queue()
p1=Process(target=producer,args=(q,))
p2=Process(target=consumer,args=(q,))
p1.start()
p2.start()
4.3、生产者消费者模型
from multiprocessing import Process,JoinableQueue
import time
import random
def producer(name,food,q):
for i in range(3):
data="{}生产{}{}".format(name,food,i)
print(data)
time.sleep(random.random())
q.put(data)
def consumer(name,q):
while True:
data=q.get()
time.sleep(random.random())
print("{}吃{}".format(name,data))
q.task_done()
if __name__ == '__main__':
q=JoinableQueue()
p1=Process(target=producer,args=("egon","包子",q))
p2=Process(target=producer,args=("tank","泔水",q))
p1.start()
p2.start()
p1.join()
p2.join()
c1=Process(target=consumer,args=("cang",q))
c1.daemon=True
c1.start()
q.join()
print("zhu")
5、守护进程
from multiprocessing import Process
import time
def task(name):
print("{} is running".format(name))
time.sleep(3)
print("{} is over".format(name))
if __name__ == '__main__':
p=Process(target=task,args=("cang",))
p.daemon=True # 将进程p设置成守护进程,这一句一定要放在start方法上面才有效否则会报错
p.start()
print("主寿终正寝")
五、线程
1、线程理论
进程:资源单位(创建一个进程就是在内存中申请一块空间将需要运行的代码丢进去)
线程:执行单位(线程指得是代码的执行过程,执行过程中需要的资源向线程所在的进程索要)为何要有线程:开设线程的开销要远远小于开设进程的开销(申请内存空间、拷贝代码)
2、开启线程的两种方式
from threading import Thread
import time
def task(name):
print("{} is running".format(name))
time.sleep(1)
print("{} is over".format(name))
if __name__ == '__main__':
t=Thread(target=task,args=("cang",))
t.start()
print("zhu")
from threading import Thread
import time
class MyThread(Thread):
def __init__(self,name):
super().__init__()
self.name=name
def run(self):
print("{} is running".format(self.name))
time.sleep(1)
print("{} is over".format(self.name))
if __name__ == '__main__':
t=MyThread("cang")
t.start()
print("zhu")
3、线程对象的方法
from threading import Thread,current_thread,active_count
import time
import os
def task(n):
print(os.getpid())
print(current_thread().name)
time.sleep(n)
if __name__ == '__main__':
t1=Thread(target=task,args=(1,))
t2=Thread(target=task,args=(2,))
t1.start()
t2.start()
t1.join() # 等待子线程运行结束后再运行主线程,不影响其它子线程的运行
print(os.getpid()) # 获取进程的pid
print(current_thread().name) # 获取线程的名字
print(active_count()) # 统计当前正在活跃的线程数
4、同一进程内的多个线程数据共享
from threading import Thread
money=100
def task():
global money
money=666
if __name__ == '__main__':
t=Thread(target=task)
t.start()
t.join()
print(money)
5、守护线程
from threading import Thread
import time
def task():
print("123")
time.sleep(1)
print("end123")
def foo():
print("456")
time.sleep(3)
print("end456")
if __name__ == '__main__':
t1=Thread(target=task)
t2=Thread(target=foo)
t1.daemon=True
t1.start()
t2.start()
print("zhu")
六、锁
1、互斥锁
'''
互斥锁:
多个进程/线程操作同一份数据的时候,会出现数据错乱的问题
加锁处理,将并发变成串行,牺牲效率但是保证了数据的安全性
'''
from multiprocessing import Process,Lock
import json
import time
import random
def search(i):
with open("a.txt",mode="rt",encoding="utf-8") as f:
res=f.read()
dic=json.loads(res)
print("用户{}查询票数:{}".format(i,dic.get("ticket_num")))
def buy(i):
with open("a.txt",mode="rt",encoding="utf-8") as f:
res=f.read()
dic=json.loads(res)
time.sleep(random.randint(1,3))
if dic.get("ticket_num")>0:
dic["ticket_num"] -= 1
with open("a.txt",mode="wt",encoding="utf-8") as f:
res=json.dumps(dic)
f.write(res)
print("用户{}买票成功".format(i))
else:
print("用户{}买票失败".format(i))
def run(i,mutex):
search(i)
# 给买票环节加锁处理
# 枪锁
mutex.acquire()
buy(i)
# 释放锁
mutex.release()
if __name__ == '__main__':
# 在主进程中生成一把锁,让所有的子进程抢,谁先抢到谁买票
mutex=Lock()
for i in range(1,10):
p=Process(target=run,args=(i,mutex))
p.start()
2、GIL全局解释器锁
'''
GIL全局解释器锁:
是Cpython解释器的特点,而不是python的特点
保证的是解释器级别的数据安全,针对不同的数据还是需要加不同的锁来处理
会导致同一个进程下的多个线程无法同时执行,也就无法利用多核优势(解释型语言的通病)
'''
from threading import Thread, Lock
import time
money = 100
def task(mutex):
global money
mutex.acquire()
tem = money
time.sleep(0.01)
money = tem - 1
mutex.release()
if __name__ == '__main__':
mutex = Lock()
t_list = []
for i in range(100):
t = Thread(target=task, args=(mutex,))
t.start()
t_list.append(t)
for t in t_list:
t.join()
print(money)
'''
100个线程起起来之后,去抢GIL
我抢到了GIL,又抢到了互斥锁(无人争抢,其它线程还在等着抢GIL)
我进入IO 自动释放GIL,其它线程虽然抢到了GIL但是抢不到互斥锁,最后还是乖乖释放GIL
我又抢到了GIL,释放互斥锁,释放GIL
'''
3、死锁与递归锁
'''
死锁:
我手上有锁A,然后去抢锁B
你手上有锁B,然后去抢锁A
递归锁:
可以被连续的acquire和release
每acquire一次计数加一,每release一次计数减一
只要计数不为0,那么其他人都无法抢到该锁
'''
from threading import Thread, RLock
import time
mutexB = mutexA = RLock()
class MyThread(Thread):
def __init__(self, name):
super().__init__()
self.name = name
def run(self):
self.task1()
self.task2()
def task1(self):
mutexA.acquire()
print("线程{}抢到锁A".format(self.name))
mutexB.acquire()
print("线程{}抢到锁B".format(self.name))
mutexB.release()
print("线程{}释放锁B".format(self.name))
mutexA.release()
print("线程{}释放锁A".format(self.name))
def task2(self):
mutexB.acquire()
print("线程{}抢到锁B".format(self.name))
time.sleep(1)
mutexA.acquire()
print("线程{}抢到锁A".format(self.name))
mutexA.release()
print("线程{}释放锁A".format(self.name))
mutexB.release()
print("线程{}释放锁B".format(self.name))
if __name__ == '__main__':
for i in range(10):
t = MyThread(i)
t.start()
4、信号量
'''
信号量:
在不同的阶段可能对应不同的技术点,在并发编程中指的是锁
如果将互斥锁比成一个坑的话,那么信号量就相当于多个坑
'''
from threading import Thread, Semaphore
import time
import random
sp = Semaphore(5) # 坑位
def task(name):
sp.acquire()
print("线程{}在蹲坑".format(name))
time.sleep(random.randint(1,3))
sp.release()
if __name__ == '__main__':
for i in range(20):
t=Thread(target=task,args=(i,))
t.start()
七、拓展
1、Event事件
'''
Event事件:
一些进程/线程需要等待另外一些进程/线程运行完毕之后才能运行,类似发射信号
'''
from threading import Thread, Event
import time
sub=Event()
def light():
print("红灯亮")
time.sleep(3)
print("绿灯亮")
sub.set()
def car(name):
print("{}等待绿灯".format(name))
sub.wait()
print("{}加油门飙起来".format(name))
if __name__ == '__main__':
t=Thread(target=light)
t.start()
for i in range(20):
t=Thread(target=car,args=(i,))
t.start()
2、多进程与多线程比较
'''
同一个进程下的多个线程无法利用多核优势,那么多线程是不是没有用了
计算密集型(多核四个任务)
多进程:总耗时10+
多线程:总耗时40+
IO密集型(多核四个任务)
多进程:相对浪费资源
多线程:相对节省资源
总结:多进程和多线程都有各自的优势,通常使用多进程下开设多线程,这样既可以利用多核也可以减少资源消耗
'''
from multiprocessing import Process
from threading import Thread
import time
import os
def task():
# m = 1
# for i in range(1, 100000):
# m *= i
time.sleep(0.01)
if __name__ == '__main__':
start_time=time.time()
print(os.cpu_count())
p_list = []
for i in range(10):
# p = Process(target=task)
# p.start()
# p_list.append(p)
t=Thread(target=task)
t.start()
p_list.append(t)
for p in p_list:
p.join()
print(time.time()-start_time)
3、进程池与线程池
'''
进程池与线程池:
在保证计算机安全的情况下,最大限度的利用计算机
虽然降低了效率,但是它保证了计算机的安全
'''
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import time
def task(n):
print(n)
time.sleep(2)
return "===>:{}".format(n ** 2)
def call_back(res):
print(res.result())
if __name__ == '__main__':
pool = ThreadPoolExecutor(5)
for i in range(20):
res = pool.submit(task, i)
res.add_done_callback(call_back)
4、协程
'''
进程:资源单位
线程:执行单位
协程:单线程实现并发
本来cpu遇到IO或者长时间被占用,需要进行切换
但是我们程序员自己在代码层面上检测所以的IO操作,一旦遇到IO了我们在代码级别完成切换
这样给cpu的感觉就是没有遇到IO,从而提高了程序的运行效率
'''
# import time
# def task1():
# for i in range(10000000):
# i+1
# def task2():
# for i in range(10000000):
# i+1
#
# start_time=time.time()
# task1()
# task2()
# print(time.time()-start_time) # 1.5311427116394043
# import time
# def task1():
# for i in range(10000000):
# i + 1
# yield
# def task2():
# t = task1()
# for i in range(10000000):
# i + 1
# next(t)
#
# start_time = time.time()
# task2()
# print(time.time() - start_time) # 2.8123199939727783
# 总结:计算密集型任务,切换会降低效率
# import time
#
# def task1():
# print("hello")
# time.sleep(2)
# def task2():
# print("world")
# time.sleep(3)
# start_time = time.time()
# task1()
# task2()
# print(time.time() - start_time) # 5.0310328006744385
from gevent import monkey;monkey.patch_all()
from gevent import spawn
import time
def task1():
print("hello")
time.sleep(2)
def task2():
print("world")
time.sleep(3)
start_time = time.time()
s1=spawn(task1)
s2=spawn(task2)
s1.join()
s2.join()
print(time.time() - start_time) # 3.031080722808838
5、IO模型
5.1、阻塞模型
import socket
server=socket.socket()
server.bind(("127.0.0.1",8080))
server.listen(5)
while True:
conn,addr=server.accept()
while True:
try:
data=conn.recv(1024)
conn.send(data.upper())
except Exception:
break
conn.close()
import socket
client=socket.socket()
client.connect(("127.0.0.1",8080))
while True:
client.send("hello".encode("utf-8"))
data=client.recv(1024)
print(data.decode("utf-8"))
5.2、非阻塞模型
'''
虽然非阻塞IO模型给你的感觉很牛逼
但是该模型会长时间占用cpu而不干活
'''
import socket
server=socket.socket()
server.bind(("127.0.0.1",8080))
server.listen(5)
server.setblocking(False)
r_list=[]
del_list=[]
while True:
try:
conn,addr=server.accept()
r_list.append(conn)
except BlockingIOError:
for conn in r_list:
try:
data = conn.recv(1024)
conn.send(data.upper())
except BlockingIOError:
continue
except ConnectionRefusedError:
conn.close()
del_list.append(conn)
continue
for conn in del_list:
r_list.remove(conn)
del_list.clear()
import socket
client=socket.socket()
client.connect(("127.0.0.1",8080))
while True:
client.send("hello".encode("utf-8"))
data=client.recv(1024)
print(data.decode("utf-8"))
5.3、IO多路复用
'''
select机制 windows、linux都有
poll机制 只有linux有
select和poll都可以监管多个对象,但是poll监管的数量更多
上述两个机制对于监管特别多的对象时,会出现相当大的延时响应
epoll机制 只有linux有
它给每个对象都绑定了一个回调机制,一旦有响应回调机制立刻发起提醒
针对不同的操作系统还要考虑不同的监管机制,书写代码太过繁琐
这是有一个人跳出来了(selectors模块)能够根据你跑的平台不同,自动帮你选择对应的监管机制
'''
import socket
import select
server=socket.socket()
server.bind(("127.0.0.1",8080))
server.listen(5)
server.setblocking(False)
read_list = [server] # 将server对象添加到监管列表中
while True:
r_list,w_list,x_list=select.select(read_list,[],[]) # 操作系统会监管这些对象,返回可执行的对象
for i in r_list: # 针对不同的对象作不同的操作
if i is server: # 针对可执行的server对象,建立链接,并将该链接对象conn添加到监管列表中
conn,addr=i.accept()
read_list.append(conn)
else: # 针对可执行的conn对象,进行接收数据操作
try:
data=i.recv(1024)
conn.send(data.upper())
except Exception:
i.close()
read_list.remove(i)
import socket
client=socket.socket()
client.connect(("127.0.0.1",8080))
while True:
client.send("hello".encode("utf-8"))
data=client.recv(1024)
print(data.decode("utf-8"))
5.4、异步IO
异步IO模型是所有模型中效率最高的,也是应用最广泛的
相关的模块与框架
模块:asyncio
框架:sanic tronado twisted