UDP
1.UDP网络发送数据
# coding=utf-8
import socket
# 1. 创建udp套接字
udp_socket = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
# 2. 准备接收方的地址
# '192.168.1.103'表示目的ip地址
# 8080表示目的端口
dest_addr = ('192.168.1.103', 8080) # 注意 是元组,ip是字符串,端口是数字
# 3. 定义发送的数据
send_data = input("请输入要发送的数据:")
# 4. 发送数据到指定的电脑上的指定程序中
udp_socket.sendto(send_data.encode('gbk'), dest_addr)
# 5. 关闭套接字
udp_socket.close()
2.UDP网络接收数据
#coding=utf-8
import socket
# 1. 创建udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2.绑定ip和端口 绑定自身ip地址
udp_socket.bind(('192.168.15.130', 11000))
# 3. 等待接收对方发送的数据
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字节数
# 4. 显示对方发送的数据
print(recv_data)
# 接收到的数据recv_data是一个元组
# 第1个元素是对方发送的数据
# 第2个元素是对方的ip和端口
print(recv_data[0].decode('gbk'))
print(recv_data[1])
# 5. 关闭套接字
udp_socket.close()
案例:
# UDP聊天软件
import socket
def send_msg(udp_socket):
"""获取键盘数据,并将其发送给对方"""
# 1. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
# 2. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 3. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
# 4. 发送数据
udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
def recv_msg(udp_socket):
"""接收数据并显示"""
# 1. 接收数据
recv_msg = udp_socket.recvfrom(1024)
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print(">>>%s:%s" % (str(recv_ip), recv_msg))
def main():
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(("", 7890))
while True:
# 3. 选择功能
print("="*30)
print("1:发送消息")
print("2:接收消息")
print("="*30)
op_num = input("请输入要操作的功能序号:")
# 4. 根据选择调用相应的函数
if op_num == "1":
send_msg(udp_socket)
elif op_num == "2":
recv_msg(udp_socket)
else:
print("输入有误,请重新输入...")
if __name__ == "__main__":
main()
# 模仿飞秋消息格式实现广播
# 飞秋消息格式如下
#版本:消息序号:用户名:电脑名:功能(32表示发送消息):发送的消息内容
"1:123456789:莉莉:水果电脑:32:有时间么?"
import socket
# 1. 创建UDP套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind(("", 8080))
# 2. 设置UDP套接字允许其广播(注意如果udp套接字需要广播,则一定要添加此语句)
s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
dest_info = ("<broadcast>", 2425) # <broadcast>会自动改为本局域网的广播ip
send_data = "1:123456789:莉莉:水果电脑:32:有时间么?"
# 3. 发送广播数据
s.sendto(send_data.encode('gbk'), dest_info)
# 4. 关闭套接字
s.close()
TCP
1.TCP网络编程客户端
# 只发数据示例
from socket import *
# 1. 创建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 2. 链接服务器
tcp_client_socket.connect(("192.168.1.104", 8080))
# 提示用户输入数据
send_data = input("请输入要发送的数据:")
# 3. 向服务器发送数据
tcp_client_socket.send(send_data.encode("utf-8"))
# 4. 关闭套接字
tcp_client_socket.close()
# ————————————————————————————————————————————————————————————————-
# 可以发送数据并接收相关回应
import socket
# 1. 创建TCP套接字
client_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 链接服务器
client_s.connect(("192.168.1.104", 8080))
# 3. 获取要发送的数据
send_content = input("请输入要发送的数据内容:")
# 4.1 发送数据
client_s.send(send_content.encode("utf-8"))
# 4.2 接收数据
recv_content = client_s.recv(1024)
print(recv_content.decode("gbk"))
# 5. 关闭套接字
client_s.close()
2.TCP网络编程服务器
# 只能接收一次数据版本
import socket
# 1. 创建TCP套接字
server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定本地信息
server_s.bind(("", 7788))
# 3. 设置为被动的
server_s.listen(128)
# 4. 等待客户端链接
new_s, client_info = server_s.accept()
# 5. 用新的套接字为已经连接好的客户端服务器
recv_content = new_s.recv(1024)
print("%s>>>%s" % (str(client_info), recv_content.decode("gbk")))
# 6. 关闭套接字
new_s.close()
server_s.close()
# ——————————————————————————————————————————————————————————————————
# 可以接收多条数据版本
import socket
# 1. 创建TCP套接字
server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2. 绑定本地信息
server_s.bind(("", 7788))
# 3. 设置为被动的
server_s.listen(128)
# 4. 等待客户端链接
new_s, client_info = server_s.accept()
# 5. 用新的套接字为已经连接好的客户端服务器
while True:
recv_content = new_s.recv(1024)
print("%s>>>%s" % (str(client_info), recv_content.decode("gbk")))
if not recv_content:
# 当客户端调用了close后,recv返回值为空,此时服务套接字就可以close了
# 6. 关闭服务套接字
new_s.close()
break
# 7. 关闭监听套接字
server_s.close()
案例:
模拟文件下载器
# 客户端
from socket import *
def main():
# 创建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 目的信息
server_ip = input("请输入服务器ip:")
server_port = int(input("请输入服务器port:"))
# 链接服务器
tcp_client_socket.connect((server_ip, server_port))
# 输入需要下载的文件名
file_name = input("请输入要下载的文件名:")
# 发送文件下载请求
tcp_client_socket.send(file_name.encode("utf-8"))
# 接收对方发送过来的数据,最大接收1024个字节(1K)
recv_data = tcp_client_socket.recv(1024)
# print('接收到的数据为:', recv_data.decode('utf-8'))
# 如果接收到数据再创建文件,否则不创建
if recv_data:
with open("[接收]"+file_name, "wb") as f:
f.write(recv_data)
# 关闭套接字
tcp_client_socket.close()
if __name__ == "__main__":
main()
# 服务器
from socket import *
import sys
def get_file_content(file_name):
"""获取文件的内容"""
try:
with open(file_name, "rb") as f:
content = f.read()
return content
except:
print("没有下载的文件:%s" % file_name)
def main():
if len(sys.argv) != 2:
print("请按照如下方式运行:python3 xxx.py 7890")
return
else:
# 运行方式为python3 xxx.py 7890
port = int(sys.argv[1])
# 创建socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# 本地信息
address = ('', port)
# 绑定本地信息
tcp_server_socket.bind(address)
# 将主动套接字变为被动套接字
tcp_server_socket.listen(128)
while True:
# 等待客户端的链接,即为这个客户端发送文件
client_socket, clientAddr = tcp_server_socket.accept()
# 接收对方发送过来的数据
recv_data = client_socket.recv(1024) # 接收1024个字节
file_name = recv_data.decode("utf-8")
print("对方请求下载的文件名为:%s" % file_name)
file_content = get_file_content(file_name)
# 发送文件的数据给客户端
# 因为获取打开文件时是以rb方式打开,所以file_content中的数据已经是二进制的格式,因此不需要encode编码
if file_content:
client_socket.send(file_content)
# 关闭这个套接字
client_socket.close()
# 关闭监听套接字
tcp_server_socket.close()
if __name__ == "__main__":
main()
Tcp三次握手和四次挥手
多任务
并行和并发
- 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已)
- 并行:指的是任务数小于等于cpu核数,即任务真的是一起执行的
- 真正的"并行"只能在多核CPU上实现,现实中由于任务数量远远多于CPU的核心数量,所以基本上都是“并发”。操作系统会自动把很多任务轮流调度到每个核心上执行。
多线程
- 什么是线程
线程是一个抽象的概念,可以把它想象成一个实体,是CPU调度和分派的基本单位。
- python通过线程实现多任务
在Python中如果想使用线程实现多任务,可以使用thread模块 但是它比较底层,即意味着过程较为复杂不方便使用;推荐使用threading模块,它是对thread做了一些包装的,可以更加方便使用
3.线程tcp并发服务器
import socket
import threading
class HandleData(threading.Thread):
"""用来接收和处理数据的线程"""
def __init__(self, client_socket):
super().__init__()
# 初始化时传入要处理的客户端对象
self.client_socket = client_socket
def run(self):
# 接收/发送数据
while True:
# 接收数据
recv_content = self.client_socket.recv(1024)
if len(recv_content) != 0:
print(recv_content)
# 把收到的数据返回
self.client_socket.send(recv_content)
else:
# 如果收到空数据 就结束
self.client_socket.close()
break
def __del__(self):
# 线程对象销毁时 结束socket
self.client_socket.close()
class TCPServer(threading.Thread):
"""tcp服务器的线程 ,接收不同客户端的连接"""
def __init__(self, port):
# 注意 这里必须调用父类的初始化方法,否则很多重要的初始化就不会执行
# threading.Thread.__init__(self)
super().__init__()
# 创建套接字
self.server_s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定本地信息
self.server_s.bind(("", port))
# 将套接字由默认的主动链接模式改为被动模式(监听模块)
self.server_s.listen(128)
def run(self):
# 开启循环 可以一直接收不同的客户端
while True:
# 等待客户端进行链接
new_s, client_info = self.server_s.accept()
print('连接%s成功' % str(client_info))
# 创建一个处理客户端的线程对象,并且开启线程
HandleData(new_s).start()
def __del__(self):
# 关闭套接字
self.server_s.close()
def main():
# 创建服务器的线程对象
tcp_server = TCPServer(12000) # 12000表示TCP要绑定的端口
# 开启服务器线程
tcp_server.start()
if __name__ == '__main__':
main()
4.可同时收发数据的UDP聊天程序
import socket
import threading
def send_msg(udp_socket):
"""获取键盘数据,并将其发送给对方"""
while True:
print("1: 发送数据")
print("2: 退出程序")
op = input("请输入操作序号:")
if op == "1":
# 1. 输入对方的ip地址
dest_ip = input("\n请输入对方的ip地址:")
# 2. 输入对方的port
dest_port = int(input("\n请输入对方的port:"))
while True:
# 3. 从键盘输入数据
msg = input("\n请输入要发送的数据:")
if msg:
# 4. 发送数据
udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port))
else:
# 要是没有输入内容则认为是要重新输入ip、port
break
elif op == "2":
# 结束发送
break
# 结束,关闭套接字
udp_socket.close()
def recv_msg(udp_socket):
"""接收数据并显示"""
while True:
try:
# 1. 接收数据
recv_msg = udp_socket.recvfrom(1024)
except:
break
else:
# 2. 解码
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode("utf-8")
# 3. 显示接收到的数据
print("接收到来自于%s的数据:%s" % (str(recv_ip), recv_msg))
def main():
# 1. 创建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 绑定本地信息
udp_socket.bind(("", 7890))
# 3. 创建一个新的线程,用来接收数据
udp_r = threading.Thread(target=recv_msg, args=(udp_socket,))
# 4. 创建一个新的线程,用来发送数据
udp_s = threading.Thread(target=send_msg, args=(udp_socket,))
# 5. 运行创建的子线程
udp_r.start()
udp_s.start()
if __name__ == "__main__":
main()
5.多线程存在的问题
如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确
6.解决方法 - 使用互斥锁
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
互斥锁用法
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
多进程
1.什么是进程
一个程序运行起来后,代码+用到的资源 称之为进程,它是操作系统分配资源的基本单元。
2.实现多任务
在python中使用使用进程实现多任务的方式有3种:
创建Process对象
基础Process类,创建自己的对象,实现run方法
使用进程池片
3.创建进程
# 唱歌与跳舞
import time
import threading # 线程的包
def dance():
while True:
print("跳舞")
time.sleep(1)
def song():
while True:
print("唱歌")
time.sleep(1)
def main():
"""一边唱歌一边跳舞"""
threading.Thread(target=dance).start()
threading.Thread(target=song).start()
if __name__ == '__main__':
main()
start():启动子进程实例(创建子进程)
is_alive():判断子进程是否还在活着
join([timeout]):是否等待子进程执行结束,或等待多少秒
terminate():不管任务是否完成,立即终止子进程
4.进程PID(进程号)
pid是唯一标识进程的号
os.getpid()获取当前进程的pid
os.getppid()获取父进程的pid
5.进程间通信-Queue
使用
- 使用Queue()时,若括号中没有指定最大可接收的消息数量,或数量为负值,那么就代表可接受的消息数量没有上限(直到内存的尽头)
- Queue的几个方法功能说明: Queue.qsize():返回当前队列包含的消息数量
Queue.empty():如果队列为空,返回True,反之False
Queue.full():如果队列满了,返回True,反之False Queue.get([block[,
timeout]]):获取队列中的一条消息,然后将其从列队中移除,block默认值为True
如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),直到从消息列队读到消息为止,如果设置了timeout,则会等待timeout秒,若还没读取到任何消息,则抛出Queue.Empty异常
如果block值为False,消息列队如果为空,则会立刻抛出Queue.Empty异常
Queue.get_nowait():相当Queue.get(False) Queue.put(item,[block[,
timeout]]):将item消息写入队列,block默认值为True
如果block使用默认值,且没有设置timeout(单位秒),消息列队如果已经没有空间可写入,此时程序将被阻塞(停在写入状态),直到从消息列队腾出空间为止,如果设置了timeout,则会等待timeout秒,若还没空间,则抛出Queue.Full异常
如果block值为False,消息列队如果没有空间可写入,则会立刻抛出Queue.Full异常
Queue.put_nowait(item):相当Queue.put(item, False)
实例
# 进程使用队列地行通讯
# 创建一个进程的队列,一个进程放,一个进程取
# 队列就是进程之间进行通讯
import multiprocessing
import time
# 取
def get_data(queue):
while True:
# 判断如果不空就获取数据
if not queue.empty():
data = queue.get()
print("取到的数据:", data)
time.sleep(1)
# 放
def put_data(queue):
for temp in range(1000000):
# 判断如果不满 就添加数据
if not queue.full():
queue.put(temp)
time.sleep(1)
def main():
queue = multiprocessing.Queue(3)
"""一个读一个取"""
# 这个把数据 放到队列中
multiprocessing.Process(target=put_data, args=(queue,)).start()
# 这个得到队列的数据
multiprocessing.Process(target=get_data, args=(queue,)).start()
if __name__ == '__main__':
main()
6.进程池
-
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。
-
初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务
示例:
from multiprocessing import Pool
import os
import random
import time
def worker(num):
for i in range(5):
print('===pid=%d==num=%d='%(os.getpid(),num))
time.sleep(1)
# 3表示进程池中最多有三个进程一起执行
pool=Pool(3)
for i in range(10):
print('---%d---'%i)
# 向进程中添加任务
# 注意:如果添加的任务数量超过了进程池中进程的个数的话,那么就不会接着往进程池中添加,
# 如果还没有执行的话,他会等待前面的进程结束,然后在往
# 进程池中添加新进程
pool.apply_async(worker,(i,))
pool.close() # 关闭进程池
pool.join() # 主进程在这里等待,只有子进程全部结束之后,在会开启主线程
用法
apply_async(func[, args[, kwds]]):使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;
进程池里的通信:
- 需要使用multiprocessing.Manager()中的Queue(),而不是multiprocessing.Queue()
7.案例:
# 文件夹copy器
import multiprocessing
import os
import time
import random
def copy_file(queue, file_name,source_folder_name, dest_folder_name):
"""copy文件到指定的路径"""
f_read = open(source_folder_name + "/" + file_name, "rb")
f_write = open(dest_folder_name + "/" + file_name, "wb")
while True:
time.sleep(random.random())
content = f_read.read(1024)
if content:
f_write.write(content)
else:
break
f_read.close()
f_write.close()
# 发送已经拷贝完毕的文件名字
queue.put(file_name)
def main():
# 获取要复制的文件夹
source_folder_name = input("请输入要复制文件夹名字:")
# 整理目标文件夹
dest_folder_name = source_folder_name + "[副本]"
# 创建目标文件夹
try:
os.mkdir(dest_folder_name)
except:
pass # 如果文件夹已经存在,那么创建会失败
# 获取这个文件夹中所有的普通文件名
file_names = os.listdir(source_folder_name)
# 创建Queue
queue = multiprocessing.Manager().Queue()
# 创建进程池
pool = multiprocessing.Pool(3)
for file_name in file_names:
# 向进程池中添加任务
pool.apply_async(copy_file, args=(queue, file_name, source_folder_name, dest_folder_name))
# 主进程显示进度
pool.close()
all_file_num = len(file_names)
while True:
file_name = queue.get()
if file_name in file_names:
file_names.remove(file_name)
copy_rate = (all_file_num-len(file_names))*100/all_file_num
print("\r%.2f...(%s)" % (copy_rate, file_name) + " "*50, end="")
if copy_rate >= 100:
break
print()
if __name__ == "__main__":
main()
协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。