通过C#套接字正确发送和接收?

问题描述:

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

Server代码:

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); 
} 

客户机是指为接收所述图像,然后在表格的背景显示它。

+0

您必须在图像末尾设置一个分隔符,这些分隔符会影响图像结束的服务器。它也被称为**文件结尾**(EOF)。 TCP不会将图像分割为适用于您的应用程序的逻辑数据包,因此您必须编写自己的信息控制。 – 2012-08-03 18:57:37

您需要将应用层协议添加到套接字通信中。

为所有发送的消息添加标题。标题包含后面的字节数。这是更简单的代码,并有更好的字节计数比kludge终止序列。

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

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

所有正在编写套接字通信的人都必读的文章:http://nitoprograms.blogspot.com/2009/04/message-framing.html 从这篇文章:重复这个咒语三次:“TCP不对数据包进行操作,TCP对数据流进行操作。

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

逻辑将类似于此: CLIENT

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; 
} 

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

+0

最好使用具有字节计数而非终止字符串的头部。 – 2012-08-03 20:12:50

+0

这是另一种解决方案。无论如何,有必要规定标题的结尾(以及消息的开头)。所以,我不会说它更好*。这只是另一种方式。 – 2012-08-03 20:18:56

+1

不,对于二进制数据字节计数头部更好。消息中前4个字节是消息长度的约定很简单。使用终止字符串需要昂贵的处理来检测接收字节中的字符串。另一个问题是何时传输的字节与终止字符串具有相同的模式。对于更多问题,发送的数据包可能会与下一条消息合并,并且您的方案不会将接收到的数据分隔为两条单独的消息。 – 2012-08-03 20:52:16

我以前回答过类似的问题,并提供了一个完整的工作示例,我认为这正是您正在尝试执行的操作。请参阅:transferring a screenshot over a TCP connection