c# - 如何在异步套接字中接收完整的屏幕截图?

标签 c# asyncsocket beginreceive

我有一个 Java android 代码将数据(图像或文本)发送到 C# 应用程序,以接收这些数据我正在使用异步套接字。但是存在一个与 BeginReceive() 函数相关的问题,当发送图像时,函数没有接收到完整的数据。那么我该如何制作一种“循环”接收完整数据并在 Picturebox 上显示图像(例如)?

表单

private Listener listener;
private Thread startListen;

private Bitmap _buffer;

public frmMain()
{
  InitializeComponent();
}

private void serverReceivedImage(Client client, byte[] image)
{
    try
    {
        byte[] newImage = new byte[image.Length - 6];

        Array.Copy(image, 6, newImage, 0, newImage.Length);

            using (var stream = new MemoryStream(newImage))
            {
                using (var msInner = new MemoryStream())
                {

                  stream.Seek(2, SeekOrigin.Begin);

                  using (DeflateStream z = new DeflateStream(stream, CompressionMode.Decompress))
                  {
                    z.CopyTo(msInner);
                  }

                  msInner.Seek(0, SeekOrigin.Begin);

                  var bitmap = new Bitmap(msInner);
                  Invoke(new frmMain.ImageCompleteDelegate(ImageComplete), new object[] { bitmap });
                }
            }
    }
     catch (Exception)
     {
        System.Diagnostics.Process.GetCurrentProcess().Kill();
     }
}

private delegate void ImageCompleteDelegate(Bitmap bitmap);
private void ImageComplete(Bitmap bitmap)
{
   if (_buffer != null)
       _buffer.Dispose();

       _buffer = new Bitmap(bitmap);
       pictureBox1.Size = _buffer.Size;
       pictureBox1.Invalidate();
}

private void pictureBox1_Paint(object sender, PaintEventArgs e)
{
  if (_buffer == null) return;
      e.Graphics.DrawImage(_buffer, 0, 0);
}

private void startToolStripMenuItem_Click(object sender, EventArgs e)
{
  startListen = new Thread(listen);
  startListen.Start();
}

private void listen()
{
  listener = new Listener();
  listener.BeginListen(101);
  listener.receivedImage += new Listener.ReceivedImageEventHandler(serverReceivedImage);

  startToolStripMenuItem.Enabled = false;
}

监听器

class Listener
{

    private Socket s;
    public List<Client> clients;

    public delegate void ReceivedImageEventHandler(Client client, byte[] image);
    public event ReceivedImageEventHandler receivedImage;

    private bool listening = false;

    public Listener()
    {
        clients = new List<Client>();
        s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    }

    public bool Running
    {
        get { return listening; }
    }

    public void BeginListen(int port)
    {
        s.Bind(new IPEndPoint(IPAddress.Any, port));
        s.Listen(100);
        s.BeginAccept(new AsyncCallback(AcceptCallback), s);
        listening = true;
    }

    public void StopListen()
    {
        if (listening == true)
        {
            s.Close();
            listening = false;
        }
    }

    void AcceptCallback(IAsyncResult ar)
    {
        Socket handler = (Socket)ar.AsyncState;
        Socket sock = handler.EndAccept(ar);
        Client client = new Client(sock);
        clients.Add(client);

        sock.BeginReceive(client.buffer, 0, client.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), client);

        client.Send("REQUEST_PRINT" + Environment.NewLine); 

        handler.BeginAccept(new AsyncCallback(AcceptCallback), handler);
    }

    void ReadCallback(IAsyncResult ar)
    {

        Client client = (Client)ar.AsyncState;
        try
        {
            int rec = client.sock.EndReceive(ar);
            if (rec != 0)
            {
                string data = Encoding.UTF8.GetString(client.buffer, 0, rec);

                if (data.Contains("SCREEN"))
                { 
                    byte[] bytes = Encoding.UTF8.GetBytes(data);
                    receivedImage(client, bytes);
                }
                else // not is a image, is a text
                {
                    // prepare text to show in TextBox
                }
            }
            else
            {
                Disconnected(client);
                return;
            }

            client.sock.BeginReceive(client.buffer, 0, client.buffer.Length, SocketFlags.None, new AsyncCallback(ReadCallback), client);
        }
        catch
        {
            Disconnected(client);
            client.sock.Close();
            clients.Remove(client);
        }
    }

}

客户端

class Client
{
    public Socket sock;
    public byte[] buffer = new byte[8192];

    public Client(Socket sock)
    {
        this.sock = sock;
    }

    public void Send(string data)
    {
        byte[] buffer = Encoding.ASCII.GetBytes(data);
        sock.BeginSend(buffer, 0, buffer.Length, SocketFlags.None, new AsyncCallback((ar) =>
        {
            sock.EndSend(ar);
        }), buffer);
    }
}

安卓代码

private byte[] compress(byte[] data) {

    Deflater deflater = new Deflater();
    deflater.setInput(data);
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length);
    deflater.finish();
    byte[] buffer = new byte[1024];
    while (!deflater.finished()) {
        int count = deflater.deflate(buffer);
        outputStream.write(buffer, 0, count);
    }
    outputStream.close();
    byte[] output = outputStream.toByteArray();

    return output;
}

public static DataOutputStream dos;
public static byte[] array;

ByteArrayOutputStream bos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bos);
array = compress(bos.toByteArray());

//...

dos = new DataOutputStream(SocketBackgroundService.clientSocket.getOutputStream());
byte[] header = ("SCREEN").getBytes(StandardCharsets.UTF_8);
byte[] dataToSend = new byte[header.length + array.length];
System.arraycopy(header, 0, dataToSend, 0, header.length);
System.arraycopy(array, 0, dataToSend, header.length, array.length);
dos.writeInt(dataToSend.length);
dos.write(dataToSend, 0, dataToSend.length);

dos.flush();

版本

我总是在这一行收到错误无效参数

var bitmap = new Bitmap(msInner);

这里使用压缩也是一样

z.CopyTo(msInner);

IvalidDataException

分别在 ServerReceivedImage() 方法上。

使用这个

File.WriteAllBytes(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "image.png"), newImage);

我注意到只收到 15KB(不使用压缩的文件大小)。

最佳答案

我正在写评论,但它没有给我足够的空间来表达我对您的代码的不满。

我的主要观点是

  • 您尝试重新压缩并完美压缩图像。 PNG 是可移植网络图形。它专为网络传输而设计。如果可以接受,您应该使用 jpeg 之类的东西。

  • 您只需使用 UTF8.GetString 解码接收到的缓冲区并搜索文本,然后重新编码该字符串并尝试解压缩并从中读取图像,从索引 6 开始,考虑到您添加的索引 6 毫无意义流开头的两个字节大小的字段,您真的不知道“屏幕”的位置。

  • 您不检查是否收到了所有流数据。

  • 所有代码看起来都像是您已经搜索了 SO 问题和答案并创建了一个副本。

现在是我的建议。

  • 从网络传输数据时,不要试图发明轮子。尝试类似 gRPC 的东西,它同时具有 android java 和 c# 包。

  • 如果您将使用原始数据,请知道您的字节数。

  • 我假设您将通过添加新的命令对来扩展您的代码。由于您没有某种信号系统的神奇标记,因此您很难区分数据和 header 。对于一个简单的实现,将某种神奇的数据添加到您的标题并搜索该数据,然后读取标题然后读取数据。您可能需要一次又一次地从套接字读取数据,直到收到所有数据。

    424A72 0600 53435245454E 008E0005   .....  724A42
     B J r    6  S C R E E N    36352   .....  rJB
    

此示例数据显示我们通过查看“BJr”获得了一个有效流。然后读取一个 2 字节无符号整数以读取命令大小,对于 SCREEN 为 6。读取命令,然后读取命令数据的四个字节无符号长度。对于我们的示例,它是 36352。为了安全起见,我添加了命令结束标记“rJB”。

要加分尝试减少内存分配/副本,您可以查看 System.Span<T>

关于c# - 如何在异步套接字中接收完整的屏幕截图?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56742014/

相关文章:

sockets - 是 Socket.BeginReceive(IList<ArraySegment<byte>> 缓冲区.. 不是异步的吗?

c# - 在 C# 中重用异常处理逻辑的最佳方式是什么?

zeromq - ZeroMQ模式,用于基于空闲状态在工作人员之间进行负载均衡工作

c# - 将查询结果写入文件

C# 套接字检查连接

c# - 我只想让我的客户向我的服务器打个招呼,但不能

c# - 两次 Socket.BeginReceive 调用之间的 TCP 数据包会发生什么情况?

c# - 在异步 HttpWebResponse 读取期间捕获未处理的 SocketException

c# - 在不影响 ComboBox 的情况下为 TextBlock 应用默认样式

c# - 从 Finalizer 调用托管资源安全吗? (如果我检查为空)