2016-11-21 103 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方法中创建新的。

相关问题