2016-11-21 65 views
0

我有我的代碼在這裏,我不明白這是什麼bug。 有人告訴我,問題是在「處理客戶端」方法中反序列化'Net'對象時。因爲每次有新客戶進來時我都會覆蓋它。你能幫我解決這個問題嗎?我仍然無法弄清楚我該怎麼做。如何解決這個序列化異常?

起初它適用於2-3消息,然後崩潰。

我得到的例外是:

序列化異常 - 輸入流不是有效的二進制格式。起始內容(以字節爲單位)爲:FF-FF-FF-FF-06-44-61-76-69-64-3A-20-66-75-63-6B-20 ...。

還有一次,我得到了相同的序列化異常只有它說 - 在最上面的對象。

代碼:

using System; 
using System.Collections.Generic; 
using System.ComponentModel; 
using System.Data; 
using System.Drawing; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Windows.Forms; 
using System.Threading; 
using System.Runtime.Serialization.Formatters.Binary; 
using System.Net.Sockets; 
using System.Net; 
using Message; 

namespace Chat 
{ 
    public partial class ChatWindow : Form 
    { 
    uMessage umsg = new uMessage(); 
    BinaryFormatter bf = new BinaryFormatter(); 
    NetworkStream Net; 
    TcpListener listener; 
    TcpClient client; 
    List<NetworkStream> Clients = new List<NetworkStream>(); 

    public ChatWindow(string ip, int port) 
    { 
     InitializeComponent(); 
     umsg.IP = ip; 
     umsg.Port = port; 
    } 

    public void OpenNewThread() 
    { 
     listener = new TcpListener(IPAddress.Parse(umsg.IP), umsg.Port); 
     listener.Start(); 
     Thread a = new Thread(Listen); 
     a.Start(); 
    } 

    public void Listen() 
    { 
     do 
     { 
      client = listener.AcceptTcpClient(); 
      Net = client.GetStream(); 
      Clients.Add(Net); 
      umsg.Name = bf.Deserialize(Net).ToString(); 
      lstboxCurrentUsers.Invoke(new Action(() => 
      { 
       lstboxCurrentUsers.Items.Add($"{umsg.Name} connected at " + DateTime.Now); 
       listboxHistory.Items.Add($"{umsg.Name} connected at " + DateTime.Now); 
      })); 
      LetSubsKnow(Clients); 


       Thread b = new Thread(() => HandleClient(Clients)); 
       b.Start(); 


     } while (true); 
    } 

    public void HandleClient(List<NetworkStream> ClientsStream) 
    { 
     while (true) 
     { 
      umsg.Message = bf.Deserialize(Net).ToString(); 

      foreach (var client in ClientsStream) 
       { 
        bf.Serialize(client, umsg.Message); 
       } 
     } 
    } 

    public void LetSubsKnow(List<NetworkStream> clientsStream) 
    {   
     foreach (var client in clientsStream) 
     { 
      bf.Serialize(client, $"{umsg.Name} Has Connected."); 
     } 
    } 
+1

確保您的代碼是線程安全的。 –

+1

我不會使用'BinaryFormatter'來序列化字符串,它會增加額外的負載,很難調試,並且與非.NET客戶端不兼容。您可能需要使用'Encoding.UTF8'。 –

回答

1

Net領域不斷得到由最近已經連接的客戶更換,所以即使你有每個客戶端一個HandleClient線程,從最近獲得NetworkStream閱讀所有這些線程。

同樣,每當客戶端連接時,umsg.Name字段將被覆蓋,而當消息到達時,umsg.Message字段將被覆蓋。

可以通過提供單個連接的NetworkStreamHandleClient,而對於收到的消息創建一個局部變量解決這些問題:

public void HandleClient(NetworkStream client) 
{ 
    ... 
    string message = bf.Deserialize(client).ToString(); 
    ... 
} 

同樣,你將需要通過客戶端的名義給LetSubsKnow方法,而不是依靠不斷更新的umsg字段。

public void LetSubsKnow(string clientName) 
{ 
    .... 
} 

而且即使你clientsStream各地作爲參數,它們都是相同的參考Clients領域,如果一個客戶端連接你發送數據時,foreach將拋出一個「集合已被修改」的例外。

這可以通過使用鎖訪問Clients字段來解決。我不會把整個foreach塊鎖定部內部,而是採取當前連接的客戶端的快照而不是:

private readonly object clientsLock = new object(); 
List<NetworkStream> Clients = new List<NetworkStream>(); 
... 
NetworkStream[] currentClients; 
lock(clientsLock) 
{ 
    currentClients = Clients.ToArray(); 
} 

foreach (NetworkStream client in currentClients) 
{ 
    // send stuff 
} 

注意,你會需要一些例外圍繞NetworkStream訪問代碼處理。

最後但並非最不重要我不認爲BinaryFormatter是線程安全的(請參閱this answer)。而不是鎖定你可能會更好的在HandleClientLetSubsKnow方法中創建新的。

相關問題