爲更好地保證教學質量和提升學生的學習積極性,我使用Python開發了一套課堂教學管理系統,具備在線點名、在線答疑、隨機提問、在線做業管理、在線自測、在線考試、數據彙總、試卷生成、屏幕廣播等功能,教師端運行界面以下圖所示:服務器
該系統投入使用已有4個學期,效果很是好,不只能夠知足上課的各類須要,還能夠做爲「Python程序設計」課程的一個完整教學案例講給學生,適用教材包括《Python程序設計基礎》(董付國編著,清華大學出版社)、《Python程序設計(第2版)》(董付國多線程
編著,清華大學出版社)、《Python能夠這樣學》(董付國著,清華大學出版社)。本文重點介紹屏幕廣播功能的技術要點,本系統界面使用tkinter編寫,使用擴展庫pillow實現屏幕截圖,使用socket實現屏幕截圖的傳送,使用多線程技術實現多客戶端的數據傳app
輸,文中略去了有關標準庫和擴展庫的導入代碼。socket
一、學生端啓動以後,監聽UDP端口1000,等待教師端發送屏幕廣播指令,代碼以下:ide
def udpListen(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 監聽本機10000端口 sock.bind(('',10000)) while True: data, addr = sock.recvfrom(100) # 收到服務器發來的廣播指令 if data == b'startBroadCast': threading.Thread(target=receiveBroadCast).start() sock.close() threading.Thread(target=udpListen).start()
二、教師端經過界面上的按鈕「開始屏幕廣播」給局域網內全部學生端發送指令,同時監聽TCP端口10001,等待學生端的鏈接,而後給每個學生端鏈接發送本機屏幕截圖,每0.5秒刷新一次。代碼以下:函數
broadcasting = False def broadcast(conn): global broadcasting while broadcasting: time.sleep(0.8) image = ImageGrab.grab() size = image.size imageBytes = image.tobytes() length = len(imageBytes) # 通知將要開始發送截圖 conn.send(b'*****') fhead = struct.pack('I32sI', length, str(size).encode(), len(str(size).encode())) conn.send(fhead) conn.send(imageBytes) else: conn.send(b'#####') conn.close() def broadcastMain(): '''廣播屏幕截圖的主線程函數''' global sockBroadCast sockBroadCast = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sockBroadCast.bind(('', 10001)) sockBroadCast.listen(150) while broadcasting: try: conn, addr = sockBroadCast.accept() except: return threading.Thread(target=broadcast, args=(conn,)).start() else: sockBroadCast.close() def onbuttonStartBroadCastClick(): global broadcasting broadcasting = True # 啓動服務器廣播線程 threading.Thread(target=broadcastMain).start() # 通知客戶端開始接收廣播 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) IP = socket.gethostbyname(socket.gethostname()) IP = IP[:IP.rindex('.')]+'.255' sock.sendto(b'startBroadCast', (IP, 10000)) buttonStopBroadCast['state'] = 'normal' buttonStartBroadCast['state'] = 'disabled' buttonStartBroadCast = tkinter.Button(root, text='開始屏幕廣播', command=onbuttonStartBroadCastClick) buttonStartBroadCast.place(x=20, y=380, width=100, height=30) def onbuttonStopBroadCastClick(): global broadcasting broadcasting = False sockBroadCast.close() buttonStopBroadCast['state'] = 'disabled' buttonStartBroadCast['state'] = 'normal' buttonStopBroadCast = tkinter.Button(root, text='結束屏幕廣播', command=onbuttonStopBroadCastClick) buttonStopBroadCast['state'] = 'disabled' buttonStopBroadCast.place(x=130, y=380, width=100, height=30)
三、學生端收到教師端經過UDP廣播發送的屏幕廣播指令以後,建立TCP Socket,鏈接教師端,並接收教師端發來的屏幕截圖,而後使用建立頂端顯示的tkinter界面用來顯示屏幕截圖。主要功能代碼以下:學習
# 使用TCP接收廣播 def receiveBroadCast(): # 獲取屏幕尺寸,建立頂端顯示的無標題欄窗體 screenWidth = 640 screenHeight = 480 top = tkinter.Toplevel(root, width=screenWidth, height=screenHeight) top.overrideredirect(True) # 頂端顯示 top.attributes('-topmost', 1) # 建立畫布,用來顯示圖像 canvas = tkinter.Canvas(top, bg='white', width=screenWidth, height=screenHeight) canvas.pack(fill=tkinter.BOTH, expand=tkinter.YES) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) serverIP = entryServerIP.get() # 鏈接服務器10001端口,失敗直接返回 try: sock.connect((serverIP, 10001)) except: print('error') top.destroy() return # 接收服務器指令 # *****表示開始傳輸一個新的截圖 # #####表示本次廣播結束 while True: data = sock.recv(5) if data == b'*****': # 接收服務器發來的一屏圖像 # 圖像大小,字節總數量 len_head = struct.calcsize('I32sI') data = sock.recv(len_head) length, size, sizeLength = struct.unpack('I32sI', data) length = int(length) size = eval(size[:int(sizeLength)]) rest = length image = [] while True: if rest == 0: break elif rest > 40960: temp = sock.recv(40960) rest -= len(temp) image.append(temp) else: temp = sock.recv(rest) rest -= len(temp) image.append(temp) image = b''.join(image) # 更新顯示 image = Image.frombytes('RGB', size, image) image = image.resize((screenWidth, screenHeight)) image = ImageTk.PhotoImage(image) try: canvas.delete(imageId) except: pass imageId = canvas.create_image(screenWidth//2, screenHeight//2, image=image) elif data == b'#####': # 廣播結束 break # 本次廣播結束,關閉窗口 sock.close() top.destroy()