2

まず第一に、私はプログラミングが初めてではないことをお知らせしたいと思います.

ソケットを使用して C# で作成しているマルチスレッド チャットに問題があります。

私は3つのスレッドを持っています:

  • void ListenSocketConnection : 接続できるソケットを確認します。接続されたソケットは List<> に追加されます
  • void CheckIfClientStillConnectedThread : ソケットが切断されているかどうかを確認します。切断されたソケットはリストから削除されます<>
  • void ReceiveDataListener : ソケットがデータを受信したかどうかを確認します
    • これが問題です。最初または 2 番目のスレッドが List<> からソケットを削除すると、「foreach (clientManager cManager in clientsList)」で例外が発生します。
    • これが2番目の問題です。その foreach 中にソケットが切断された場合、'foreach ClientManager cManager in 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
    }
}
4

2 に答える 2

4
  1. 複数のスレッドがありますが、同期は見られません。それは正しくありません。ロックを使用して、変更可能な共有状態を保護します。
  2. このすべてのソケットの集中管理、ポーリングとチェックを行う代わりに、DataAvailableソケットごとに 1 つのスレッドまたは非同期 IO を使用しないのはなぜですか? そうすれば、一度に 1 つのソケットのみを管理できます。ポーリングは必要ありません。(またはその非同期バージョン) を呼び出しReadて、データが到着するのを待つだけです。これは、ソケットを処理するためのはるかに優れたパラダイムです。基本的に、ソケット コードにDataAvailableポーリングが含まれている場合、ベスト プラクティスに反することになります (そして、どこかにバグがある可能性があります)。2 つを使用せずにこれをどのように解決するかを考えてください。それは可能であり、より良いです。
  3. ReceiveDataListenerデータが利用可能であれば、メッセージ全体が利用可能であると仮定します。TCP はストリーム指向であるため、これは誤りです。送信されたデータは、任意の小さなチャンクで受信できます。私のポイント(2)に行くと、これが修正されます。

(2) を詳しく説明すると、これは基本的にアクター モデルです。ソケットごとに 1 つのアクター。スレッドを使用してアクターを実装するかどうか、async/await従来の非同期 IO を使用するか使用するかは問題ではありません。

お役に立てれば。以下のコメントでフォローアップの質問をしてください。

于 2013-10-07T16:20:51.687 に答える