【问题标题】:Proper sending and receiving via C# sockets?通过 C# 套接字正确发送和接收?
【发布时间】:2014-07-31 01:07:09
【问题描述】:

我正在尝试使用 C# 创建远程桌面服务器和客户端。服务器捕获屏幕,然后通过套接字将其发送给客户端。我正在使用下面的代码,尽管它只在客户端显示 jpeg 图像的一部分。我认为这是因为图像是在多个数据包中发送的,而目前代码只读取一个数据包并显示它。谁能解释我将如何更改我的代码,以便它在显示之前接收多个数据包(整个图像)。

服务器代码:

Socket serverSocket;
Socket clientSocket;

public Form1()
{
    InitializeComponent();

    backgroundWorker1.RunWorkerAsync();
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        serverSocket = new Socket(AddressFamily.InterNetwork,
                                  SocketType.Stream,
                                  ProtocolType.Tcp);

        IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Any, 8221);

        serverSocket.Bind(ipEndPoint);
        serverSocket.Listen(4);

        //Accept the incoming clients
        serverSocket.BeginAccept(new AsyncCallback(OnAccept), null);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Stream Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

private void timer1_Tick(object sender, EventArgs e)
{
    timer1.Stop();

    Rectangle bounds = new Rectangle(0, 0, 1280, 720);
    Bitmap bitmap = new Bitmap(bounds.Width, bounds.Height);

    using (Graphics g = Graphics.FromImage(bitmap))
    {
        g.CopyFromScreen(Point.Empty, Point.Empty, bounds.Size);
    }

    System.IO.MemoryStream stream = new System.IO.MemoryStream();

    ImageCodecInfo myImageCodecInfo;
    System.Drawing.Imaging.Encoder myEncoder;
    EncoderParameter myEncoderParameter;
    EncoderParameters myEncoderParameters;

    myEncoderParameters = new EncoderParameters(1);

    myImageCodecInfo = GetEncoderInfo("image/jpeg");
    myEncoder = System.Drawing.Imaging.Encoder.Quality;
    myEncoderParameter = new EncoderParameter(myEncoder, 40L);
    myEncoderParameters.Param[0] = myEncoderParameter;

    bitmap.Save(stream, myImageCodecInfo, myEncoderParameters);

    byte[] imageBytes = stream.ToArray();

    stream.Dispose();

    clientSocket.Send(imageBytes);

    timer1.Start();
}

如您所见,我正在使用一个将间隔设置为 30 的计时器来发送图像字节。

客户端代码:

public Socket clientSocket;

byte[] byteData = new byte[2048];
MemoryStream ms;

public Form1()
{
    InitializeComponent();

    backgroundWorker1.RunWorkerAsync();

    this.DoubleBuffered = true;
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
    try
    {
        clientSocket = new Socket(AddressFamily.InterNetwork,
                       SocketType.Stream, ProtocolType.Tcp);

        IPEndPoint ipEndPoint = new IPEndPoint(IPAddress.Parse("MY EXTERNAL IP HERE"), 8221);

        //Connect to the server
        clientSocket.BeginConnect(ipEndPoint,
            new AsyncCallback(OnConnect), null);

    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "SGSclient",
                        MessageBoxButtons.OK,
                        MessageBoxIcon.Error);
    }
}

private void OnConnect(IAsyncResult ar)
{
    try
    {
        //Start listening to the data asynchronously
        clientSocket.BeginReceive(byteData,
                                   0,
                                   byteData.Length,
                                   SocketFlags.None,
                                   new AsyncCallback(OnReceive),
                                   null);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message, "Stream Error",
            MessageBoxButtons.OK, MessageBoxIcon.Error);
    }
}

private void OnReceive(IAsyncResult ar)
{
    try
    {
        int byteCount = clientSocket.EndReceive(ar);

        ms = new MemoryStream(byteData);

        using (BinaryReader br = new BinaryReader(ms))
        {
            this.BackgroundImage = Image.FromStream(ms).GetThumbnailImage(this.ClientRectangle.Width, this.ClientRectangle.Height, null, IntPtr.Zero);
        }

    }
    catch (ArgumentException e)
    {
        //MessageBox.Show(e.Message);
    }

    clientSocket.BeginReceive(byteData, 0, byteData.Length, SocketFlags.None, new AsyncCallback(OnReceive), null);
}

客户端用于接收图像,然后将其显示在表单的背景上。

【问题讨论】:

  • 您必须在图像的末尾设置一个分隔符,一些字节集将sinalize 图像已结束的服务器。它也称为文件结束 (EOF)。 TCP 不会将图像拆分为应用程序的逻辑数据包,因此您必须编写自己的信息控制。

标签: c# image sockets


【解决方案1】:

您需要将应用程序级协议添加到您的套接字通信中。

为所有发送的消息添加标题。标头包含随后的字节数。它是更简单的代码,并且具有字节计数比混合终止序列更好。

客户端然后进行两组读取: 1) 读取任何标头中已知的字节数。

2) 从标头中提取字节数后,循环读取,直到获得指示的字节数。

所有编写套接字通信的人的必读文章:http://nitoprograms.blogspot.com/2009/04/message-framing.html 来自那篇文章:重复这个口头禅三遍:“TCP 不对数据包进行操作。TCP 对数据流进行操作。”

【讨论】:

    【解决方案2】:

    我之前回答了一个类似的问题,并提供了一个完整的示例,我认为它完全符合您的要求。见:transferring a screenshot over a TCP connection

    【讨论】:

      【解决方案3】:

      您必须在图像的末尾设置一个分隔符,一些字节集将 sinalize 图像已结束的服务器。它也称为文件结束 (EOF) 或消息结束。 TCP 不会将图像拆分为应用程序的逻辑数据包,因此您必须编写自己的信息控制。

      逻辑类似于: 客户

      byte[] EndOfMessage = System.Text.Encoding.ASCII.GetBytes("image_end");
      byte[] ImageBytes = GetImageBytes();
      byte[] BytesToSend = new byte[EndOfMessage.Length + ImageBytes.Length];
      Array.Copy(ImageBytes, 0, BytesToSend);
      Array.Copy(EndOfMessage, 0, BytesToSend, ImageBytes.Length, EndOfMessage.Length);
      
      SendToServer(BytesToSend);
      

      服务器

      byte[] EndOfMessage = System.Text.Encoding.ASCII.GetBytes("image_end");
      byte[] ReceivedBytes;
      
      while(!IsEndOfMessage(ReceivedBytes, EndOfMessage ))
      {
      //continue reading from socket and adding to ReceivedBytes
      }
      
      ReceivedBytes = RemoveEndOfMessage(ReceivedBytes, EndOfMessage );
      PrintImage(ReceivedBytes);
      

      我现在正在工作,无法提供完整的运行示例,很抱歉。

      问候


      支持方式:

      private bool IsEndOfMessage(byte[] MessageToCheck, byte[] EndOfMessage)
      {
          for(int i = 0; i++; i < EndOfMessage.Length)
          {
              if(MessageToCheck[MessageToCheck.Length - (EndOfMessage.Length + i)] != EndOfMessage[i])
                  return false;
          }
      
          return true;
      }
      
      private byte[] RemoveEndOfMessage(byte[] MessageToClear, byte[] EndOfMessage)
      {
          byte[] Return = new byte[MessageToClear.Length - EndOfMessage.Length];
          Array.Copy(MessageToClear, Return, Return.Length);
      
          return Return;
      }
      

      同样,我无法测试它们,所以你可能会发现一些错误。

      【讨论】:

      • 最好使用具有字节数的标头而不是终止字符串。
      • 这是另一种解决方案。无论如何,有必要约定标题的结尾(和消息的开头)。所以,我不会说它更好。这只是另一种方式。
      • 否,对于二进制数据字节计数标头更好。消息中的前 4 个字节是消息长度的约定很简单。使用终止字符串需要昂贵的处理来检测接收到的字节中的字符串。另一个问题是当传输的字节具有与终止字符串相同的模式时。对于另一个问题,发送的数据包可能会与下一条消息合并,并且您的方案不会将接收到的数据分成两个单独的消息。
      • (1) 您依赖于约定。 (2) OP 正在发送图像,不可能与 EOM 字符串发生冲突。 (3) “下一条消息”是什么意思? TCP 没有 message 概念,我们只是连接字节。 (4) 请注意您的回答,OP 将决定哪个服务更好。问候。
      • 通过套接字发送的数据可以包含来自多个发送调用的数据。网络对数据的处理是网络的业务。您可以使用一个图像缓冲区进行发送,然后稍后再使用另一个图像缓冲区进行另一次发送。两个缓冲区可能在同一个数据包中到达,而 Receive 调用在一个数据包中获取所有数据——来自两个不同 Send 调用的数据。
      猜你喜欢
      • 2021-06-13
      • 2010-10-19
      • 1970-01-01
      • 2012-02-12
      • 2013-09-11
      • 1970-01-01
      • 2013-04-01
      • 1970-01-01
      • 2016-12-08
      相关资源
      最近更新 更多