Python實現局域網內屏幕廣播的技術要點分析(轉載)

  轉載:https://mp.weixin.qq.com/s?timestamp=1498531736&src=3&ver=1&signature=Eq6DPvkuGJi*G5spckebex6lYxUF**UjirOaOrRPQy8C8naLhUiAH-LIqNEmXvsjhalJZc3-uWbnntdmmer5raOoqYH4MAAQT9SoV5qrOJtdnNWJZAUxPlgzzCS1nv6ar3uoIIRG11OHhHPXVEU3pWcFP4Cxz5s7Kh1XjKqgOsg=canvas

  爲更好地保證教學質量和提升學生的學習積極性,我使用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()
相關文章
相關標籤/搜索