【问题标题】:Cannot close Asynchronous C# Server Socket connection无法关闭异步 C# 服务器套接字连接
【发布时间】:2014-03-27 01:18:49
【问题描述】:

我正在编写允许 Android 客户端连接到 C# 服务器套接字的代码。客户端和服务器工作正常,但我无法关闭或断开套接字。

服务器由点击事件启动:

private void btnStartServer_Click(object sender, EventArgs e)
{
    AsynchronousSocketListener Async = new AsynchronousSocketListener();
    receiveThread = new Thread(new ThreadStart(Async.StartListening));
    receiveThread.Start();

    btnStartServer.Enabled = false;
    btnStopServer.Enabled = true;
    MessageBox.Show("Server Started");
}

然后是服务器代码的大头:

// State object for reading client data asynchronously
public class StateObject
{
    // Client  socket.
    public Socket workSocket = null;
    // Size of receive buffer.
    public const int BufferSize = 1024;
    // Receive buffer.
    public byte[] buffer = new byte[BufferSize];
    // Received data string.
    public StringBuilder sb = new StringBuilder();  
}

public class AsynchronousSocketListener
{
    // Thread signal.
    public static ManualResetEvent allDone = new ManualResetEvent(false);

    public AsynchronousSocketListener()
    {
    }

    public void StartListening()
    {
        // Data buffer for incoming data.
        byte[] bytes = new Byte[1024];

        // Establish the local endpoint for the socket.
        IPHostEntry ipHostInfo = Dns.Resolve(Dns.GetHostName());
        IPAddress ipAddress = ipHostInfo.AddressList[0];
        IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 3000);
        System.Diagnostics.Debug.WriteLine(ipAddress);

        // Create a TCP/IP socket.
        Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp );

        // Bind the socket to the local endpoint and listen for incoming connections.
        try
        {
            listener.Bind(localEndPoint);
            listener.Listen(100);

            while (true)
            {
                // Set the event to nonsignaled state.
                allDone.Reset();

                // Start an asynchronous socket to listen for connections.
                Console.WriteLine("Waiting for a connection...");
                listener.BeginAccept(new AsyncCallback(AcceptCallback), listener );

                Singleton s = Singleton.Instance;

                if (s.getIsEnded() == false)
                {
                    // Wait until a connection is made before continuing.
                    allDone.WaitOne();
                }
                else
                {
                    listener.Shutdown(SocketShutdown.Both);
                    listener.Disconnect(true);
                    break;
                }
            }
        }
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }

    public static void AcceptCallback(IAsyncResult ar)
    {
        // Get the socket that handles the client request.
        Socket listener = (Socket) ar.AsyncState;
        Socket handler = listener.EndAccept(ar);

        // Create the state object.
        StateObject state = new StateObject();
        state.workSocket = handler;
        handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);

        // Signal the main thread to continue.
        allDone.Set();
    }

    public static void ReadCallback(IAsyncResult ar)
    {
        String content = String.Empty;

        // Retrieve the state object and the handler socket
        // from the asynchronous state object.
        StateObject state = (StateObject) ar.AsyncState;
        Socket handler = state.workSocket;

        // Read data from the client socket. 
        int bytesRead = handler.EndReceive(ar);

        if (bytesRead > 0)
        {
            // There  might be more data, so store the data received so far.
            state.sb.Append(Encoding.ASCII.GetString(state.buffer,0,bytesRead));

            // Check for end-of-file tag. If it is not there, read 
            // more data.
            content = state.sb.ToString();
            if (content.IndexOf("<EOF>") > -1)
            {
                // All the data has been read from the 
                // client. Display it on the console.
                Console.WriteLine("Read {0} bytes from socket. \n Data : {1}", content.Length, content );
                if (content.Equals("end<EOF>"))
                {
                    Console.WriteLine("Should end");
                    Singleton s = Singleton.Instance;
                    s.setIsEnded(true);
                }
                // Echo the data back to the client.
                Send(handler, content);
            }
            else
            {
                // Not all data received. Get more.
                handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0,
                new AsyncCallback(ReadCallback), state);
            }
        }
    }

    private static void Send(Socket handler, String data)
    {
        // Convert the string data to byte data using ASCII encoding.
        byte[] byteData = Encoding.ASCII.GetBytes(data);

        // Begin sending the data to the remote device.
        handler.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), handler);
    }

    private static void SendCallback(IAsyncResult ar)
    {
        try
        {
            // Retrieve the socket from the state object.
            Socket handler = (Socket) ar.AsyncState;

            // Complete sending the data to the remote device.
            int bytesSent = handler.EndSend(ar);
            Console.WriteLine("Sent {0} bytes to client.", bytesSent);

            handler.Shutdown(SocketShutdown.Both);
            handler.Close();
        } 
        catch (Exception e)
        {
            Console.WriteLine(e.ToString());
        }
    }
}

使用单例,我可以保留一个唯一变量来检查服务器是否应该运行。这是在上面的StartListening() 方法中检查的:

public class Singleton
{
    private static Singleton instance;
    private Boolean isEnded = false;

    private Singleton() { }

    public static Singleton Instance
    {
        get
        {
            if (instance == null)
            {
                instance = new Singleton();
            }
            return instance;
        }
    }

    public void setIsEnded(Boolean setter)
    {
        isEnded = setter;
    }

    public Boolean getIsEnded()
    {
        return isEnded;
    }
}

终于尝试通过向服务器发送带有字符串"end&lt;EOF&gt;" 的消息来停止服务器。 ReadCallback() 的服务器逻辑将通知单例设置isEnded = true。这不是一个很好的解决方案,但它是我在撰写本文时可以获得一半工作的唯一方法。断开套接字的逻辑在StartListening() 中。理想情况下,它会断开连接,以便重新启动套接字。

当我尝试断开连接然后再次启动套接字时出现此错误:

A first chance exception of type 'System.Net.Sockets.SocketException' occurred in System.dll
System.Net.Sockets.SocketException (0x80004005): Only one usage of each socket address (protocol/network address/port) is normally permitted
    at System.Net.Sockets.Socket.DoBind(EndPoint endPointSnapshot, SocketAddress socketAddress)
    at System.Net.Sockets.Socket.Bind(EndPoint localEP)
    at StartServer.AsynchronousSocketListener.StartListening() in c:\Users\Conor\Desktop\StartServer\StartServer\StartServer.cs:line 89

如果我停止服务器然后尝试从 android 客户端发送一个字符串,则在服务器上接收到消息,然后我在服务器控制台上收到以下消息:

System.Net.Sockets.SocketException (0x80004005): A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied
    at System.Net.Sockets.Socket.Shutdown(SocketShutdown how)
    at StartServer.AsynchronousSocketListener.StartListening()

【问题讨论】:

  • 1.请澄清 disconnect当我尝试断开连接然后再次启动套接字时发生此错误 中的含义 - 是 end&lt;EOF&gt; 还是 Singleton.Instance.setIsEnded(true) 在某些情况下执行button_click 处理程序?
  • 2.另外 如果我停止服务器,然后尝试从 android 客户端发送一个字符串 - 你如何停止它(参见前面的评论)?
  • 3.而且在服务器上收到消息,然后我在服务器控制台上收到以下消息 - 你怎么知道收到了消息?你是在控制台打印的吗? “那么”到底是什么时候?
  • end&lt;EOF&gt; 从按钮单击事件发送到服务器。就目前而言,当在ReadCallback() 中接收到end&lt;EOF&gt; 时,逻辑会将单例中的isEnded 设置为true。这一切都发生在后台线程中。在AsynchronousSocketListener() 类中StartListening() 方法的While (true) 循环中,我使用isEnded 单例值的值来运行listener.Shutdown(SocketShutdown.Both);listener.Disconnect(true);。这就是我说 disconnect 时所指的代码。
  • 消息被写入控制台。 然后 是在消息写入控制台之后。我相信它可能会接受客户端数据,因为线程仍在后台运行。不过我不确定。

标签: c# sockets asyncsocket


【解决方案1】:

这是一个完整的服务器测试控制台程序。使用 telnet 客户端很容易测试:

远程登录本地主机 3000

请注意,您将无法看到您输入的内容,但是当您按下 Enter 时,所有行都将发送到服务器。但是由于服务器会回显数据(当它接收到&lt;EOF&gt; 字符串时),您会在 telnet 控制台中看到它作为响应。

主要是你的代码。 我添加了许多 *Console.WriteLine* 以便您更好地了解事件流、异步 Socket 使用的多线程性质。

注意while(true)循环中的变化:当服务器收到end&lt;EOF&gt;时,它不仅将IsEnded设置为true,还会设置ManualResetEvent,让等待的监听线程解除阻塞并注意IsEnded标志。

还要摆脱第二个 SocketException(在 Socket.Shutdown 中),您不会尝试关闭您的侦听套接字。仅关闭您读取和写入的套接字。

还要注意 AcceptCallback 中的 try-catch(ObjectDisposedException):使用我的代码,您不太可能偶然发现此异常,但在我编写示例时,我用它优雅地捕获了关闭侦听套接字的事件。

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

class Program
{
    static void Main(string[] args)
    {
        var asyncSocketListener = new AsynchronousSocketListener();
        var listenerThread = new Thread(asyncSocketListener.StartListening);
        listenerThread.Start();
        Console.WriteLine("Server Started");

        listenerThread.Join();
        Console.WriteLine("Press any key to exit...");
        Console.ReadKey();
    }

    public class Singleton
    {
        private static Singleton instance;
        private bool isEnded;

        private Singleton() { }

        public static Singleton Instance
        {
            get
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }

        public bool IsEnded
        {
            get { return isEnded; }
            set { isEnded = value; }
        }
    }

    public class AsynchronousSocketListener
    {
        // State object for reading client data asynchronously
        private class StateObject
        {
            // Size of receive buffer.
            public const int BufferSize = 1024;
            // Receive buffer.
            public byte[] Buffer = new byte[BufferSize];
            // Client  socket.
            public Socket WorkSocket;
            // Received data string.
            public StringBuilder Sb = new StringBuilder();
        }

        // Thread signal.
        private static ManualResetEvent allDone = new ManualResetEvent(false);

        public void StartListening()
        {
            var localEndPoint = new IPEndPoint(IPAddress.Any, 3000);
            var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

            // Bind the socket to the local endpoint and listen for incoming connections.
            try
            {
                listener.Bind(localEndPoint);
                listener.Listen(100);
                Console.WriteLine("Listening on {0}...", localEndPoint.ToString());

                while (true)
                {
                    allDone.Reset();

                    if (Singleton.Instance.IsEnded)
                    {
                        Console.WriteLine("Closing listener socket...");

                        listener.Close();
                        break;
                    }

                    Console.WriteLine("Waiting for a new connection...");
                    listener.BeginAccept(AcceptCallback, state: listener);

                    allDone.WaitOne();
                }

                Console.WriteLine("Server stopped.");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }

        public static void AcceptCallback(IAsyncResult ar)
        {
            Socket clientSocket;
            try
            {
                clientSocket = ((Socket)ar.AsyncState).EndAccept(ar);
            }
            catch (ObjectDisposedException)
            {
                Console.WriteLine(" Listening socket has been closed.");
                allDone.Set();
                return;
            }

            Console.WriteLine(" Connection accepted {0}", clientSocket.RemoteEndPoint.ToString());

            var stateObject = new StateObject { WorkSocket = clientSocket };
            clientSocket.BeginReceive(stateObject.Buffer, 0, StateObject.BufferSize, 0, ReadCallback, stateObject);

            // Signal the main thread to continue.
            Console.WriteLine(" Signal the main thread to accept a new connection");
            allDone.Set();
        }

        public static void ReadCallback(IAsyncResult ar)
        {
            string content;

            // Retrieve the state object and the handler socket
            // from the asynchronous state object.
            var stateObject = (StateObject)ar.AsyncState;
            Socket clientSocket = stateObject.WorkSocket;

            // Read data from the client socket. 
            int bytesRead = clientSocket.EndReceive(ar);

            if (bytesRead > 0)
            {
                // There  might be more data, so store the data received so far.
                stateObject.Sb.Append(Encoding.ASCII.GetString(stateObject.Buffer, 0, bytesRead));

                // Check for end-of-file tag. If it is not there, read 
                // more data.
                content = stateObject.Sb.ToString();
                if (content.IndexOf("<EOF>") > -1)
                {
                    // All the data has been read from the 
                    // client. Display it on the console.
                    Console.WriteLine("     Read {0} bytes from socket. \n      Data : {1}", content.Length, content);
                    if (content.Equals("end<EOF>"))
                    {
                        Console.WriteLine("     !!!Should stop the server");
                        Singleton.Instance.IsEnded = true;
                        allDone.Set();
                    }

                    // Echo the data back to the client.
                    byte[] byteData = Encoding.ASCII.GetBytes(content);
                    clientSocket.BeginSend(byteData, 0, byteData.Length, 0, WriteCallback, clientSocket);
                }
                else
                {
                    // Not all data received. Get more.
                    clientSocket.BeginReceive(stateObject.Buffer, 0, StateObject.BufferSize, 0, ReadCallback, stateObject);
                }
            }
        }

        private static void WriteCallback(IAsyncResult ar)
        {
            try
            {
                // Retrieve the socket from the state object.
                var clientSocket = (Socket)ar.AsyncState;

                // Complete sending the data to the remote device.
                int bytesSent = clientSocket.EndSend(ar);
                Console.WriteLine("         Sent {0} bytes to client.", bytesSent);
                Console.WriteLine("         Disconnecting the client...");

                clientSocket.Shutdown(SocketShutdown.Both);
                clientSocket.Close();

                Console.WriteLine("         Client disconnected.");
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}

【讨论】:

  • 不幸的是我已经尝试过了,但它产生了同样的错误。
  • 查看我的更新答案。代码大部分是你的——我只是让它工作。它仍然不是线程安全的并且容易出现错误,您甚至可能永远不会遇到)快乐学习)
  • 一些兴趣点:线程安全的 Singleton 发布、线程安全的 IsEnded 标志(内存模型、指令重新排序、易失性)、Socket.*异步方法系列
  • @Inishkenny 您对您的代码还有其他问题吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-11-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多