2014-01-28 210 views
2

我正在編寫允許Android客戶端連接到C#服務器套接字的代碼。客戶端和服務器正常工作,但無法關閉或斷開套接字。無法關閉異步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; 
    } 
} 

最後已試圖通過使用String "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() 
+0

1.請澄清一下** **斷開意味着_this當我嘗試斷開連接,然後啓動插座again_發生錯誤 - 是它最終''或者是'Singleton.Instance.setIsEnded( true)'在某個button_click處理程序中執行? –

+0

2.另外_如果我停止服務器,然後嘗試從Android客戶端發送一個字符串 - 你如何阻止它(請參閱previos評論)? –

+0

3.並且_信息在服務器上被接收,然後我在服務器console_上收到以下信息 - 您如何知道收到的信息?你在控制檯打印了嗎?什麼時候是「那麼」? –

回答

0

這是一個完整的服務器測試控制檯程序。這是容易測試使用Telnet客戶端:

遠程登錄本地主機3000

注意,你將無法看到您鍵入的內容,但是當你按下輸入,所有行會發送到服務器。但是由於服務器迴應數據(當它收到<EOF>字符串時),您將在telnet控制檯中看到該數據作爲響應。

它主要是你的代碼。 爲了更好地理解事件流,異步Socket使用的多線程性質,我添加了許多* Console.WriteLine * s。

注意的變化在while(true)循環:當服務器收到end<EOF>這不僅會設置IsEnded真正,也將設置ManualResetEvent的,讓等待的監聽線程疏通並注意已結束標誌。

也擺脫了第二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()); 
      } 
     } 
    } 
} 
+0

不幸的是,我已經嘗試過這一點,但它會產生相同的錯誤。 – Inishkenny

+0

查看我的更新回答。代碼主要是你的 - 我只是讓它工作。它仍然不是線程安全的,容易出錯,甚至可能永遠不會遇到)快樂學習) –

+0

一些興趣點:線程安全的Singleton發佈,線程安全的IsEnded標誌(內存模型,指令重新排序,易失性),套接字。 *異步方法家庭 –