Как непрерывно отправлять и получать скриншоты с помощью асинхронного программирования сокетов?

У меня есть цель постоянно отправлять и получать снимки экрана с помощью асинхронного сокета, и я создал этот следующий код после просмотра нескольких ссылок в Интернете. Перед попыткой непрерывной отправки я протестировал отправку только один раз, но скриншот не был правильно получен на сервере.

Некоторая идея о том, как это исправить? Заранее спасибо.

Сервер:

Imports System.IO
Imports System.Net
Imports System.Net.Sockets
Imports System.Text

Public Class frmServer
    Dim serverSocket As Socket
    Dim clientSocket As Socket
    Dim byteData(1023) As Byte

    Private Sub FrmServer_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        serverSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        Dim IpEndPoint As IPEndPoint = New IPEndPoint(IPAddress.Any, 8800)
        serverSocket.Bind(IpEndPoint)
        serverSocket.Listen(5)
        serverSocket.BeginAccept(New AsyncCallback(AddressOf OnAccept), Nothing)
    End Sub

    Private Sub OnAccept(ByVal ar As IAsyncResult)
        clientSocket = serverSocket.EndAccept(ar)
        'New connection established
        clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, New AsyncCallback(AddressOf OnReceive), clientSocket)
        serverSocket.BeginAccept(New AsyncCallback(AddressOf OnAccept), Nothing)
    End Sub

    Private Sub OnReceive(ByVal ar As IAsyncResult)
        Dim client As Socket = ar.AsyncState
        client.EndReceive(ar)

        'Using MStream As New MemoryStream(byteData)
        'Dim returnImage As Image = Image.FromStream(MStream)
        'pbDesktop.Image = returnImage
        'End Using

        File.WriteAllBytes(Environment.CurrentDirectory & "\screenshot.jpg", byteData)

        'Array.Clear(byteData, 0, byteData.Length)
        clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, New AsyncCallback(AddressOf OnReceive), clientSocket)
    End Sub

Клиент:

Imports System.Drawing.Imaging
Imports System.IO
Imports System.Net
Imports System.Net.Sockets

Public Class frmClient

    Private Function BytesScreen() As Byte()
        Dim bounds As Rectangle
        Dim screenshot As Bitmap
        Dim graph As Graphics
        bounds = Screen.PrimaryScreen.Bounds
        screenshot = New Bitmap(bounds.Width, bounds.Height)
        graph = Graphics.FromImage(screenshot)
        graph.CopyFromScreen(bounds.X, bounds.Y, 0, 0, bounds.Size, CopyPixelOperation.SourceCopy)

        Dim ms As New MemoryStream()
        screenshot.Save(ms, ImageFormat.Jpeg)
        Dim bitmapbytestream(ms.Length) As Byte
        bitmapbytestream = ms.ToArray

        Return bitmapbytestream
    End Function

    Dim clientSocket As Socket

    Private Sub BtnConnect_Click(sender As Object, e As EventArgs) Handles btnConnect.Click
        clientSocket = New Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)
        Dim IpAddress As IPAddress = IPAddress.Parse("127.0.0.1")
        Dim IpEndPoint As IPEndPoint = New IPEndPoint(IpAddress, 8800)
        clientSocket.BeginConnect(IpEndPoint, New AsyncCallback(AddressOf OnConnect), Nothing)
    End Sub

    Private Sub OnConnect(ByVal ar As IAsyncResult)
        clientSocket.EndConnect(ar)
        Send(BytesScreen, clientSocket)
    End Sub

    Private Sub Send(ByVal byteData As Byte(), ByVal client As Socket)
        Try
            client.BeginSend(byteData, 0, byteData.Length, SocketFlags.None, New AsyncCallback(AddressOf OnSend), client)
        Catch ex As Exception
            If Not client.Connected Then 'if connection was forcibly disconnected
                'reconnecting to the server
                btnConnect.PerformClick()
            End If
        End Try
    End Sub

    Private Sub OnSend(ByVal ar As IAsyncResult)
        Dim client As Socket = ar.AsyncState
        client.EndSend(ar)
        'Send(BytesScreen, clientSocket)
    End Sub

End Class

person BrowJr    schedule 05.03.2020    source источник
comment
Первая, наиболее очевидная проблема заключается в том, что ваш сервер считывает не более 1 КБ данных (1024 байта) за раз. Большинство изображений больше, чем это, однако это не так просто, как увеличение размера буфера, поскольку вы не знаете, сколько данных даст вам каждый вызов Receive. TCP — это потоковый протокол, то есть на прикладном уровне все данные представлены в виде непрерывного потока байтов, и вы получаете только то, что скачали на данный момент. Таким образом, для одного вызова Send может потребоваться несколько вызовов Receive в зависимости от размера ваших данных.   -  person Visual Vincent    schedule 05.03.2020
comment
Что вам нужно, так это что-то, чтобы сказать вам, где фрагменты данных начинаются и заканчиваются. Самое простое, но очень надежное решение называется префиксом длины, о котором вы можете узнать больше в моем более раннем ответе. с примером реализации в другом.   -  person Visual Vincent    schedule 05.03.2020
comment
(Извините, пришлось разделить это на несколько комментариев) То, как вы решите это использовать, зависит от того, что вам нужно. Если вы просто отправляете один файл за раз и ничего не промежуточно, вы можете отправить все в одном пакете, но если вам нужно отправить другие виды данных, не дожидаясь, пока весь файл будет отправлено, вы должны разделить ваш файл на несколько небольших пакетов и отправить их один за другим. Я склонен разбивать файлы на пакеты по 8192 байта (8 КБ), так как, исходя из моего опыта, это обеспечивает хорошее соотношение между скоростью и задержкой в ​​широком диапазоне различных скоростей соединения.   -  person Visual Vincent    schedule 05.03.2020
comment
@VisualVincent, BeginReceive() и BeginSend(), являются асинхронными сокетами, а не синхронными сокетами: D. Спасибо за ваше объяснение комментариев выше, они будут полезны тем, кто использует сокеты синхронизации.   -  person BrowJr    schedule 05.03.2020
comment
Я думаю, вы неправильно поняли... Префикс длины - это просто способ обозначить, где начинается и заканчивается часть данных. Неважно, используете ли вы сокеты синхронно или асинхронно, вам все равно нужно знать, сколько данных нужно получить и где вы в данный момент читаете по отношению к остальной части. Когда я сказал Send и Receive, я имел в виду не синхронные методы, а скорее любое время, когда вы выполняете операцию отправки или получения в потоке, асинхронную или нет.   -  person Visual Vincent    schedule 06.03.2020
comment
but if you need to send other kinds of data without waiting for the entire file to be sent - Да, это тоже цель.   -  person BrowJr    schedule 06.03.2020
comment
Хотя мой пример реализации во втором ответе устарел и основан на синхронных методах с использованием потоков, вполне возможно сделать это и с помощью асинхронных вызовов. Это просто вопрос сохранения состояния, то есть отслеживания того, для какого пакета вы получаете данные. Для этого используется последний параметр BeginRead() и BeginSend() (в котором вы указываете clientSocket). Хотя я бы скорее рекомендовал методы ReadAsync() и SendAsync(), поскольку они позволяют использовать асинхронность, но при этом сохраняют более чистый, синхронный стиль кода.   -  person Visual Vincent    schedule 06.03.2020
comment
@VisualVincent, я уже какое-то время пробовал (собственный код), асинхронные сокеты работают, отправляя и получая данные любого типа правильно, но до сих пор безуспешно. Уже с синхронным сокетом все кажется проще и с несколькими ссылками в сети :D.   -  person BrowJr    schedule 06.03.2020
comment
Попробуйте ReadAsync() и WriteAsync() из NetworkStream вместе с Await, если вы используете любую версию Visual Studio, более новую, чем 2010. Они созданы для того, чтобы ваш код сохранял свой обычный нисходящий поток, оставаясь при этом асинхронным и не прерывая вызывающий поток.   -  person Visual Vincent    schedule 06.03.2020
comment
@VisualVincent, however it isn't as simple as increasing the buffer size — сработало увеличение размера буфера (Dim byteData(81927657) As Byte). since you don't know how much data each Receive call will give you. - Как это обнаружить? какая-то идея?   -  person BrowJr    schedule 06.03.2020
comment
since you don't know how much data each Receive call will give you. - я думаю, что это можно решить. Также здесь можно увидеть пример кода, основанный на первой ссылке ответа @Mike.   -  person BrowJr    schedule 06.03.2020
comment
Это здорово, однако вы понимаете, что только что нашли три разных ссылки, каждая из которых описывает префикс длины? Они похожи на мои ответы, только с небольшими вариациями. Однако это хорошо, потому что это означает, что теперь у вас есть много исследовательского материала. :)   -  person Visual Vincent    schedule 06.03.2020