2013-10-07 82 views
2

首先C#的問題,我只想讓你知道,我是不是新來編程,它應該是更容易幫助我:)與多線程及插座

我有我多問題我在C#中使用Socket進行線程聊天。

我有3個主題:

  • 無效ListenSocketConnection:檢查插口,可以連接。連接套接字添加到列表中<>
  • void CheckIfClientStillConnectedThread:檢查Socket是否斷開連接。檢查一個Socket接收數據
    • 這裏是問題:斷開插座從列表<>
    • 無效ReceiveDataListener刪除。如果第一個或第二個線程從列表<>中刪除套接字,則'foreach(clientsManager中的ClientManager)'將引發異常。
    • 這是第二個問題。如果這的foreach「的foreach ClientManager cManager在clientsList)」中的插座斷開將引發異常:DisposedException

你對我怎麼能解決這個問題的任何提示?

這裏是我的代碼:

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 
using System.Net; 
using System.Net.Sockets; 
using System.ComponentModel; 
using System.Threading; 

namespace JAChat.Library 
{ 
    class SocketServer 
    { 
     private Socket socketServer; 
     private BackgroundWorker bwSocketConnectListener; 
     private BackgroundWorker bwCheckIfConnected; 
     private BackgroundWorker bwReceiveDataListener; 
     private List<ClientManager> clientsList; 

     #region Constructor 
     public SocketServer(int port) 
     { 
      clientsList = new List<ClientManager>(); 

      socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
      socketServer.Bind(new IPEndPoint(IPAddress.Any, port)); 
      socketServer.Listen(100); 

      bwSocketConnectListener = new BackgroundWorker(); 
      bwSocketConnectListener.DoWork += new DoWorkEventHandler(ListenSocketConnection); 
      bwSocketConnectListener.RunWorkerAsync(); 

      bwCheckIfConnected = new BackgroundWorker(); 
      bwCheckIfConnected.DoWork += CheckIfClientStillConnectedThread; 
      bwCheckIfConnected.RunWorkerAsync(); 

      bwReceiveDataListener = new BackgroundWorker(); 
      bwReceiveDataListener.DoWork += ReceiveDataListener; 
      bwReceiveDataListener.RunWorkerAsync(); 
     } 
     #endregion 

     #region Getter 
     public List<ClientManager> connectedClients 
     { 
      get 
      { 
       return clientsList; 
      } 
     } 
     #endregion 

     #region Public Methods 
     /// <summary> 
     /// Parse and send the command object to targets 
     /// </summary> 
     public void sendCommand(Command cmd) 
     { 
      BackgroundWorker test = new BackgroundWorker(); 
      test.DoWork += delegate { 
       foreach(ClientManager cManager in clientsList){ 
        cManager.sendCommand(cmd); 
       } 
      }; 
      test.RunWorkerAsync(); 
     } 

     /// <summary> 
     /// Disconnect and close the socket 
     /// </summary> 
     public void Disconnect() 
     { 
      socketServer.Disconnect(false); 
      socketServer.Close(); 
      socketServer = null; //Stop some background worker 
     } 
     #endregion 

     #region Private Methods 
     private void ListenSocketConnection(object sender, DoWorkEventArgs e) 
     { 
      while (socketServer != null) 
      { 
       //Get and WAIT for new connection 
       ClientManager newClientManager = new ClientManager(socketServer.Accept()); 
       clientsList.Add(newClientManager); 
       onClientConnect.Invoke(newClientManager); 
      } 
     } 

     private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e){ 
      while(socketServer != null){ 
       for(int i=0;i<clientsList.Count;i++){ 
        if(clientsList[i].socket.Poll(10,SelectMode.SelectRead) && clientsList[i].socket.Available==0){ 
         clientsList[i].socket.Close(); 
         onClientDisconnect.Invoke(clientsList[i]); 
         clientsList.Remove(clientsList[i]); 
         i--;       
        } 
       } 
       Thread.Sleep(5); 
      } 
     } 

     private void ReceiveDataListener(object unused1, DoWorkEventArgs unused2){ 
      while (socketServer != null){ 
       foreach (ClientManager cManager in clientsList) 
       { 
        try 
        { 
         if (cManager.socket.Available > 0) 
         { 
          Console.WriteLine("Receive Data Listener 0"); 
          //Read the command's Type. 
          byte[] buffer = new byte[4]; 
          int readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None); 
          Console.WriteLine("Receive Data Listener 1"); 
          if (readBytes == 0) 
           break; 
          Console.WriteLine("Receive Data Listener 2"); 
          CommandType cmdType = (CommandType)(BitConverter.ToInt32(buffer, 0)); 
          Console.WriteLine("Receive Data Listener 3"); 

          //Read the sender IP size. 
          buffer = new byte[4]; 
          readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          int senderIPSize = BitConverter.ToInt32(buffer, 0); 

          //Read the sender IP. 
          buffer = new byte[senderIPSize]; 
          readBytes = cManager.socket.Receive(buffer, 0, senderIPSize, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          IPAddress cmdSenderIP = IPAddress.Parse(System.Text.Encoding.ASCII.GetString(buffer)); 

          //Read the sender name size. 
          buffer = new byte[4]; 
          readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          int senderNameSize = BitConverter.ToInt32(buffer, 0); 

          //Read the sender name. 
          buffer = new byte[senderNameSize]; 
          readBytes = cManager.socket.Receive(buffer, 0, senderNameSize, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          string cmdSenderName = System.Text.Encoding.Unicode.GetString(buffer); 

          //Read target IP size. 
          string cmdTarget = ""; 
          buffer = new byte[4]; 
          readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          int targetIPSize = BitConverter.ToInt32(buffer, 0); 

          //Read the command's target. 
          buffer = new byte[targetIPSize]; 
          readBytes = cManager.socket.Receive(buffer, 0, targetIPSize, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          cmdTarget = System.Text.Encoding.ASCII.GetString(buffer); 

          //Read the command's MetaData size. 
          string cmdMetaData = ""; 
          buffer = new byte[4]; 
          readBytes = cManager.socket.Receive(buffer, 0, 4, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          int metaDataSize = BitConverter.ToInt32(buffer, 0); 

          //Read the command's Meta data. 
          buffer = new byte[metaDataSize]; 
          readBytes = cManager.socket.Receive(buffer, 0, metaDataSize, SocketFlags.None); 
          if (readBytes == 0) 
           break; 
          cmdMetaData = System.Text.Encoding.Unicode.GetString(buffer); 

          //Create the command object 
          Command cmd = new Command(cmdType, cmdSenderIP, cmdSenderName, IPAddress.Parse(cmdTarget), cmdMetaData); 
          this.onCommandReceived(cmd); 
         } 
        } 
        catch (ObjectDisposedException) {/*Le socket s'est déconnectée pendant le for each. Ignore l'érreur et retourne dans le while*/ } 
        catch (InvalidOperationException) { /* clientsList a été modifié pendant le foreach et délanche une exception. Retour while*/} 
       }     
      } 
      Console.WriteLine("Receive data listener closed"); 
     } 
     #endregion 

     #region Events 
     public delegate void OnClientConnectEventHandler(ClientManager client); 
     /// <summary> 
     /// Events invoked when a client connect to the server 
     /// </summary> 
     public event OnClientConnectEventHandler onClientConnect = delegate { }; 

     public delegate void OnClientDisconnectEventHandler(ClientManager client); 
     /// <summary> 
     /// Events invoked when a client disconnect from the server 
     /// </summary> 
     public event OnClientDisconnectEventHandler onClientDisconnect = delegate { }; 

     public delegate void OnCommandReceivedEventHandler(Command cmd); 
     /// <summary> 
     /// Events invoked when a command has been sent to the server 
     /// </summary> 
     public event OnCommandReceivedEventHandler onCommandReceived = delegate { }; 
     #endregion 
    } 
} 
+2

*檢查是否收到套接字的那個經常會引發異常。爲什麼?*什麼例外? –

+0

任何不使用「異步」套接字的原因? –

+0

@usr我想你錯過了閱讀第一行。 OP明確指出*我不是新手編程* –

回答

0

收集是由多個線程修改,因此計數很可能每一次,詢問。因此,您應該將其設置爲固定的金額;即在循環之前,然後遍歷列表。另外,因爲您正在刪除元素,所以倒計數而不是倒數是更好的選擇。

考慮下面的代碼:

private void CheckIfClientStillConnectedThread(object sender, DoWorkEventArgs e) 
{ 
    while (socketServer != null) 
    { 
     int count = clientsList.Count -1; 
     for (int i=count; i >= 0 ; i--) 
     { 
      if (clientsList[i].socket.Poll(10, SelectMode.SelectRead) && clientsList[i].socket.Available == 0) 
      { 
       clientsList[i].socket.Close(); 
       onClientDisconnect.Invoke(clientsList[i]); 
       clientsList.Remove(clientsList[i]); 
      } 
     } 
     Thread.Sleep(5); 
    } 
} 
+0

哈哈感謝它是真的,如果某些東西被添加或從列表中刪除,可以避免一些例外。感謝您的提示! – Jamesst20

4
  1. 有多個線程,但我沒有看到任何同步。這是不正確的。使用鎖來保護可變的共享狀態。
  2. 而不是所有的套接字,輪詢和DataAvailable檢查的中央管理,爲什麼你不使用每個套接字或異步IO任一個線程?這樣你一次只能管理一個套接字。沒有必要的投票。您只需撥打Read(或其異步版本)並等待數據到達。這是一個更好的處理套接字的範例。基本上,如果你的套接字代碼包含DataAvailable或者輪詢,你就會違背最佳實踐(並且可能在某處出現bug)。想想你如何解決這個問題,而不使用這兩個。這是可能的,更好的。
  3. ReceiveDataListener你假設,如果數據可用,整個消息可用。這是錯誤的,因爲TCP是面向流的。您可以以任意小的塊接收發送的數據。用我的觀點(2)來解決這個問題。

詳細說明(2):這基本上是一個演員模型。每個插座一個演員。無論您是使用線程實現演員,使用async/await還是使用傳統異步IO都無關緊要。

希望這會有所幫助。隨意在下面的評論中提出後續問題。

+0

感謝您的回答,我習慣於使用BackgroundWorket或'new Thread()'進行線程處理。但是您看起來是在討論某些不同的東西,而我不確定要理解。另外,不是每個套接字創建一個線程都是錯的嗎?我的意思是太多線程不是很好?你可能有一個很好的例子可以說明你想告訴我什麼?謝謝:) – Jamesst20

+0

如果插座很少(如數十個),每個插座一個線程/任務/背景工作幾乎總是可以的。如果你有更多,使用異步IO,而不是輪詢。關於異步IO如何在C#4上工作,這裏有一個很好看的例子:http://msdn.microsoft.com/en-us/library/fx6588te.aspx。在C#5上,你會使用'async/await',這使得這幾乎是微不足道的。 – usr