繁体   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