简体   繁体   中英

C# Asychronous Sockets/Multithreading: Am I using async sockets and threading correctly?

I've read numerous tutorials and other pages on asynchronous sockets in C#. I need to write a TCP client that can perform reading and writing at the same time. That is why I chose asynchronous sockets. Here is the code. Am I using asynchronous sockets and threading correctly?

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

    namespace WindowsFormsApplication2
    {
        public delegate void ConnectEventHandler(object sender, ConnectEventArgs e);
        // More similar delegates defined for each event to pass to the GUI thread...

        public class Client
        {
            bool m_MasterSwitch;
            ArrayList m_ProductList;

            public event ConnectEventHandler ConnectEvent = delegate { };

            Socket m_Socket;

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

            public Client()
            {
                m_MasterSwitch = false;
                m_ProductList = new ArrayList();

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

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

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

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

                m_WaitHandle.Set();
            }

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

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

                        task = m_Tasks.Dequeue();
                    }

                    if (task == null)
                    {
                        return;
                    }
                    else
                    {
                        task.DoTask(this);
                    }
                }
            }

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

                    m_Socket.BeginConnect(IPs, port, new AsyncCallback(ConnectCallback), m_Socket);
                }
                catch (Exception)
                {
                    OnConnect(false, "Unable to connect to server.");
                }
            }

            private void ConnectCallback(IAsyncResult ar)
            {
                try
                {
                    Socket socket = (Socket)ar.AsyncState;

                    socket.EndConnect(ar);

                    OnConnect(true, "Successfully connected to server.");

                    StartRead(socket);
                }
                catch (Exception)
                {
                    OnConnect(false, "Unable to connect to server.");
                }
            }

            private void StartRead(Socket socket)
            {
                StateObject state = new StateObject();
                state.AsyncSocket = socket;

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

            private void ReceiveCallback(IAsyncResult ar)
            {
                try
                {
                    StateObject state = (StateObject)ar.AsyncState;

                    int bytes_read = state.AsyncSocket.EndReceive(ar);

                    char[] chars = new char[bytes_read + 1];
                    System.Text.Decoder decoder = System.Text.Encoding.UTF8.GetDecoder();
                    int charLength = decoder.GetChars(state.Buffer, 0, bytes_read, chars, 0);

                    String data = new String(chars);

                    ParseMessage(data);

                    StartRead(state.AsyncSocket);
                }
                catch (ObjectDisposedException)
                {
                }
                catch (SocketException)
                {
                }
            }

            private void StartSend(string message)
            {
                try
                {
                    StateObject state = new StateObject();
                    state.AsyncSocket = m_Socket;

                    byte[] buffer = Encoding.UTF8.GetBytes(message);
                    m_Socket.BeginSend(buffer, 0, buffer.Length, 0, new AsyncCallback(SendCallback), state);
                }
                catch (Exception)
                {
                }
            }

            private void SendCallback(IAsyncResult ar)
            {
                try
                {
                    StateObject state = (StateObject)ar.AsyncState;
                    state.AsyncSocket.EndSend(ar);
                }
                catch
                {
                }
            }

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

                foreach (string message in messages)
                {
                    if (message != "\0")
                    {
                        string[] tokens = message.Split(',');

                        string command = tokens[0];

                        switch (command)
                        {
                            case "@1":
                            {
                                string exchangeName = tokens[1];
                                string connectionStatus = tokens[2];
                                string loggedInStatus = tokens[3];

                                bool connectionStatusBool;
                                bool loggedInStatusBool;

                                if (connectionStatus == "1")
                                {
                                    connectionStatusBool = true;
                                }
                                else
                                {
                                    connectionStatusBool = false;
                                }

                                if (loggedInStatus == "1")
                                {
                                    loggedInStatusBool = true;
                                }
                                else
                                {
                                    loggedInStatusBool = false;
                                }

                                OnExchangeStatusMessage(exchangeName, connectionStatusBool, loggedInStatusBool);

                                break;
                            }
                            case "@2":
                            {
                                string isError = tokens[1];
                                string logMessage = tokens[2];
                                bool isErrorBool;

                                if (isError == "error")
                                {
                                    isErrorBool = true;
                                }
                                else
                                {
                                    isErrorBool = false;
                                }

                                OnLogMessage(isErrorBool, logMessage);

                                break;
                            }
                            case "@3":
                            {
                                OnLogMessage(false, message);

                                break;
                            }
                            case "@4":
                            {
                                string masterSwitch = tokens[1];

                                if (masterSwitch == "1")
                                {
                                    m_MasterSwitch = true;
                                }
                                else
                                {
                                    m_MasterSwitch = false;
                                }

                                m_ProductList.Clear();

                                for (int i = 2; i < tokens.Length; ++i)
                                {
                                    string[] productArray = tokens[i].Split('=');

                                    if (productArray.Length < 2)
                                    {
                                        break;
                                    }

                                    string name = productArray[0];
                                    string isLive = productArray[1];
                                    bool isLiveBool;

                                    if (isLive == "1")
                                    {
                                        isLiveBool = true;
                                    }
                                    else
                                    {
                                        isLiveBool = false;
                                    }

                                    m_ProductList.Add(new Product(name, isLiveBool));
                                }

                                OnLiveMessage(m_MasterSwitch, m_ProductList);

                                break;
                            }
                            default:
                                break;
                        }
                    }
                }
            }

            public void SetLiveStatus()
            {
                bool newState = (! m_MasterSwitch);
                string newStateString;

                if (newState)
                {
                    newStateString = "1";
                }
                else
                {
                    newStateString = "0";
                }

                StartSend(String.Format("@4,{0}\n", newStateString));
            }

            public void SetProductLiveStatus(Product product)
            {
                if (m_ProductList.Contains(product))
                {
                    string masterSwitchString;

                    if (m_MasterSwitch)
                    {
                        masterSwitchString = "1";
                    }
                    else
                    {
                        masterSwitchString = "0";
                    }

                    bool newState = (! product.IsLive);
                    string newStateString;

                    if (newState)
                    {
                        newStateString = "1";
                    }
                    else
                    {
                        newStateString = "0";
                    }

                    StartSend(String.Format("@4,{0},{1}={2}\n", masterSwitchString, product.Name, newStateString));
                }
            }

            private void OnConnect(bool isConnected, string message)
            {
                ConnectEvent(this, new ConnectEventArgs(isConnected, message));
            }

            // More events to pass to the GUI...
        }
    }

As others have stated, there is a lot of code to go through. But just from a quick read...

  1. You're missing message framing .
  2. You're ignoring the return value of EndSend . It is possible that a partial send may complete, and you'll have to send the rest of the data.

There is a lot of code here and it doesn't compile so I can't run it or test it. I have to code review the code based on how async socket code is normally written and mentally execute the code to see if I think it would work.

So do I think it would work? I don't know. Does it work? You have run it? If I say it's OK and it doesn't work then I'll feel dumb because maybe I overlooked something obvious in your code.

But for a simple "eyes only" code review, I can say, yes, you appear to be using the aysnc socket API within the bounds of the way it was intended to be used. There are many stylistic things that I would have done differently but that is not your question.

But even if you have a good basic async design, many things can go wrong in the implementation. This client/server still needs to be integrated into an application and many async design issues depend critically on that relationship.

Only you can compile, run, debug and test your program. In the course of doing that, if you run into a specific problem that makes you suspect that your async design is flawed, try to whittle it down to a small test case and come back and we'll see if we can help.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM