Асинхронные Сокеты


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

Программы.в CS

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;

    namespace FastEyeClient
    {
        class Program
        {
            static void Main(string[] args)
            {
                Client client = new Client();
                client.ConnectEvent += new ConnectEventHandler(OnConnect);
                client.SetLiveStatusEvent += new SetLiveStatusEventHandler(OnSetLiveStatus);

                client.Connect("hostname", 1987);

                Thread.Sleep(1000);

                client.SetLiveStatus("hostname", true);
            }

            private static void OnConnect(object sender, ConnectEventArgs e)
            {
                Console.WriteLine(e.Message);
            }

            private static void OnSetLiveStatus(object sender, SetLiveStatusEventArgs e)
            {
                Console.WriteLine(e.Message);
            }
        }
    }

Клиента.в CS

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

    namespace FastEyeClient
    {
        public delegate void ConnectEventHandler(object sender, ConnectEventArgs e);
        public delegate void SetLiveStatusEventHandler(object sender, SetLiveStatusEventArgs e);

        public class Client : IDisposable
        {
            public event ConnectEventHandler ConnectEvent;
            public event SetLiveStatusEventHandler SetLiveStatusEvent;

            ServerManager m_Manager;

            EventWaitHandle m_WaitHandle;
            readonly object m_Locker;
            Queue<Event> m_Tasks;
            Thread m_Thread;

            public Client()
            {
                m_Manager = new ServerManager(this);

                m_WaitHandle = new AutoResetEvent(false);
                m_Locker = new object();
                m_Tasks = new Queue<Event>();

                m_Thread = new Thread(Run);
                m_Thread.Start();
            }

            public void EnqueueTask(Event task)
            {
                lock (m_Locker)
                {
                    m_Tasks.Enqueue(task);
                }

                m_WaitHandle.Set();
            }

            public void Dispose()
            {
                EnqueueTask(null);
                m_Thread.Join();
                m_WaitHandle.Close();
            }

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

                    lock (m_Locker)
                    {
                        if (m_Tasks.Count > 0)
                        {
                            task = m_Tasks.Dequeue();

                            if (task == null)
                            {
                                return;
                            }
                        }
                    }

                    if (task != null)
                    {
                        task.DoTask(m_Manager);
                    }
                    else
                    {
                        m_WaitHandle.WaitOne();
                    }
                }
            }

            public void Connect(string hostname, int port)
            {
                EnqueueTask(new ConnectEvent(hostname, port));
            }

            public void SetLiveStatus(string hostname, bool status)
            {
                EnqueueTask(new SetLiveEvent(hostname, status));
            }

            public void OnConnect(bool isConnected, string message)
            {
                if (ConnectEvent != null)
                {
                    ConnectEvent(this, new ConnectEventArgs(isConnected, message));
                }
            }

            public void OnSetLiveStatus(string hostname, string message)
            {
                if (SetLiveStatusEvent != null)
                {
                    SetLiveStatusEvent(this, new SetLiveStatusEventArgs(hostname, message));
                }
            }
        }
    }

Сервер.в CS

    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);
            }
        }
    }


Комментарии
1 ответ

Нить фигню безопасности.

Просветительские мероприятия

Это не является потокобезопасным:

if (SetLiveStatusEvent != null)
{
SetLiveStatusEvent(this, new SetLiveStatusEventArgs(hostname, message));
}

Значение SetLiveStatusEvent может измениться после проверки на null, но, прежде чем вызывать его. Это не проблема, в примере кода, который вы выложили, но будет в более сложную систему. Вместо этого попробуйте что-то вроде этого:

var handler = SetLiveStatusEvent;
if (handler != null)
{
handler(this, new SetLiveStatusEventArgs(hostname, message));
}

Когда вы пишете ваши слушатели событий, имейте в виду, что существует ее возможность, что они будут вызываться после того, как они отписались от события.

Использованием класса queue с ручной синхронизации


Я не вижу никаких проблем с тем, как вы взаимодействуете с очередью, но в .Net версии 4.0 есть система.Коллекции.Одновременно.Класса методом blockingcollection, который будет заботиться о всех логика синхронизации для вас.

Другие вещи

Явное создание делегата

Вам не надо создавать новый экземпляр делегата.

socket.BeginConnect(IPs, port, new AsyncCallback(ConnectCallback), socket);
client.ConnectEvent += new ConnectEventHandler(OnConnect);

Вы можете просто передать имя метода.

socket.BeginConnect(IPs, port, ConnectCallback, socket);
client.ConnectEvent += OnConnect;

Пустой catch пункт

В методе ReceiveCallback и некоторые другие места, есть пустые catch (исключение) блоков, в рабочем коде я хочу хотя бы увидеть исключение регистрируется.

Также, при возможности не простудиться исключение в пользу более конкретных исключений.

Авто Свойства

Предполагая, что вы используете C# 4.0, вы могли бы использовать:

public string Hostname
{
get;
set;
}

Вместо:

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

5
ответ дан 21 мая 2011 в 11:05 Источник Поделиться