2012-08-03 196 views
0

我正在嘗試使用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

回答

4

您需要將應用層協議添加到套接字通信中。

爲所有發送的消息添加標題。標題包含後面的字節數。這是更簡單的代碼,並有更好的字節計數比kludge終止序列。

客戶端然後執行兩組讀取: 1)讀取已知位於任何標頭中的字節數。

2)從頭文件中提取字節數後,循環讀取,直到得到指示的字節數。

所有正在編寫套接字通信的人都必讀的文章:http://nitoprograms.blogspot.com/2009/04/message-framing.html 從這篇文章:重複這個咒語三次:「TCP不對數據包進行操作,TCP對數據流進行操作。

1

您必須在圖像末尾設置一個分隔符,這些分隔符將會影響圖像結束的服務器。它也被稱爲文件結尾(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