socket
1.硬件C/S架构(打印机)
2.软件C/S架构
互联网中处处是C/S架构
如黄色网站是服务端,你的浏览器是客户端(B/S架构也是C/S架构的一种)
腾讯作为服务端为你提供视频,你得下个腾讯视频客户端才能看它的视频)
http://www.cnblogs.com/linhaifeng/articles/6129246.html
C/S架构与socket的关系:
我们学习socket就是为了完成C/S架构的开发
TCP套接字
# 客户端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect(('127.0.0.1',8000)) # 拨通电话
phone.send('hello'.encode('utf-8')) # 发送消息
data = phone.recv(1024)
print('收到服务端发来的消息:',data)
# 服务端
import socket
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 买了一部手机
phone.bind(('127.0.0.1',8000)) # 绑定了一张手机卡,此处IP必须是本机地址
phone.listen(5) # 开机,5相当于允许可以同时最多5个人给你打电话
print('在下一行开始等待')
conn,addr = phone.accept() # 等电话,等待接收二进制字节
# 当客户端发来消息时执行下面代码
msg = conn.recv(1024) # 接收消息
print('发来的消息是:',msg)
conn.send(msg.upper()) # 发消息,这里我们加了大写返回的修饰
conn.close() # 关闭四次握挥手
phone.close()
TCP的可靠之处。。。。。。。。。。。。。。。。。在数据丢不了,可靠在发送过去之后服务端会有提示收到了,如果发送失败发送端可以再发一次,数据仍在。UDP却不是,只管发不管收,也不建立链接
# 服务端
# 通过以下方式可以减少socket的出现次数,使代码看着简洁
from socket import *
ip_port = ('127.0.0.1',8000)
back_log = 5
buffer_size = 1024
TCP_server = socket(AF_INET,SOCK_STREAM)
TCP_server.bind(ip_port)
TCP_server.listen(back_log)
print('服务器在等了')
conn,addr = TCP_server.accept()
print('双向链接是',conn)
print('客户端地址',addr)
while True:
data = conn.recv(buffer_size)
print('客户端发来的消息',data.decode('utf-8'))
conn.send(data.upper())
conn.close()
TCP_server.close()
# 客户端
# from socket import *
#
# ip_port = ('127.0.0.1',8000)
# back_log = 5
# buffer_size = 1024
#
# TCP_client = socket(AF_INET,SOCK_STREAM)
# TCP_client.connect(ip_port)
#
# while True: # 这样就可以实现多次发送消息
# msg = input('请输入:')
# if not msg:
# continue
# TCP_client.send(msg.encode('utf-8'))
# print('客户端发送了')
# data = TCP_client.recv(buffer_size)
# print('收到服务端的消息',data.decode('utf-8'))
#
# TCP_client.close()
# 服务端
from socket import *
ip_port = ('127.0.0.1',8000)
back_log = 5
buffer_size = 1024
TCP_server = socket(AF_INET,SOCK_STREAM)
TCP_server.bind(ip_port)
TCP_server.listen(back_log)
while True: # 这里来一个循环,可以跟多个客户端创建链接
print('服务器在等了')
conn,addr = TCP_server.accept()
print('双向链接是',conn)
print('客户端地址',addr)
# 如果多个用户同时访问服务器,我们设置的最大是5,其他的用户被挂起,放入缓冲区排队访问
# 当第一个客户端关闭链接后,服务器的conn却还在等着收,所以会报错,如何避免这种问题成功地跟下一个客户端连接呢?
while True: # 这里的循环是为了多次收发消息
try:
data = conn.recv(buffer_size)
print('客户端发来的消息',data.decode('utf-8'))
conn.send(data.upper())
except Exception:
break # 直接终止这个while循环
conn.close()
TCP_server.close()
UDP套接字
# 服务器
from socket import *
ip_port = ('127.0.0.1',8000)
buffer_size = 1024
UDP_server = socket(AF_INET,SOCK_DGRAM) # 数据报
UDP_server.bind(ip_port)
while True:
data,addr = UDP_server.recvfrom(buffer_size)
print(data)
UDP_server.sendto(data.upper(),addr)
# 客户端
from socket import *
ip_port = ('127.0.0.1',8000)
buffer_size = 1024
UDP_client = socket(AF_INET,SOCK_DGRAM)
while True:
msg = input('请输入:').strip()
UDP_client.sendto(msg.encode('utf-8'),ip_port)
data,addr = UDP_client.recvfrom(buffer_size)
print(data.decode('utf-8'))
UDP添加时间玩法
# UDP服务器
from socket import *
import time
ip_port = ('127.0.0.1',8000)
buffer_size = 1024
UDP_server = socket(AF_INET,SOCK_DGRAM) # 数据报
UDP_server.bind(ip_port)
while True:
data,addr = UDP_server.recvfrom(buffer_size)
print(data)
if not data: # 可以返回服务器的时间,并且控制时间的返回格式
fmt = '%Y-%m-%d %X'
else:
fmt = data.decode('utf-8')
sever_time = time.strftime(fmt)
UDP_server.sendto(sever_time.encode('utf-8'),addr) # 此处时间是字符串格式,如果不是,需要转化成字符串格式再encode
# 客户端
from socket import *
ip_port = ('127.0.0.1',8000)
buffer_size = 1024
UDP_client = socket(AF_INET,SOCK_DGRAM)
while True:
msg = input('请输入:').strip()
UDP_client.sendto(msg.encode('utf-8'),ip_port)
data,addr = UDP_client.recvfrom(buffer_size)
print('当前服务器的时间是',data.decode('utf-8'))
TCP之远程执行命令
from socket import *
import subprocess
ip_port = ('127.0.0.1',8000)
back_log = 5
buffer_size = 1024
TCP_server = socket(AF_INET,SOCK_STREAM)
TCP_server.bind(ip_port)
TCP_server.listen(back_log)
while True:
print('服务器在等了')
conn,addr=TCP_server.accept()
print('新的客户端链接',addr)
while True: # 收
# if not cmd: break 部分操作系统应对用户直接断开链接(quit)会无限收空进入死循环问题
# 针对windows我们只能用异常处理了
try:
cmd = conn.recv(buffer_size)
if not cmd: break
print('收到客户端的命令',cmd)
# 执行命令,运行结果是cmd_res
# subprocess模块
res = subprocess.Popen(cmd.decode('utf-8'),shell = True,
stderr = subprocess.PIPE,
stdout = subprocess.PIPE,
stdin = subprocess.PIPE)
# stdout代表程序正常输出的结果
# cmd.stdout会将程序运行结果放在管道中,用它的read方法将结果从管道取出来之后,管道为空
# 标准输出,标准错误,标准输入都会被丢进管道中,只有拿出来,才可以显示在屏幕上或者进行一系列操作
err = res.stderr.read()
if err: # 判断管道中是否有标准错误,即判断命令是否出错
cmd_res = err
else: # 如果没错,将输出结果从管道中拿出来
cmd_res = cmd.stdout.read()
# 发
if not cmd_res: # 如果标准输出结果不为空,则表示命令执行成功,注意其编码
cmd_res = '执行成功'.encode('gbk') # 管道中的运行结果采用系统默认编码,windows是gbk
conn.send(cmd_res)
except Exception as e:
print(e)
break
from socket import *
ip_port = ('127.0.0.1',8000)
back_log = 5
buffer_size = 1024
TCP_client = socket(AF_INET,SOCK_STREAM)
TCP_client.connect(ip_port)
while True:
cmd = input('请输入:').strip()
if not cmd: continue # 如果用户输入为空
if cmd == 'quit': break # 如果用户输入quit需要退出
TCP_client.send(cmd.encode('utf-8'))
cmd_res = TCP_client.recv(buffer_size)
print('命令的执行结果是',cmd_res.decode('gbk'))
TCP_client.close()
UDP之远程执行命令
from socket import *
ip_port=('192.168.12.63',8080)
back_log=5
buffer_size=1024
udp_client=socket(AF_INET,SOCK_DGRAM)
while True:
cmd=input('>>: ').strip()
if not cmd:continue
if cmd == 'quit':break
udp_client.sendto(cmd.encode('utf-8'),ip_port)
cmd_res,addr=udp_client.recvfrom(buffer_size)
print('命令的执行结果是 ',cmd_res.decode('gbk'))
udp_client.close()
from socket import *
ip_port = ('127.0.0.1',8000)
back_log = 5
buffer_size = 1024
UDP_client = socket(AF_INET,SOCK_DGRAM)
while True:
cmd = input('请输入:').strip()
if not cmd: continue # 如果用户输入为空
if cmd == 'quit': break # 如果用户输入quit需要退出
UDP_client.sendto(cmd.encode('utf-8'),ip_port)
cmd_res,addr = UDP_client.recvfrom(buffer_size)
print('命令的执行结果是',cmd_res.decode('gbk'))
UDP_client.close()
粘包现象
什么是粘包?
所谓粘包问题主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
两种情况下会发生粘包。
1、发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据了很小,会合到一起,产生粘包)
from socket import *
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
conn,addr=tcp_server.accept()
data1=conn.recv(6) # 第一次数据 b'hellow'
print('第一次数据',data1)
data2=conn.recv(4)
print('第2次数据',data2)
data3=conn.recv(5)
print('第3次数据',data3)
from socket import *
import time
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
tcp_client.send('hello'.encode('utf-8'))
tcp_client.send('world'.encode('utf-8'))
tcp_client.send('egon'.encode('utf-8'))
2、接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
from socket import *
ip_port=('127.0.0.1',8080)
tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)
conn,addr=tcp_socket_server.accept()
data1=conn.recv(2) #一次没有收完整
data2=conn.recv(10)#下次收的时候,会先取旧的数据,然后取新的
print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))
conn.close()
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)
s.send('hello feng'.encode('utf-8'))
拆包的发生情况
当发送端缓冲区的长度大于网卡的MTU时,tcp会将这次发送的数据拆成几个数据包发送出去。
补充问题一:为何tcp是可靠传输,udp是不可靠传输
基于tcp的数据传输请参考我的另一篇文章http://www.cnblogs.com/linhaifeng/articles/5937962.html,tcp在数据传输时,发送端先把数据发送到自己的缓存中,然后协议控制将缓存中的数据发往对端,对端返回一个ack=1,发送端则清理缓存中的数据,对端返回ack=0,则重新发送数据,所以tcp是可靠的
而udp发送数据,对端是不会返回确认信息的,因此不可靠
补充问题二:send(字节流)和recv(1024)及sendall
recv里指定的1024意思是从缓存里一次拿出1024个字节的数据
send的字节流是先放入己端缓存,然后由协议控制将缓存内容发往对端,如果待发送的字节流大小大于缓存剩余空间,那么数据丢失,用sendall就会循环调用send,数据不会丢失
解决粘包的low比方法
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
from socket import *
import subprocess
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,addr=tcp_server.accept()
print('新的客户端链接',addr)
while True:
#收
try:
cmd=conn.recv(buffer_size)
if not cmd:break
print('收到客户端的命令',cmd)
#执行命令,得到命令的运行结果cmd_res
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
err=res.stderr.read()
if err:
cmd_res=err
else:
cmd_res=res.stdout.read()
#发
if not cmd_res:
cmd_res='执行成功'.encode('gbk')
# 解决粘包
length=len(cmd_res) # 先拿到运算结果的长度,此时是整形
conn.send(str(length).encode('utf-8')) # 将整形转为字符串,然后再编码,接着发给客户端
client_ready=conn.recv(buffer_size) # 收一下客户端的反馈
if client_ready == b'ready': # 如果收到客户端的字节格式的ready,就意味着准备好了
conn.send(cmd_res) # 发送结果
except Exception as e:
print(e)
break
from socket import *
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd=input('>>: ').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
# 解决粘包
length=tcp_client.recv(buffer_size) # 第一次交互,收到服务器发来的运算结果长度
tcp_client.send(b'ready') # 接着第二次交互,送过去b'ready'
length=int(length.decode('utf-8')) # 再将解码后的字符串形式length化为整型
# 这里我们需要考虑如果计算结果很大的话,无法一次收完,所以我们做个判断
recv_size=0
recv_msg=b''
while recv_size < length: # 将接收的数据逐步赋值给recv_msg
recv_msg += tcp_client.recv(buffer_size)
recv_size=len(recv_msg) # 1024
print('命令的执行结果是 ',recv_msg.decode('gbk'))
tcp_client.close()
程序的运行速度远快于网络传输速度,所以在发送一段字节前,先用send去发送该字节流长度,这种方式会放大网络延迟带来的性能损耗
这才叫开发
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes
>>> struct.pack('i',1111111111111)
from socket import *
import subprocess
import struct
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_server=socket(AF_INET,SOCK_STREAM)
tcp_server.bind(ip_port)
tcp_server.listen(back_log)
while True:
conn,addr=tcp_server.accept()
print('新的客户端链接',addr)
while True:
#收
try:
cmd=conn.recv(buffer_size)
if not cmd:break
print('收到客户端的命令',cmd)
#执行命令,得到命令的运行结果cmd_res
res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE,
stdin=subprocess.PIPE)
err=res.stderr.read()
if err:
cmd_res=err
else:
cmd_res=res.stdout.read()
#发
if not cmd_res:
cmd_res='执行成功'.encode('gbk')
# 解决粘包
length = len(cmd_res)
data_length = struct.pack('i',length) # 将length用int的方式固定为4个字节
# 此处利用了粘包的特性,会将只有四个字节的文件报头和真正的运行结果合成一次性发送给客户端
conn.send(data_length) # 将我们自定义的文件报头发送给客户端
conn.send(cmd_res) # 将运行结果发送给客户端
except Exception as e:
print(e)
break
from socket import *
import struct
ip_port=('127.0.0.1',8080)
back_log=5
buffer_size=1024
tcp_client=socket(AF_INET,SOCK_STREAM)
tcp_client.connect(ip_port)
while True:
cmd=input('>>: ').strip()
if not cmd:continue
if cmd == 'quit':break
tcp_client.send(cmd.encode('utf-8'))
# 解决粘包
length_data = tcp_client.recv(4) # 收到的文件报头是四个字节,这是已知的,并且这个报头是以元组的形式存在的
length = struct.unpack('i',length_data)[0] # 用stuct的方式解码length_data,这个元组的第一个元素就是我们的文件长度
# 这里我们需要考虑如果计算结果很大的话,无法一次收完,所以我们做个判断
recv_size=0
recv_msg=b''
while recv_size < length: # 将接收的数据逐步赋值给recv_msg
recv_msg += tcp_client.recv(buffer_size)
recv_size=len(recv_msg) # 1024
print('命令的执行结果是 ',recv_msg.decode('gbk'))
tcp_client.close()
多线程socketserver
import socketserver
'''
def __init__(self,request,client_addr,server):
self.request = request
self.client_address = client_address
self.server = server
self.setup()
try:
self.handle()
finally:
self.finish()
'''
class MyServer(socketserver.BaseRequestHandler):
def handle(self):
print('conn is: ',self.request) # 相当于conn
print('addr is: ',self.client_address) # 相当于addr
while True:
try:
# 收消息
data = self.request.recv(1024)
if not data : break
print('收到客户端的消息是',data,self.client_address)
# 发消息
self.request.sendall(data.upper())
except Exception as e:
print(e)
break
if __name__ == '__main__':
s=socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer) #多线程
# s=socketserver.ForkingTCPServer(('127.0.0.1',8080),MyServer) #多进程
# self.server_address = server_address
# self.RequestHandlerClass = RequestHandlerClass
print(s.server_address)
print(s.RequestHandlerClass)
print(MyServer)
print(s.socket)
print(s.server_address)
s.serve_forever()
线程与进程
join(), getDaemon(True)
import threading
import time
def music():
print('begin to listen %s' %time.ctime())
time.sleep(3)
print('stop listening %s' %time.ctime())
def games():
print('begin to play games %s' %time.ctime())
time.sleep(5)
print('stop playing games %s' %time.ctime())
t1 = threading.Thread(target=music)
t2 = threading.Thread(target=games) # 其实是实例化
if __name__ == '__main__':
# t1.setDaemon(True)# t1跟主线程同生死,但主线程却不得不等t2结束才能结束,但t2用的时间都已经够t1运行完了
t2.setDaemon(True) # t2跟主线程同生死,主线程结束我就结束,主线程却不得不等t1结束了之后才结束(三角恋)
t1.start() # 其实执行了threading这个类中的run()方法
t2.start()
# t1.join() # 让t1优先执行完再执行主线程
# t2.join() # 让t2优先执行,主线程等着,此处在等待t2执行完之前t1就已经执行结束了
print('all over %s' % time.ctime()) # 为主线程代码,正常情况下和其他线程同时执行
同步锁
import threading
import time
def sub():
global num
# num-=1
# print ("ok")
lock.acquire() # 加一把锁,在加锁和解锁之间的代码,都会等每一个线程操作完才放下一个线程进来
temp=num
time.sleep(1) # 递减赋值的时间太慢,导致一次赋值操作的时间已经够切换好几个线程来操作了
# 比如第一个线程来拿100,它快速-1之后还得等一秒才能把99赋值给新的num,在这一秒之间已经切换了好几个线程进来也拿的是100
num=temp-1
lock.release() # 释放锁
num=100
l=[]
lock=threading.Lock()
for i in range(100):
t=threading.Thread(target=sub)
t.start()
l.append(t)
for t in l:
t.join()
print(num)
递归锁
import threading
import time
class Mythreading(threading.Thread):
def actionA(self):
# 五个线程同时抢占,随机轮流
# A.acquire() #这样上锁会造成死锁
r_lock.acquire() # count +1
print(self.name,"gotA",time.ctime())
time.sleep(2)
# B.acquire()
r_lock.acquire() # count +1
print(self.name, "gotB", time.ctime())
time.sleep(1)
r_lock.release() # count -1
r_lock.release() # count -1
def actionB(self):
# B.acquire()
r_lock.acquire()
print(self.name, "gotB", time.ctime())
time.sleep(2)
# A.acquire()
r_lock.acquire()
print(self.name, "gotA", time.ctime())
time.sleep(1)
r_lock.release()
r_lock.release()
def run(self):
self.actionA()
self.actionB()
if __name__ == '__main__':
# A = threading.Lock() # 自定义一把锁
# B = threading.Lock()
r_lock = threading.RLock()
L = []
for i in range(5):
t = Mythreading()
t.start()
L.append(t)
for i in L:
i.join()
print('all over')
同步对象event
import threading
import time
class Boss(threading.Thread):
def run(self):
print('我说一下,大家今天要加班到22:00')
print(event.isSet()) # False,此时的event还没有被设置
event.set() # 一旦被设定,就会和event.wait()呼应
time.sleep(5)
print('好了,大家今天可以下班了')
print(event.isSet()) # True
event.set() # 再次设定,老板宪法话,员工才可以呼应
class Workers(threading.Thread):
def run(self):
event.wait() # event被设定之前,此处一直等待,被设定之后才会执行下面代码
print('哎!又加班,命苦啊!!')
time.sleep(1)
event.clear() # 清除第一个event
event.wait() # 呼应第二个event,boss发话了才能庆祝
print('下班咯!Yeah!!!')
if __name__ == '__main__':
event = threading.Event()
l = []
for i in range(5):
l.append(Workers()) # 创造了五个workers
l.append(Boss()) # 创造了一个boss
for t in l:
t.start()
for t in l:
t.join()
print('all over')
信号量
import threading,time
class Mythreading(threading.Thread):
def run(self):
if semaphore.acquire(): # 加一把锁,这把锁可以允许最多存在5把钥匙
print('咱们5个5个来',self.name)
time.sleep(3)
semaphore.release() # 解开,下一批5个进来
if __name__ == '__main__':
semaphore=threading.Semaphore(5) # 指定最大允许多少个线程
l = []
for i in range(100):
l.append(Mythreading())
for t in l:
t.start()
线程队列
import queue
q = queue.Queue(3) # 先进先出模式(FIFO)
#q=queue.LifoQueue() # 先进后出模式(FILO)
q.put(12)
q.put('hello')
q.put({'name':'harvey'})
# q.put(34) # 设置只能放入3个,如果超出线程会wait,等有一个出去了才能进去,无运行效果
# q.put(item,block=False) # 超出之后会报错
# q.put_nowwait(item) # 相当于默认q.put(item,block=False)
while 1:
data = q.get() # 取
# data = q.get(block=False) # 会报错,全部取完之后 empty错误
print(data)
print('--------------------')
------------------------------------------------------------------
import queue
q=queue.PriorityQueue() # 自定义级别模式,1级优先
q.put([3,12])
q.put([1,'hello'])
q.put([2,{'name':'harvey'}])
while 1:
data = q.get()
# print(data) # 连同等级与数据一起拿出来
print(data[1]) # 此处是用索引的方式,只取数据,不要前面的等级
print('--------------------')
join,task_done
import threading,queue,time
q = queue.Queue()
def Producer(name):
count = 0
while count < 0:
print('making...')
time.sleep(5) # 做好一个包子要5秒
q.put(count)
print('Producer %s has produced %s baozi..' %(name, count))
count +=1
q.join() # 做好一个就发一个通知,否则task_done()会等着
print("包子来咯")
def Consumer(name):
count = 0
while count < 0:
time.sleep(3)
data = q.get()
print('吃着呢,还不错')
time.sleep(4)
q.task_done() # 接到通知才往下走,否则等着,此处就可以不用判断q.get()是否为空了
print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
count += 1
p1 = threading.Thread(target=Producer, args=('A君',))
c1 = threading.Thread(target=Consumer, args=('B君',))
c2 = threading.Thread(target=Consumer, args=('C君',))
c3 = threading.Thread(target=Consumer, args=('D君',))
p1.start()
c1.start()
c2.start()
c3.start()
进程调用
from multiprocessing import Process
import time
def f(name):
time.sleep(1)
print('hello', name,time.ctime())
if __name__ == '__main__':
p_list=[]
for i in range(3):
p = Process(target=f, args=('alvin',)) # 跟创建线程形式一样,但实际过程大不一样
p_list.append(p)
p.start()
for i in p_list:
i.join()
print('end')
# 自定义+继承
from multiprocessing import Process
import time
class Myprocess(Process):
# 此处我们没有用到实例方法,所以不用继承
# def __init__(self):
# super(Myprocess, self).__init__()
def run(self):
time.sleep(1)
print('hello', self.name,time.ctime())
if __name__ == '__main__':
p_list=[]
for i in range(3):
p = Myprocess()
p.daemon = True # 此处与线程中的p.Setdaemon作用相同,守护进程,与主进程同生共死
p_list.append(p)
p.start()
for i in p_list:
i.join()
print('end')
from multiprocessing import Process
import time,os
def info(title):
print('title',title)
print('父级进程id:',os.getppid())
print('当前进程id:',os.getpid())
if __name__ == '__main__':
info('main process line') # 主进程
time.sleep(1) # 一秒之后开始子进程
print('---------------')
p = Process(target=info,args=('harvey',)) # 建立一个子进程去运行info
p.start()
p.join()
如图:
进程通信
import queue,time
import multiprocessing
def foo(q):
time.sleep(1)
q.put(12)
q.put('hello')
if __name__ == '__main__':
q = multiprocessing.Queue() # 创建一个进程队列,就可以达到进程共享的效果了
# q = queue.Queue # 线程队列,无法被进程共享
p = multiprocessing.Process(target=foo,args=(q,))
p.start
print(q.get())
print(q.get())
管道
from multiprocessing import Process,Pipe
def f(conn):
conn.send([{'name':'harvey'},'hello',12]) # 给主进程发消息
response = conn.recv() # 拿主线程发来的消息
print(response)
conn.close()
if __name__ == '__main__':
parent_conn,son_conn = Pipe() # 双向管道
p = Process(target=f,args=(son_conn,))
p.start()
print(parent_conn.recv()) # 看看主线程收到了什么
parent_conn.send('儿子你好') # 给子线程打声招呼
p.join()
Manager
Queue和Pipe只是实现了数据交互,并没有实现数据共享,Manager可以实现一个进程修改另一个进程的数据
from multiprocessing import Process, Manager
def f(d,l,n):
d[n] = '1' # {0:'1'}
d['2'] = 2 # {0:'1','2':2}
l.append(n) # 每次添加一个range(10),子线程修改主线程的l
print(l)
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict() # {}
l = manager.list(range(5)) # [0,1,2,3,4]
p_list = []
for i in range(10): # 开10个进程
p = Process(target=f, args=(d,l,i))
p.start()
p_list.append(p)
for res in p_list:
res.join()
print(d)
print(l)
进程同步
from multiprocessing import Process, Lock
import time
def f(l, i):
l.acquire()
time.sleep(1)
print('hello world %s' % i)
l.release()
if __name__ == '__main__':
lock = Lock()
for num in range(10):
Process(target=f, args=(lock, num)).start()
进程池(可以用来测试电脑几核)
from multiprocessing import Process,Pool
import time,os
def Foo(i):
time.sleep(1)
print(i)
return 'hello %s' %i
def Bar(arg):
print(arg)
if __name__ == '__main__':
pool = Pool(5) # 可以指定几个进程并发,太多会占用资源,太少会浪费资源
for i in range(100):
# pool.apply(func=Foo, args=(i,)) #同步接口
# callback为回调函数,就是某个动作或者函数执行成功后再去执行的函数,即跟屁虫
pool.apply_async(func=Foo, args=(i,),callback=Bar)
pool.close()
pool.join() # close必须在join之前,固定格式
print('all over')
复习一下生成器
def f():
print('ok1')
s = yield 6
print(s)
print('ok2')
yield 2
gen = f()
# print(gen) # 生成器可不是普通函数,想进入的方法不同
# next(gen) # ok1 遇到yield会进入等待,等下一次拿的时候,就从这个位置开始,不会从头开始
RET = gen.__next__()
print(RET)
# next(gen) # 报错,StopIteration
gen.send(5) # ok1,6,5,ok2,到第一个yield给值
协程
协程是利用了生成器的原理,处理IO阻塞,由我们全局掌控子进程和主进程的交互次数和交互内容,告别了抢占模式,所以也不需要锁的概念,但仍然不能利用多核运行。
# 消费者生产者模型
import time
import queue
def consumers(name):
print('关于吃包子,我不是针对谁')
while 1:
new_baozi = yield
print("[%s] 我不客气了 %s" % (name,new_baozi))
def producer():
r = con1.__next__()
r = con2.__next__()
n = 0
while 1:
time.sleep(1)
print("\033[32;1m[producer]\033[0m 包子马上好 %s and %s" %(n,n+1))
con1.send(n) # 一个send过一个yield
con2.send(n+1)
n =+ 2 # 客人每次拿两个,所以每次累加2
if __name__ == '__main__':
con1 = consumers('c1')
con2 = consumers('c2')
producer()
greenlet模块
from greenlet import greenlet
def test1():
print(12)
gr2.switch()
print(34)
def test2():
print(56)
gr1.switch()
print(78)
gr1.switch()
gr1 = greenlet(test1)
gr2 = greenlet(test2)
gr2.switch() # 每次switch都会切换
gevent模块
import requests,time
import gevent
start = time.time()
def f(url):
print('GET: %s' % url)
resp = requests.get(url) # 爬取网页
data = resp.text # 封装网页数据
print('%d bytes received from %s.' % (len(data), url)) # 计算数据长度
''' # 串行爬取
f('https://www.python.org/')
f('https://www.yahoo.com/')
f('https://www.baidu.com/')
f('https://www.sina.com.cn/')
f("http://www.xiaohuar.com/hua/")
'''
gevent.joinall([ # 用gevent方法来协程爬取
gevent.spawn(f, 'https://www.python.org/'),
gevent.spawn(f, 'https://www.yahoo.com/'),
gevent.spawn(f, 'https://www.baidu.com/'),
gevent.spawn(f, 'https://www.sina.com.cn/'),
gevent.spawn(f, 'http://www.xiaohuar.com/hua/'),
])
print("cost time:",time.time()-start) # 计算消耗时间
IO阻塞与非阻塞
blocking IO
########################################server
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8000))
sk.listen(5) # 可以监听5个客户端来访
while 1:
conn,addr = sk.accept() # 阻塞IO
while 1:
conn.send('连接成功'.encode('utf8'))
data = conn.recv(1024)
print(data.decode('utf8'))
########################################client
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8000))
while 1:
data = sk.recv(1024)
print(data.decode('utf8'))
sk.send(b'有事问你')
nonblocking IO
#server
import socket,time
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sk.bind(('127.0.0.1',8000))
sk.listen(5) # 可以监听5个客户端来访
sk.setblocking(False) # 默认是True,设置后即为非阻塞
print ('waiting for client connecting .......')
while 1:
try: # 这是有客户端连接的情况
connection,address = sk.accept() # 进程主动轮询,隔一段时间看一下有没有客户端进来
print(address)
client_msg = connection.recv(1024)
print(str(client_msg),'utf8')
connection.close()
except Exception as e: # 意外之外
print(e)
time.sleep(3) # 3秒轮询一次
#client
import socket,time
sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
while 1:
sk.connect(('127.0.0.1',8000))
print('连接上了')
sk.sendall(bytes('hello','utf8'))
time.sleep(2)
break
IO多路复用,有select,poll,epoll,selectors
#server
import socket
import select
sk = socket.socket()
sk.bind(('127.0.0.1',8000))
sk.listen(5) # 可以监听5个客户端来访
inp = [sk,]
print ('waiting for client connecting .......')
while 1:
# 可以监听同时多个客户端,设定5秒监听一次
r,w,e = select.select(inp,[],[],5)
# 当有新客户端链接时,r即[sk,],当客户端发消息来时,r即[sk,conn],所以要执行for,必须r不为空时才可运行
for i in r:
conn,add = i.accept()
print(conn)
print(add)
print('hello')
inp.append(conn)
msg = conn.recv(1024)
print(msg.decode('utf8'))
print(conn)
print('>>>>>>')
#client
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',8000))
while 1:
inp = input(">>").strip()
sk.send(inp.encode("utf8"))
# data = sk.recv(1024)
# print(data.decode("utf8"))
完整版
#sever
import socket
import select
sk = socket.socket()
sk.bind(('127.0.0.1',8000))
sk.listen(5) # 可以监听5个客户端来访
inp = [sk,]
print ('waiting for client connecting .......')
while 1:
r,w,e = select.select(inp,[],[],5)
for i in r: # 第二次监听,如果客户发消息,则sk不变,conn会变化,即r为[sk,conn],所以走else
if i == sk:
conn,addr = i.accept() # 如果i中有sk,则说明有新客户端连接
print(conn)
inp.append(conn) # inp[sk,conn],到print结束第一次监听,从for下一次监听开始
else: # 这里进行消息交互
data_byte = i.recv(1024)
print(str(data_byte,'utf8'))
msg = input('回答%s号客户>>>'%inp.index(i))
i.sendall(bytes(msg,'utf8'))
print('>>>>>>')
#client
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8000))
while True:
inp = input(">>>>")
sk.sendall(bytes(inp, "utf8"))
data = sk.recv(1024)
print(str(data, 'utf8'))
selectors模块
# server
import selectors
import socket
sel = selectors.DefaultSelector() # 根据操作系统自己做选择,将合适的多路复用方法拿过来(select,poll,epoll)
def accept(sock,mask):
conn,addr = sock.accept() # be ready
conn.setblocking(False)
sel.register(conn,selectors.EVENT_READ,read) # 将conn和read做绑定
def read(conn,mask):
try:
data = conn.recv(1024) # be ready
if not data:
raise Exception # 如果数据为空,报错
conn.send(data) # 将消息发回客户端,看看是不是阻塞的
except Exception as e:
print('closing', conn)
sel.unregister(conn) # 如果客户端中途断开,关闭这个链接,不影响其他客户端
conn.close()
sock = socket.socket()
sock.bind(('127.0.0.1',8000))
sock.listen(100)
sock.setblocking(False)
sel.register(sock, selectors.EVENT_READ, accept) # 将sock与accept做绑定
print("server.....")
# 当有新客户端连接时,监听到sock和运行accept,当发消息时,监听sock和conn并运行read
while True:
events = sel.select() # 监听,有变化就往下走
for key,mask in events:
callback = key.data # callback就是sock对应的函数accept
callback(key.fileobj, mask) # key.fileobj就是sock
# client
import socket
sk = socket.socket()
sk.connect(('127.0.0.1', 8000))
while True:
inp = input(">>>>")
sk.send(inp.encode("utf8"))
data = sk.recv(1024)
print(data.decode('utf8'))