一、发送端。
f.py,源码如下:
from time import sleep
from os import startfile
from zlib import compress
from threading import Thread
from socket import (socket,AF_INET,SOCK_DGRAM,SOL_SOCKET,SO_BROADCAST)
from tkinter import Tk,BooleanVar,Button,Label
from PIL.ImageGrab import grab
root = Tk()
root.title("屏幕广播发送端-小6哥")
# 初始尺寸和位置
root.geometry('320x60+500+209')
#两个方向都不允许缩放
root.resizable(False,False)
#缓冲区大小
BUFFER_SIZE = 60*1024
#控制是否正在发送的变量
sending = BooleanVar(root,value=False)
def send_image():
sock = socket(AF_INET,SOCK_DGRAM)
# 广播
sock.setsockopt(SOL_SOCKET,SO_BROADCAST,True)
IP ='255.255.255.255'# 发送地址为<广播>
serverPort = 22222# 请自行选择
# Windows系统因为特殊的原因要多一些代码
import platform
if platform.system() == 'Windows':
with socket(AF_INET, SOCK_DGRAM) as c:
c.connect(('114.114.114.114', 80))
local_ip = c.getsockname()[0]
sock.bind((local_ip, serverPort))
while sending.get():
# 屏慕截图
im = grab()
w, h = im.size
# 把图像转换为字节串,压缩后传输,减少带宽占用
im_bytes = compress(im.tobytes())
# 通知大家开始发送一幅图像
sock.sendto(b'start',(IP,serverPort))
#分块发送,以免字节串太长无法发送
for i in range(len(im_bytes)//BUFFER_SIZE+1):
start = i * BUFFER_SIZE
end = start + BUFFER_SIZE
sock.sendto(im_bytes[start:end],(IP,serverPort))
# 通知大家已发送完一幅图像
sock.sendto(b'_over'+str((w,h)).encode(),(IP,serverPort))
#0.5秒截图发送一次
sleep(0.5)
sock.sendto(b'close',(IP,serverPort))
sock.close()
lbCopyRight = Label(root,text='小6哥开发,关注微信公众号“暂无”查看源码识',fg='red',cursor='plus')
lbCopyRight.place(x=5,y=5,width=310,height=20)
url = r'https://blog.csdn.net/liujava56?spm=1011.2266.3001.5343'
lbCopyRight.bind("<Button-1>",lambda e: startfile(url))
def btnStartClick():
sending.set(True)
Thread(target=send_image).start()
btnStart['state'] = 'disabled'
btnStop['state'] = 'normal'
btnStart = Button(root,text='开始广播',command=btnStartClick)
btnStart.place(x=30,y=30,width=125,height=20)
def btnStopClick():
sending.set(False)
btnStart['state'] = 'normal'
btnStop['state'] ='disabled'
btnStop = Button(root,text='停止广播',command=btnStopClick)
btnStop['state'] ='disabled'
btnStop.place(x=165,y=30,width=125,height=20)
root.mainloop()
二、接收端
s.py,源码如下:
from time import sleep
from os import startfile
from zlib import decompress
from threading import Thread
from tkinter import Tk,Canvas,BOTH,YES,Label
from socket import socket,AF_INET,SOCK_DGRAM
from PIL.Image import frombytes
from PIL.ImageTk import PhotoImage
#url = r'https://blog.csdn.net/liujava56?spm=1011.2266.3001.5343'
#startfile(url)
root = Tk()
root.title('屏幕广播接收端-小6哥-微信公众号“暂无”')
root.geometry('800x600+0+0')
# root.overrideredirect(True)
root.attributes('-topmost',True)
#使用Labe1显示图像,自带双缓冲,避免图像闪烁
lbImage = Label(root)
lbImage.pack(fill=BOTH,expand=YES)
BUFFER_SIZE = 60 *1024
data =[]
def show_image(image_bytes, image_size):
#窗口尺寸
screen_width = root.winfo_width()
screen_height = root.winfo_height()
# 必须使用全局变量,否则会不显示图像
global im
#重建图像,如果接收图像数据不完整就直接放弃
try:
im = frombytes('RGB',image_size,image_bytes)
except:
return
# 显示图像
im = im.resize((screen_width,screen_height))
im = PhotoImage(im)
lbImage['image'] = im
lbImage.image = im
def recv_image():
global receiving,im
# 创建UDP套接字,绑定用来接收屏幕广播的端口
sock = socket(AF_INET,SOCK_DGRAM)
serverPort = 22222
sock.bind(('',serverPort))
while receiving:
# 开始接收图像数据
try:
chunk,_= sock.recvfrom(BUFFER_SIZE)
except:
# 收到不完整图像,直接丢弃
data.clear()
continue
# 等待接收开始标志
# 防止半路加入的客户端从中间开始接收,导致图像不完整
# 收到开始标志
if chunk == b'start':
data.clear()
continue
#一幅图像传输结束,重建图像并显示
elif chunk.startswith(b'_over'):
image_size = eval(chunk[5:])
try:
image_data = decompress(b''.join(data))
except:
# 收到不完整图像,直接丢弃
data.clear()
continue
global thread_show
# 创建线程并启动
thread_show = Thread(target=show_image,args=(image_data,image_size))
thread_show.daemon =True
thread_show.start()
data.clear()
continue
elif chunk == b'close':
#receiving = False
#sock.close()
sleep(0.1)
#root.destroy()
data.append(chunk)
receiving = True
thread_sender = Thread(target=recv_image)
thread_sender.daemon = True
thread_sender.start()
#退出程序时,如果正在接收就停止接收再退出,保证线程安全
def close_window():
global receiving
receiving = False
sleep(0.3)
root.destroy()
root.protocol('WM_DELETE_WINDOW',close_window)
root.mainloop()