什么是网络:
一些相互连接的,以共享资源目的计算机的集合.
为什么学习网络编程:
能编写基于网络通讯的软件,与其他计算机的软件进行数据通讯
ip地址:用来在网络中标记一台计算机,是网络设备给每个计算机分配的唯一标识
IP 地址是指互联网协议地址(英语:Internet Protocol Address,又译为网际协议地址), 是IP Address的缩写。IP 地址是IP协议提供的一种统一的地址格式.
ip地址的作用:用来在网络中标记一台电脑,是网络设备为网络中的每台计算机分配的一个唯一标 识。比如192.168.1.1;在本地局域网上是唯一的。
ip地址格式:xxx.xxx.xxx.xxx 点分十进制(IPv4)**
常用:A,B类(欧洲等发达国家使用),C类(国内)
公网ip
1.0.0.1-126.255.255.254
128.1.0.1-191.255.255.254
192.0.1.1-223.255.255.254
局域网ip、私有ip:
10.xxx.xxx.xxx 内网
172.16.xxx.xxx - 172.31.xxx.xxx 内网
192.168.0.0-192.168.255.255 常用
特殊ip
127.0.0.1
localhost
IPv6 : 冒号分十六进制,号称地球上每一粒沙子分配一个IP地址
IPv6 支持测试地址:http://test-ipv6.com/
ifconfig
ipconfig
ping 192.168.16.33
ping www.baidu.com
osk:调出键盘
两种传输方式
数据传输前,不需要建立连接,可以直接收发数据
UDP (User Datagram Protocol )不提供复杂的控制机制, 如果传输过程中出现丢包, UDP 也不 负责重发. 甚至当出现包到达顺序乱掉时候也没有纠正的功能. 由于 UDP 面向无连接, 它可以随时 发送数据. 再加上 UDP 本身的处理既简单又高效, 因此常用于以下几个方面:
1.包总量较少的通信(DNS). 视频、音频等多媒体通信(即时通信).2.限定于 LAN 等特定网络中的应用通信.
- 广播通信(广播、多播)
UDP协议
需要先建立连接,才能进行数据收发
TCP 协议, 传输控制协议(英语:Transmission Control Protocol,缩写为 TCP)是一种面向 连接的、可靠的、基于字节流的传输层通信协议.
两者区别
TCP提供的是面向连接的、可靠的数据流传输;
UDP提供的是非面向连接的、不可靠的数据流传输。 TCP提供可靠的服务,通过TCP连接传送的数据,无差错、不丢失,不重复,按序到达
UDP尽最大努力交付,即不保证可靠交付。
34
TCP面向字节流;
UDP面向报文。
TCP连接只能是点到点的; UDP支持一对一、一对多、多对一和多对多的交互通信。TCP协议
查看端口号及进程id
netstat -ano | findstr 2425
netstat -an | grep 21
Linux查看进程名称&PID&端口
sudo lsof -i :21
查看服务器socket
sudo netstat -ntl
导入依赖模块
import socket
创建套接字socket对象
# 参数一:AddressFamily 地址类型
# socket.AF_INET IPv4(默认)
# socket.AF_INET6 IPv6
# 参数二:传输协议类型
# socket.SOCK_DGRAM UDP协议
# socket.SOCK_STREAM TCP协议(默认)
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
发送数据
"""
参数一:data
要发送的数据的二进制形式,bytes字节数组
参数二:address
接收者的(IP地址,端口号),元组
元素1:字符串IP地址
元素2:整数类型端口号
"""
udp_client_socket.sendto("你好!".encode(), ("192.168.16.64", 8888))
接收数据
# bufsize recvfrom(1024) 执行一次接收的最大字节数
# 没有人给当前socket发数据,此代码会一直阻塞
# 一旦收到了数据,recvfrom会自己解除阻塞
# 接收到的数据(b'\xd4\xbc123', ('192.168.16.58', 8888)) 元组
# 元素1:接收到的消息(字节数组)
# 元素2:发送者的IP和端口号的元组
recv_data = udp_client_socket.recvfrom(1024)
# print(recv_data)
try:
print(recv_data[0].decode()) # 消息
except Exception as e:
# print("解码错误,尝试使用GBK解码")
print(recv_data[0].decode("GBK")) # 消息
print(recv_data[1]) # 地址
关闭套接字
udp_client_socket.close()
# encoding字符集
utf-8 万国码, gbk汉字
# errors错误处理方式,
strict严格模式(默认),ignore忽略错误
添加广播权限
# 3)开启广播权限 # 参数1:SOL_SOCKET 当前socket对象 # 参数2:SO_BROADCAST 要修改的属性名 # 参数3:属性值 udp_socket.setsockopt(SOL_SOCKET, SO_BROADCAST, True)
发送地址
255.255.255.255
xxx.xxx.xxx.255
导入模块
创建套接字对象
socket.SOCK_STREAM
连接TCP服务器
tcp_client_socket.connect((“192.168.16.58”, 8080))
收发数据
# 发送数据到服务器 tcp_client_socket.send("哈哈哈哈,你打不我吧!".encode()) # 等待接收数据 recv_data = tcp_client_socket.recv(1024) print(recv_data.decode("GBK"))
关闭连接
导入模块
创建socket对象
绑定IP和端口号
tcp_server_socket.bind(("", 7788))
开启监听,设置为被动模式listen
tcp_server_socket.listen(128)
等待客户端的接入accept
# 代码会阻塞,直到有客户端接入,释放阻塞 # 收到元组: # 元素1:服务器为客户端创建的socket对象 # 元素2:客户端的address(IP,port) tcp_client_socket, ip_port = tcp_server_socket.accept()
使用新的客户端socket对象收发数据
# 使用新的客户端的socket对象,进行数据的【接收&发送】操作 recv_bytes = tcp_client_socket.recv(1024) print("收到{}消息:{}".format(ip_port,recv_bytes.decode("GBK"))) # 发送数据 tcp_client_socket.send("下课!".encode())
关闭连接
# 7)关闭与客户端的连接 tcp_client_socket.close() # 8)关闭服务器套接字 tcp_server_socket.close()
能够接收一个客户端发来的多条信息
# 6)recv/send接收发送数据
while True:
# 使用新的客户端的socket对象,进行数据的【接收&发送】操作
recv_bytes = tcp_client_socket.recv(1024) # 阻塞
if recv_bytes:
print("收到{}消息:{}".format(ip_port,recv_bytes.decode("GBK")))
# 发送数据
tcp_client_socket.send("收到!".encode())
else:
print("客户端已断开连接:", ip_port)
break
能够接受多个客户端连接
# 5)accept等待客户端的链接
while True:
# 代码会阻塞,直到有客户端接入,释放阻塞
# 收到元组:
# 元素1:服务器为客户端创建的socket对象
# 元素2:客户端的address(IP,port)
tcp_client_socket, ip_port = tcp_server_socket.accept()
print("有新的客户端接入:",ip_port)
# 6)recv/send接收发送数据
while True:
# 使用新的客户端的tcp_client_socket对象,进行数据的【接收&发送】操作
...
只能同时和一个客户端进行数据的收发,下一个客户端必须等上一个断开了,才能接入
域名,简称DN(全称:Domain Name),域名可以理解为是一个网址,就是一个特殊的名 字。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3QQiS65S-1581259279621)(img/45.png)]
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络 协议。所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收 HTML页面的方法。
HTTP是一个客户端和服务器端请求和应答的标准(TCP)。客户端是终端用户,服务器端是 网站。通过使用Web浏览器、网络爬虫或者其它的工具,客户端发起一个到服务器上指定端口 (默认端口为80)的HTTP请求。
超文本传输协议是一种应用层协议。
响应报文
响应行、状态行
响应头
空行
响应体
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hFGSgBLx-1581259279621)(img/46.png)]
长连接
短连接
每次收发数据都需要建立连接,请求数量大时,访问速度慢。
线程,可简单理解为是程序执行的一条分支,也是程序执行流的最小单元。线程是被系统独 立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源, 但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
import time
import threading #导入依赖模块threading
def sing():
for i in range(1, 5):
print('正在唱歌....', i)
time.sleep(0.5)
def dance():
for i in range(1, 5):
print('正在跳舞....', i)
time.sleep(0.8)
if __name__ == '__main__':
# 创建threading.Thread对象,将要执行的函数传给target参数
t = threading.Thread(target=sing)
d = threading.Thread(target=dance)
# 执行线程对象的start函数,开启子线程
t.start()
d.start()
获取当前活动线程的数量:threading.active_count()
当前活跃的线程对象列表:threading.enumerate()
得到列表长度len(threading.enumerate())
获取当前线程名称
threading.current_thread()
线程参数传递3种方式:
元组
# 1)传递args元组 threading.Thread(target=子线程函数名, args=元组)
# 参数必须和函数形参顺序一致
sing_thread = threading.Thread(target=sing, args=(10, 100, 1000))
字典
# 2)传递kwargs字典 threading.Thread(target=子线程函数名, kwargs=字典)
# 字典里的参数顺序可以和形参顺序不同
sing_thread = threading.Thread(target=sing, kwargs={"a":10, "c":1000, "b":100})
元组合字典的混合
# 3)元组和字典的混合
# 如果形参里的参数有默认值,创建Thread时可以不传,否则必传
sing_thread = threading.Thread(target=sing, args=(10,) , kwargs={"b":100})
线程执行顺序:
守护线程
# 必须在start之前执行
thread.setDaemon(True)
thread.start()
自定义线程类的流程
创建一个类继承threading.Thread
class MyThread(threading.Thread)
pass
重写父类的run函数
def run(self):
pass
创建对象并调用start函数,启动自定义线程
thread = MyThread(10)
# 内部会自动触发run函数执行
thread.start()
更多
super().__init__()
线程名.join
函数让其他线程等待其执行完毕再执行使用步骤
创建互斥锁
my_lock = threading.Lock()
资源修改前获取锁
my_lock.acquire()
资源修改后释放锁
my_lock.release()
注意事项
什么是死锁:多个任务在同一时间,都在等待对方释放锁,这个状态就是死锁状态
如何避免:在获取锁后,资源操作完毕,及时释放锁
代码演示:
# index 0 1 2 3 4
value_list = [1, 3, 5, 7, 9]
# 获取锁, 代码可能阻塞
my_lock.acquire()
# 对资源的操作
if index >= len(value_list):
print("角标越界: " , index)
# 释放
my_lock.release()
return
print("value: ", value_list[index])
# 释放
my_lock.release()
或
# index 0 1 2 3 4
value_list = [1, 3, 5, 7, 9]
# 获取锁, 代码可能阻塞
my_lock.acquire()
try:
# 对资源的操作
print("value: ", value_list[index])
except:
print("角标越界: " , index)
finally:
# 确保锁可以释放
my_lock.release()
将UDP聊天器改为多任务版
重新修改用户菜单,删除接收消息栏,改为默认在后台接收消息
开启子线程循环接收消息
主线程退出时,子线程也随之退出(设置守护线程)
def recv_msg(udp_socket):
"""
需要在子线程循环接收消息
:param udp_socket:
:return:
"""
while True:
# 1) 使用socket的recvfrom函数接收数据, 使子线程进入阻塞状态
bytes_data, ip_port = udp_socket.recvfrom(1024)
# 2) 解码并打印收到的数据
try:
# 尝试使用Linux默认的utf-8编码集进行解码
print("收到{}发来的消息:{}".format(ip_port, bytes_data.decode()))
except:
# 如果解码失败, 则使用GBK进行解码
print("收到{}发来的消息:{}".format(ip_port, bytes_data.decode("GBK")))
# 开启子线程,在子线程接收消息
thread_recv = threading.Thread(target=recv_msg, args=(udp_socket,))
# 使子线程设置为守护线程,当主线程退出时,该线程也退出
thread_recv.setDaemon(True)
thread_recv.start()
TCP服务器代码
开启子线程同时接收多个客户端的接入,同时接收每个客户端的多条消息
每个新来的客户端分配一个子线程
while True:
# 6. 接收客户端连接 (线程1:主线程 - 通过循环支持多个客户端的接入)
tcp_client_socket, client_ip_port = tcp_server.accept() # 会阻塞等待客户端接入
# 给新接入的客户端单独开启一个新的子线程, 循环接收这个客户端的多条消息
# recv_msg(tcp_client_socket, client_ip_port)
new_client_thread = threading.Thread(target=recv_msg,
args=(tcp_client_socket, client_ip_port))
# 设置子线程为守护线程,随着主线程退出而自动关闭
new_client_thread.setDaemon(True)
# 开启新线程,此代码不会阻塞主线程
# 线程开启后,主线程又立即进入accept状态,等待客户端接入
new_client_thread.start()
在每个子线程中,while True循环接收客户端消息, recv函数阻塞的是子线程,不影响主线程接收新客户端接入
def recv_msg(tcp_client_socket, client_ip_port):
print("有新的客户端接入:{},线程名:{}".format(\
client_ip_port, threading.current_thread()))
while True:
# 线程2..:子线程 循环接收某一个客户端的多条消息
# 7. 接收客户端发来的消息 (阻塞)
recv_data = tcp_client_socket.recv(4096)
if recv_data:
# 8. 解码打印客户端信息
print("收到{}发来的消息:{}".format(\
client_ip_port, recv_data.decode("GBK")))
else:
print("客户端{}已断开".format(client_ip_port))
# 9. 关闭和客户端的连接
tcp_client_socket.close()
break
无论有多少个cpu,python在执行时会在同一时刻只允许一个线程运行。
python下想要充分利用多核CPU,就用**多进程。**因为每个进程有各自独立的GIL,互不干扰,这样就可以真正意义上的并行执行,在python中,有多核CPU情况下,多进程的执行效率优于多线程。
import threading, multiprocessing
查看cpu个数: count = multiprocessing.cpu_count()
当某个线程申请到一个锁,其余线程不能再申请。于是有了递归锁(其实就是内部维护了一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次acquire。直到一个线程所有的acquire都被release,其他的线程才能获得资源。)
导入模块
import multiprocessing
创建Process对象
process = multiprocessing.Process(target=work)
启动继承
process.start()
进程相关的api
is_alive():判断进程子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
Process创建的实例对象的常用属性:
name:当前进程的别名,默认为Process-N,N为从1开始递增的整数
pid:当前进程的pid(进程号)
获取进程名称:
multiprocessing.current_process().name
获取进程pid
multiprocessing.current_process().pid
os.getpid()
父进程pid (ppid)
os.getppid()
进程的销毁
Windows:可以在任务管理器中根据PID杀死进程,也可以使用kill pid
关闭指定进程
Linux&OSX:使用kill -9 pid
强行杀死进程
主进程和子进程特点:
进程参数的传递
# 元组args
process = multiprocessing.Process(target=work, args=(10, 100, 1000))
# 字典kwargs
process = multiprocessing.Process(target=work, kwargs={"c":1000, "b":100, "a":10})
# 混合
process = multiprocessing.Process(target=work, args=(10, 100),kwargs={"c":1000})
进程之间不能共享全局变量的。IPC
守护进程:主进程和子进程一种约定,主进程退出时,子进程也随之退出
实现方式:
设置守护进程
# 方式1:设置子进程为守护进程
process.daemon = True
提前终止子进程
# 方式2:提前终结子进程
process.terminate()
队列的创建: 进程间通信 IPC实现的一种
初始化Queue()对象时(例如:q=Queue()),若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头);
# 1) 导入模块
import multiprocessing
# 2)创建队列对象(大小)
queue = multiprocessing.Queue(3)
阻塞式的数据添加&获取
添加queue.put("abc")
获取value queue.get()
设置timeout,可以在如果阻塞超过timeout时间时,抛出异常
value = queue.get(timeout=3) print(value)
非阻塞式数据的添加&获取
queue.put_nowait(123)
如果队列已满,抛出异常queue.Fullvalue = queue.get_nowait()
如果队列为空,抛出异常_queue.Empty# 判断队列是否为空,Python低于3.7版本上,可能会出现empty在queue刚添加完数据后,empty为True
print("empty: ", queue.empty())
# 判断队列是否已满
print("full: ",queue.full())
print(queue.get())
print(queue.get())
# 获取队列元素个数
print("qsize: ", queue.qsize())
消息队列:实现进程间的数据交互
使用步骤:
创建消息队列并将queue传给每个进程
queue = multiprocessing.Queue(5)
创建并启动一个或多个进程
p1 = multiprocessing.Process(target=work1, args=(queue,))
p2 = multiprocessing.Process(target=work2, args=(queue,))
p1.start()
p2.start()
添加数据
def work1(queue):
""" 进程work1不断地放入数据 """
for i in range(10):
print("+++进程1向queue添加数据:", i)
queue.put(i)
queue.put(i)
queue.put(i)
time.sleep(0.5)
获取数据
try:
while True:
value = queue.get(timeout = 3)
print("---进程2从queue获取数据:", value)
except:
print("3秒之内没有获取到数据,主动关闭掉自己")
两种方式处理消息队列
.join()
完成数据的写入后,另一个进程读取初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么 就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求 就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。
import multiprocessing import time
def copy_work():
print("拷贝中....",multiprocessing.current_process().pid)
time.sleep(0.3)
if __name__ == '__main__':
# 创建进程池 # Pool(3) 表示创建容量为3个进程的进程池
pool = multiprocessing.Pool(3)
for i in range(10):
# 利用进程池同步拷贝文件,进程池中的进程会必须等上一个进程退出才能执行下一个进程 pool.apply_async(copy_work)
pool.close() # 注意:如果使用异步方式执行copy_work任务,主进程不再等待子进程执行完毕再退出! pool.join()
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():等待进程池中的所有进程执行完毕再退出, 必须在close或terminate之后使用;
如果要使用Pool创建进程,就需要使用multiprocessing.Manager()中的Queue()
用法: queue = multiprocessing.Manager().Queue(3)
文件夹copy器
源文件夹 -> 目标文件夹
./test -> C:\Users\Poplar\Desktop\test
def 文件拷贝: 源文件夹, 目标文件夹, 文件名
1. 声明并拼接源文件&目标文件的路径
2. 打开源文件&创建目标文件
3. 读取源文件&写入目标文件(循环)
1. 定义变量保存源文件夹&目标文件夹
2. 创建目标文件夹
3. 获取并遍历源文件夹的所有文件
4. 定义函数执行文件的拷贝(遍历)
我们已经知道可以对list、tuple、str等类型的数据使用for…in…的循环语法从其中依次拿到数据进 行使用,我们把这样的过程称为遍历,也叫迭代。
我们把可以通过for…in…这类语句迭代读取一条数据供我们使用的对象称之为可迭代对象 (Iterable)。
from collections.abc import Iterable
# 判断列表是否可以迭代
print(isinstance([1, 2, 3], Iterable))
可迭代对象的本质是:一个对象所属的类中含有 iter() 方法,该对象就是一个可迭代对象。
from collections.abc import Iterable
class MyClass(object):
def __iter__(self):
pass
my_class = MyClass()
print(isinstance(my_class, Iterable))
我们分析对可迭代对象进行迭代使用的过程,发现每迭代一次(即在for…in…中每循环一次) 都会返回对象中的下一条数据,一直向后读取数据直到迭代了所有数据后结束。那么,在这个过程 中就应该有一个“人”去记录每次访问到了第几条数据,以便每次迭代都可以返回下一条数据。我们 把这个能帮助我们进行数据迭代的“人”称为迭代器(Iterator)
__iter__(self):
的魔法函数,变成了可迭代对象__iter__(self):
魔法函数迭代器
- 记录当前迭代位置
- 配合**next()**获取可迭代对象的下一条数据
获取可迭代对象的迭代器
迭代器 = iter(可迭代对象)
获取可迭代对象的下一条数据
数据 = next(迭代器)
自定义迭代对象
# 自定义的可迭代对象类
# 声明`__iter__`
class MyClass:
def __iter__(self):
# return iter([1,2,3,4])
# return [1,3,5].__iter__()
return MyIterator()
# 自定义迭代器类
# 声明2个函数`__iter__`,`__next__`
class MyIterator:
"""自定义迭代器"""
def __iter__(self):
pass
def __next__(self):
pass
注意,当我们已经迭代完最后一个数据之后,再次调用next()函数会抛出StopIteration的异常,来 告诉我们所有数据都已迭代完成,不用再执行next()函数了。
一、创建自定义类
1)构造函数
2)声明addItem()
添加数据
3)声明__iter__()
返回迭代器
二、创建迭代器类
1)构造函数
2)声明__iter__()
函数
2)声明__next__()
函数
三、实现目标
lst = MyList()
lst.addItem("永强")
lst.addItem("赵四")
lst.addItem("安红")
for item in lst:
print(item)
self.a, self.b = (self.b, self.a + self.b)
生成器的创建有两种方式:
类似推导式的结构
lst_genrator = (i * 2 for i in range(5))
print(type(lst_genrator),lst_genrator)
#
for item in lst_genrator:
print(item)
yield
得到既是可迭代对象,也是迭代器
def fibonacci(num):
# 1)初始化a,b
a, b = 1, 1
# 2)初始化位置索引
current_index = 0
print("a" * 30)
# 3)循环修改a、b
while current_index < num:
print("b" * 30)
# 临时保存a
yield a
# 更新a、b值
a, b = b, a+b
# 更新位置索引
current_index += 1
print("c" * 30)
if __name__ == '__main__':
fib = fibonacci(10)
value = next(fib)
print(value)
try:
while True:
value = next(fib)
print(value)
except:
pass
next(生成器)
或生成器.send(None)
生成器.send(None)
返回的是下一个yield的内容next
或send
结果,也不是函数调用的结果import time
"""
使用yield简单实现协程
"""
def work1():
counter = 0
while True:
print("work1....执行中", counter)
time.sleep(0.5)
yield
counter += 1
def work2():
counter = 0
while True:
print("work2.....................执行中", counter)
time.sleep(0.5)
yield
counter += 1
if __name__ == '__main__':
gen1 = work1()
gen2 = work2()
while True:
next(gen1)
next(gen2)
from greenlet import greenlet
import time
def work1():
while True:
print("work1....")
# 切换其他协程
gl2.switch()
time.sleep(0.5)
def work2():
while True:
print("work2...............")
# 切换其他协程
gl1.switch()
time.sleep(0.5)
if __name__ == '__main__':
# 创建两个greenlet对象
gl1 = greenlet(work1)
gl2 = greenlet(work2)
gl1.switch()
import gevent
def work1():
for i in range(5):
print("work1......", i)
gevent.sleep(1)
def work2():
for i in range(5):
print("work2.............", i)
gevent.sleep(1)
if __name__ == '__main__':
# 指派任务
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
gevent.sleep(10)
# time.sleep(10)
# g1.join()
# g2.join()
import gevent
from gevent import monkey
monkey.patch_all()
import time
import threading
def work1():
for i in range(5):
print("work1......", i, gevent.getcurrent(), threading.current_thread())
time.sleep(1)
def work2():
for i in range(10):
print("work2.............", i, gevent.getcurrent(), threading.current_thread())
time.sleep(1)
if __name__ == '__main__':
print("主线程:",gevent.getcurrent(), threading.current_thread())
# 给gevent指派任务
g1 = gevent.spawn(work1)
g2 = gevent.spawn(work2)
print("11111")
g1.join()
print("22222")
g2.join()
print("33333")
from gevent import monkey
monkey.patch_all()
from urllib import request
import gevent
def download_url(img_url, file_name):
print("开始下载:",img_url, file_name)
# 下载数据
response = request.urlopen(img_url)
# 准备文件,接收网络数据
with open(file_name, "wb") as file:
while True:
data = response.read(4096)
if data:
file.write(data)
else:
break
print("\33[42;1m 文件下载完毕 \033[0m{},文件名:{}".format(img_url, file_name))
if __name__ == '__main__':
img_url1 = "http://img.mp.itc.cn/upload/20170716/8e1b835f198242caa85034f6391bc27f.jpg"
img_url2 = "http://img.mp.sohu.com/upload/20170529/d988a3d940ce40fa98ebb7fd9d822fe2.png"
img_url3 = "http://image.uczzd.cn/11867042470350090334.gif?id=0&from=export"
g1 = gevent.spawn(download_url, img_url1, "1.gif")
g2 = gevent.spawn(download_url, img_url2, "2.gif")
g3 = gevent.spawn(download_url, img_url3, "3.gif")
gevent.joinall([g1, g2, g3])
# g1.join()
# g2.join()
# g3.join()