Асинхронные сокеты C #: это потокобезопасно и правильно сделано? - PullRequest
1 голос
/ 16 мая 2011

Мне нужно реализовать клиентское приложение TCP.Клиент и сервер отправляют сообщения друг другу.Я хочу сделать эту программу достаточно масштабируемой, чтобы обрабатывать подключения к нескольким серверам одновременно.Кажется, что асинхронные сокеты - путь для этого.Я новичок в C #, поэтому я уверен, что не знаю, что я здесь делаю.Я написал несколько классов и простую консольную программу для начала.В конце концов, я хочу создать приложение Windows Forms, но сначала я хочу начать с малого и просто.Класс Client работает в своем собственном потоке.Это все потокобезопасно и правильно сделано?Это много кода, и я попытался вырезать жир

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;

    namespace FastEyeClient
    {
        public class Server
        {
            private ServerManager m_Manager;
            private string m_Hostname;
            private bool m_IsLive;

            private class StateObject
            {
                public Socket AsyncSocket = null;
                public const int BufferSize = 1024;
                public byte[] Buffer = new byte[BufferSize];
                public StringBuilder Builder = new StringBuilder();
            }

            public Server(ServerManager manager, Socket socket)
            {
                try
                {
                    m_Manager = manager;

                    IPEndPoint endPoint = (IPEndPoint)socket.RemoteEndPoint;
                    IPAddress ipAddress = endPoint.Address;
                    IPHostEntry hostEntry = Dns.GetHostEntry(ipAddress);
                    Hostname = hostEntry.HostName;

                    IsLive = false;

                    StateObject state = new StateObject();
                    state.AsyncSocket = socket;

                    socket.BeginReceive(state.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
                }
                catch (Exception)
                {
                }
            }

            public string Hostname
            {
                get
                {
                    return m_Hostname;
                }
                set
                {
                    m_Hostname = value;
                }
            }

            public bool IsLive
            {
                get
                {
                    return m_IsLive;
                }
                set
                {
                    m_IsLive = value;
                }
            }

            private void ReceiveCallback(IAsyncResult result)
            {
                try
                {
                    StateObject state = (StateObject)result.AsyncState;
                    Socket socket = state.AsyncSocket;

                    int read = socket.EndReceive(result);

                    if (read > 0)
                    {
                        state.Builder.Append(Encoding.ASCII.GetString(state.Buffer, 0, read));

                        if (state.Builder.Length > 1)
                        {
                            string messages = state.Builder.ToString();

                            ParseMessages(messages);
                        }
                    }

                    StateObject newState = new StateObject();
                    newState.AsyncSocket = socket;

                    socket.BeginReceive(newState.Buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), newState);
                }
                catch (Exception)
                {
                }
            }

            private void ParseMessages(string messages)
            {
                string[] messagesArray = messages.Split('\n');

                foreach (string message in messagesArray)
                {
                    string[] tokens = message.Split(',');

                    if (tokens[0].Contains("@"))
                    {
                        ParseServerMessage(tokens);
                    }
                }
            }

            private void ParseServerMessage(string[] tokens)
            {
                tokens[0].Remove(0, 1);

                if (tokens[0] == "4")
                {
                    bool status;

                    if (tokens[1] == "0")
                    {
                        status = false;
                        m_Manager.SetLiveStatus(m_Hostname, status);
                    }
                    else if (tokens[1] == "1")
                    {
                        status = true;
                        m_Manager.SetLiveStatus(m_Hostname, status);
                    }
                }
            }
        }
    }

ServerManager.cs

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Net;
    using System.Net.Sockets;

    namespace FastEyeClient
    {
        public class ServerManager
        {
            private Client m_Client;

            private Dictionary<string, Server> m_Servers;
            private object m_Locker;

            public ServerManager(Client client)
            {
                m_Client = client;

                m_Servers = new Dictionary<string, Server>();
                m_Locker = new object();
            }

            public void AddServer(string hostname, int port)
            {
                try
                {
                    IPAddress[] IPs = Dns.GetHostAddresses(hostname);

                    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                    socket.BeginConnect(IPs, port, new AsyncCallback(ConnectCallback), socket);
                }
                catch (Exception)
                {
                    bool isConnected = false;
                    string message = "Could not connect to server.";

                    m_Client.OnConnect(isConnected, message);
                }
            }

            private void ConnectCallback(IAsyncResult ar)
            {
                bool isConnected;
                string message;

                try
                {
                    Socket socket = (Socket)ar.AsyncState;

                    socket.EndConnect(ar);

                    IPEndPoint endPoint = (IPEndPoint)socket.RemoteEndPoint;
                    IPAddress ipAddress = endPoint.Address;
                    IPHostEntry hostEntry = Dns.GetHostEntry(ipAddress);
                    string hostname = hostEntry.HostName;

                    lock (m_Servers)
                    {
                        if (m_Servers.ContainsKey(hostname))
                        {
                            isConnected = false;
                            message = "Client is already connected to server";
                        }
                        else
                        {
                            m_Servers.Add(hostname, new Server(this, socket));

                            isConnected = true;
                            message = "Successfully connected.";
                        }
                    }

                    m_Client.OnConnect(isConnected, message);
                }
                catch (Exception)
                {
                    isConnected = false;
                    message = "Could not connect to server.";

                    m_Client.OnConnect(isConnected, message);
                }
            }

            public void SetLiveStatus(string hostname, bool newStatus)
            {
                string message;

                lock (m_Locker)
                {
                    if (m_Servers.ContainsKey(hostname))
                    {
                        if (m_Servers[hostname].IsLive == newStatus)
                        {
                            message = "Server is already set to this status.";
                        }
                        else
                        {
                            m_Servers[hostname].IsLive = newStatus;

                            message = "Successfully set new status.";
                        }
                    }
                    else
                    {
                        message = "Server not found.";
                    }
                }

                m_Client.OnSetLiveStatus(hostname, message);
            }
        }
    }

Ответы [ 3 ]

1 голос
/ 16 мая 2011
  1. Запускается ли он?
  2. Выдает ли исключение (я)?

    Ошибка при попытке запустить код сервера в нескольких потоках:

ИЗБЕГАЙТЕ попыток манипулировать, читать или записывать сокеты в разных потоках.Пусть один поток принимает соединения от сокета сервера и порождает поток для обработки транзакций.Если вы получаете слишком много потоков одновременно, у вас будет 1 поток, обрабатывающий несколько сокетов.

0 голосов
/ 16 мая 2011

Нет, это не потокобезопасно.

Подписчик может отказаться от подписки перед проверкой и вызовом:

if (ConnectEvent != null)
{
    ConnectEvent(this, new ConnectEventArgs(isConnected, message));
}

Определить событие как:

public event ConnectEventHandler ConnectEvent = delegate{};

и удалите событие, чтобы получить безопасность потока.


Я бы уменьшил цикл выполнения до:

private void Run()
{
    while (true)
    {
        m_WaitHandle.WaitOne();
        Event task = null;

        lock (m_Locker)
        {
            if (m_Tasks.Count == 0)
                            {
                m_WaitHandle.Reset();
                continue;
            }

            task = m_Tasks.Dequeue();
        }

        task.DoTask(m_Manager);
    }
}
  1. Цикл продолжит работать, пока событие не будетсброс.
  2. Убедитесь, что в очередь не вставлены нулевые элементы, вместо проверки на нулевое значение.
0 голосов
/ 16 мая 2011

Вы можете упростить шаблон производитель-потребитель в классе Client, используя BlockingCollection вместо комбинации AutoResetEvent и простого старого Queue.

Метод EnqueueTask будетвыглядит так:

public void EnqueueTask(Event task)
{
  m_Queue.Add(task);
}

Метод Run будет выглядеть следующим образом:

public void Run()
{
  while (true)
  {
    Event task = m_Queue.Take();
    if (task == null)
    {
      return;
    }
    task.DoTask();
  }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...