1

私は小さなマルチスレッドネットワークサーバーを書いています。すべての古典的なもの:着信接続をリッスンし、それらを受け入れてから、異なるスレッドでそれらを提供します。また、このサーバーは再起動する必要がある場合があります。そのためには、a)リスニングを停止し、b)接続されているすべてのクライアントをキックアウトし、c)設定/待機を調整し、d)リスニングを再開する必要があります。

ええと、私はマルチスレッドプログラムの開発についてほとんど知らないので、助けを求めています。これが私がやってきたことです(コアのもののみ):

class Server
{
    class MyClient
    {
        Server server;
        TcpClient client;
        bool hasToFinish = false;

        public MyClient(Server server, TcpClient client)
        {
            this.server = server;
            this.client = client;
        }

        public void Go()
        {
            while (!hasToFinish)
            {
                // do all cool stuff
            }
            CleanUp();
        }

        private void CleanUp()
        {
            // finish all stuff

            client.Close();
            server.myClients.Remove(this);
        }

        public void Finish()
        {
            hasToFinish = true;
        }
    }

    bool running = false;
    TcpListener listener;
    HashSet<MyClient> myClients = new HashSet<MyClient>();

    public void Start()
    {
        if (running)
            return;

        myClients.Clear();
        listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234);
        listener.Start();
        listener.BeginAcceptTcpClient(AcceptClient, this);
        running = true;
    }

    public void Stop()
    {
        if (!running)
            return;

        listener.Stop();
        foreach (MyClient client in myClients)
        {
            client.Finish();
        }
        myClients.Clear();
        running = false;
    }

    public void AcceptClient(IAsyncResult ar)
    {
        MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar));
        myClients.Add(client);
        client.Go();
    }
}

それは絶対に不十分です。同期はありません(どこに配置すればよいかわかりません!)。Server.Stop()を呼び出しても、MyClient-sはすぐに停止しません。これらの問題を解決するにはどうすればよいですか?

4

1 に答える 1

1

コードは非常にきれいに見えます。簡単な変更でスレッドセーフにすることができます。

問題には、「クライアント」、「サーバー」、およびクライアントとサーバーの相互作用の 3 つの部分があります。

まずクライアントから、Go() メソッドが 1 つのスレッド (A としましょう) によって呼び出され、Finish() メソッドが別のスレッド (B) によって呼び出されます。変数が CPU キャッシュにキャッシュされている可能性があるため、スレッド B が hasToFinish フィールドを変更しても、スレッド A は変更をすぐに認識しない場合があります。hasToFinish フィールドを「揮発性」にすることで修正できます。これにより、更新時にスレッド B が変数の変更をスレッド A に強制的に公開します。

今度はサーバークラスです。以下の例のように、「サーバー」インスタンスで 3 つのメソッドを同期することをお勧めします。Start と Stop が順番に呼び出され、変更された変数がスレッド間で公開されるようにします。

クライアントとサーバーの相互作用にも対処する必要があります。あなたのコードでは、クライアントはサーバーからその参照を削除しますが、サーバーはすべてのクライアント参照をFinish()時にクリアします。私には冗長に見えます。クライアントのコードの一部を削除できれば、何も心配する必要はありません。なんらかの理由でロジックをサーバーではなくクライアントに保持することを選択した場合は、Server クラスでパブリック メソッド呼び出し RemoveClient(Client client) を作成し、それを Server インスタンスと同期させます。次に、HashSet を直接操作する代わりに、クライアントがこのメソッドを呼び出せるようにします。

これで問題が解決することを願っています。

public void Start()
{
  lock(this) 
  {
    if (running)
        return;

    myClients.Clear();
    listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 1234);
    listener.Start();
    listener.BeginAcceptTcpClient(AcceptClient, this);
    running = true;
  }
}

public void Stop()
{
  lock(this)
  {
    if (!running)
        return;

    listener.Stop();
    foreach (MyClient client in myClients)
    {
        client.Finish();
    }
    myClients.Clear();
    running = false;
  }
}

public void AcceptClient(IAsyncResult ar)
{
  lock(this)
  {
    MyClient client = new MyClient(this, ((TcpListener)ar.AsyncState).EndAcceptTcpClient(ar));
    myClients.Add(client);
    client.Go();
  }
}
于 2012-09-04T21:18:24.950 に答える