无法关闭异步C#服务器套接字连接

时间:2014-01-28 10:43:49

标签: c# sockets asyncsocket

我正在编写代码,允许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<EOF>"的消息来停止服务器。 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 个答案:

答案 0 :(得分:0)

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

  

telnet localhost 3000

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

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

请注意while(true)循环中的更改:当服务器收到end<EOF>时,它不仅会将 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());
            }
        }
    }
}