簡體   English   中英

通過本地 TCP 套接字發送大字節數組太慢

[英]Sending large byte array over local TCP socket is too slow

我通過在發送設備上使用 DirectX 桌面復制將我的桌面從一台設備流式傳輸到另一台設備,將生成的 bitmap 的數據流復制到一個字節數組,然后通過套接字將該字節數組通過 WiFi 發送到本地連接的設備聯系。

另一個設備接收字節數組,將其復制到數據流中以從中創建 bitmap,然后將其呈現給 window。

復制/渲染只需要 <20ms。

發送每個字節數組所需的時間約為 900ms - 1400ms。 但是,如果我在同一設備上作為服務器/客戶端連接到套接字,它會非常快。 所以問題在於實際將其發送到其他連接的設備。

理想情況下,我希望這個發送/接收過程花費 <100 毫秒。

如果我發送像單個 int 這樣簡單的東西,它會立即收到。

我還應該注意 DataStream object 是每個像素的顏色數據數組,因此這是每個分辨率的恆定大小(ARGB 1920x1080 = 8294400 字節)。

我發送的設備的 wifi 適配器的最大鏈接速度為 400/400 Mbps,接收設備為 240/240,所以我認為這也不是問題。

這是我的套接字代碼:

using System;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace StreamSocket {
    public class Instance {
        private Socket Host;
        private Socket Socket;
        
        public string InstanceType { get; set; }
        public int BufferSize { get; set; }
        public bool IsConnected { get { return Socket != null && Socket.Connected; } }

        public event EventHandler<EventArgs> Connected = delegate { };
        public event EventHandler<EventArgs> Disconnected = delegate { };
        public event EventHandler<EventArgs> ConnectionFailed = delegate { };
        public event EventHandler<DataReceivedEventArgs> DataReceived = delegate { };

        public class DataReceivedEventArgs : EventArgs {
            public int Code { get; private set; }
            public byte[] Data { get; private set; }

            public DataReceivedEventArgs(int code, byte[] data) {
                Code = code; Data = data;
            }
        }

        public void Start(int bufferSize, string ip = "") {
            BufferSize = bufferSize;

            if(ip == string.Empty) { //Start as Server
                Host = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                Host.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                Host.Bind(new IPEndPoint(IPAddress.Any, 8080));
                Host.Listen(1);
                Host.BeginAccept(new AsyncCallback(AcceptCallback), Host);
            } else { //Start as Client
                Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                Socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, 1);
                Socket.BeginConnect(ip, 8080, new AsyncCallback(ConnectCallback), Socket);
            }
        }

        public void Disconnect() {
            if(Socket == null) { return; }

            Disconnected(this, new EventArgs());
            if(Host != null) {
                if(Socket != null) {
                    Socket.Disconnect(true);
                }
                Host.BeginAccept(new AsyncCallback(AcceptCallback), Host);
            } else {
                Socket.Close();
            }
        }

        private void AcceptCallback(IAsyncResult result) {
            Socket = ((Socket)result.AsyncState).EndAccept(result);
            Connected(this, new EventArgs());
            BeginReceive();
        }

        private void ConnectCallback(IAsyncResult result) {
            try {
                Socket.EndConnect(result);
                Connected(this, new EventArgs());
                BeginReceive();
            } catch {
                ConnectionFailed(this, new EventArgs());
            }
        }

        public void Send(byte[] data) {
            if(!IsConnected) { return; }

            try {
                Socket.BeginSend(data, 0, data.Length, 0, new AsyncCallback(SendCallback), Socket);
            } catch { Disconnect(); }
        }

        private void SendCallback(IAsyncResult result) {
            try {
                Socket.EndSend(result);
            } catch { Disconnect(); }
        }

        private void BeginReceive() {
            if(!IsConnected) { return; }

            SocketData data = new SocketData() { Buffer = new byte[BufferSize] };
            Socket.BeginReceive(data.Buffer, 0, BufferSize, 0, new AsyncCallback(ReceiveCallback), data);
        }

        private void ReceiveCallback(IAsyncResult result) {
            if(!IsConnected) { return; }

            try {
                SocketData data = (SocketData)result.AsyncState;
                int bytesRead = Socket.EndReceive(result);
                if(bytesRead > 0) {
                    if(data.Length == 0) {
                        //Get headers from start of first buffer
                        using(MemoryStream ms = new MemoryStream(data.Buffer)) {
                            using(BinaryReader br = new BinaryReader(ms)) {
                                data.Code = br.ReadByte();
                                data.Length = br.ReadInt32();
                            }
                        }
                        //Write the rest of the buffer
                        if(data.Buffer.Length > sizeof(byte) + sizeof(int)) {
                            data.Stream.Write(data.Buffer, sizeof(byte) + sizeof(int), bytesRead - (sizeof(byte) + sizeof(int)));
                        }
                    } else {
                        //Writer buffer chunk that doesn't start with header
                        data.Stream.Write(data.Buffer, 0, bytesRead);
                    }

                    if(data.Stream.Length != data.Length) {
                        //Expected to receive more chunks
                        Socket.BeginReceive(data.Buffer, 0, BufferSize, 0, new AsyncCallback(ReceiveCallback), data);
                    } else {
                        //All data received
                        DataReceived(this, new DataReceivedEventArgs(data.Code, data.Stream.ToArray()));
                        data.Stream.Dispose();
                        
                        BeginReceive();
                    }
                } else {
                    Disconnect();
                }
            } catch { Disconnect(); }
        }
    }
}

以上由其他 2 個類 Server/Client 使用,它們啟動套接字實例並接收接收數據時的事件:

private void Client_DataReceived(object sender, Instance.DataReceivedEventArgs e) {
    switch((Code)e.Code) {
        case Code.BitmapReceived:
            //This sends the result to the DX Renderer
            BytesToBitmapData(e.Data);
            break;
    }
}

以下是在 BitmapData 和字節數組之間進行轉換的函數:(這些只需要大約 <10ms,所以我認為它們不是問題)

public static byte[] BitmapDataToBytes(SerialCode code, BitmapData bitmapData) {
    using(MemoryStream ms1 = new MemoryStream()) {
        using(BinaryWriter bw1 = new BinaryWriter(ms1)) {
            bw1.Write((byte)code); //Header - Code
            bw1.Write((sizeof(int) * 4) + bitmapData.Data.Length); //Header - Length
            //Body:
            bw1.Write(bitmapData.Size.Width);
            bw1.Write(bitmapData.Size.Height);
            bw1.Write(bitmapData.Pitch);
            bw1.Write(bitmapData.Data.Length);
            bw1.Write(bitmapData.Data);

            return ms1.ToArray();
        }
    }
}

public static BitmapData BytesToBitmapData(byte[] bytes) {
    using(MemoryStream ms = new MemoryStream(bytes)) {
        using(BinaryReader br = new BinaryReader(ms)) {
            Size size = new Size(br.ReadInt32(), br.ReadInt32());
            int pitch = br.ReadInt32();
            int length = br.ReadInt32();
            byte[] data = br.ReadBytes(length);

            return new BitmapData() { Size = size, Pitch = pitch, Data = data };
        }
    }
}

我認為問題可能與我的套接字代碼的效率或 TCP 的限制有關? 如何提高發送大字節 arrays 的速度?

240 Mbit/s 表示的速度可以在 240/2 *95% = 14 MB/s 左右移動(Wi-Fi 開銷為 /2,TCP 在 IPv4 開銷上為 95%),大約 1.7 個屏幕價值或 580 ms/屏幕 - 前提是附近網絡(或藍牙或......)沒有網絡擁塞。

您需要提高鏈接速度 [*1] - 千兆以太網約為。 快 8 倍 - 或通過壓縮(周圍有更快的壓縮器)或智能緩存(僅傳輸更改的像素)來減少數據量。 對於任意視頻,會想到像 H.264 這樣的視頻編解碼器,大多數 GPU 或強大的 CPU 可以實時壓縮。

順便說一句,HDMI 不會無緣無故地使用高達 48 Gbit/s 的傳輸速度。

[*1] 較低的速度很重要,因為您無法以比接收速度更快的速度傳輸數據。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM