2017-08-12 109 views
1

下面的代码创建套接字服务器和客户端C#插座BeginAccept事件停止射击

我启动服务器,如果我在其他以后开始一个客户端。如果我立即启动三个客户端,它工作正常

,然后针对一个或多个客户执行代码波纹管后BeginAccept事件不会触发

结果波纹管是

服务器入门

服务器正在等待连接...

客户0.0.0.0:6352请求连接

客户0.0.0.0:6353请求连接

客户127.0.0.1:6351请求连接

客户127.0.0.1:6351连接连接

客户端127.0

客户127.0.0.1:6352。 0.1:6353连接

ServerOnClientConnection客户:127.0.0.1:6351

Server正在等待的连接...

ServerOnClientConnection客户:127.0.0.1:6353

代码如下

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

    namespace Test { 
public class TestSockets { 

    #region server 
    Socket serverSocket; 
    bool serverIsAlive; 
    public ManualResetEvent waitForConnection = new ManualResetEvent(false); 
    private Encoding encod = Encoding.Unicode; 

    public void ServerStartInThread() { 
     byte[] bytes = new Byte[1024]; 
     IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); 
     IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 5500); 
     Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
     serverSocket = socket; 
     try { 
      socket.Bind(localEndPoint); 
      socket.Listen(100); 
      Thread pollThread = new Thread(delegate() { 
       serverIsAlive = true; // needs if reopen 
       SendMessage("Server Started"); 
       while (serverIsAlive) { 
        try { 
         SendMessage("Server is waiting for a connection..."); 
         socket.BeginAccept(new AsyncCallback(ServerOnClientConnection), socket); 
         waitForConnection.Reset(); 
         waitForConnection.WaitOne(); 
        } 
        catch (Exception ex) { 
         SendMessage("Server: " + ex.ToString()); 
        } 
       } 
       SendMessage("Server Stopped"); 
       socket.Close(); 
      }) { 
       Name = "SocketServer" 
      }; 
      pollThread.Start(); 
     } 
     catch (Exception ex) { 
      SendMessage("Server: " + ex.ToString()); 
     } 
    } 

    public void ServerOnClientConnection(IAsyncResult ar) { 
     try { 
      Socket listener = (Socket)ar.AsyncState; 
      Socket clientSocket = listener.EndAccept(ar); 
      SendMessage("ServerOnClientConnection Client: " + clientSocket.RemoteEndPoint.ToString()); 
      StateObject state = new StateObject() { 
       socket = clientSocket 
      }; 
      clientSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ServerReceiveCallback), state); 
      waitForConnection.Set(); 
     } 
     catch (Exception ex) { 
      SendMessage("ServerOnClientConnection: " + ex.ToString()); 
     } 
    } 

    public void ServerReceiveCallback(IAsyncResult ar) { 
     StateObject state = (StateObject)ar.AsyncState; 
     Socket socket = state.socket; 
     try { 
      if (socket == null) return; 
      if (!socket.Connected) { 
       return; 
      } 
      int bytesRead = socket.EndReceive(ar); 
      if (bytesRead > 0) { 
       state.sb.Append(encod.GetString(state.buffer, 0, bytesRead)); 
       socket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ServerReceiveCallback), state); 
      } 
     } 
     catch (Exception ex) { 
      SendMessage("ServerReceiveCallback: " + ex.ToString()); 
     } 
    } 
    #endregion 

    #region client 
    private Socket client; 
    private bool isAlive = false; 
    private ManualResetEvent connectDone = new ManualResetEvent(false); 

    public void StartInThread() { 
     try { 
      IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); 
      IPEndPoint remoteEP = new IPEndPoint(ipAddress, 5500); 
      Thread pollThread = new Thread(delegate() { 
       isAlive = true; 
       while (isAlive) { 
        try { 
         if (client != null && client.Connected) { 
          continue; 
         } 
         client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
         client.BeginConnect(remoteEP, new AsyncCallback(ClientConnectCallback), client); 
         SendMessage(string.Format("Client {0} requests connection", client.LocalEndPoint.ToString())); 
         connectDone.Reset(); 
         connectDone.WaitOne(3000, false); 
         if (client.Connected) { 
          StateObject state = new StateObject() { 
           socket = client 
          }; 
          SendMessage(string.Format("Client {0} connected", client.LocalEndPoint.ToString())); 
          client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ClientReceiveCallback), state); 
         } 
        } 
        catch (Exception ex) { 
         SendMessage("ClientStartInThread1: " + ex.ToString()); 
        } 
       } 
       SendMessage("Client Disconnected"); 
      }) { 
       Name = "ClientThread" 
      }; 
      pollThread.Start(); 
     } 
     catch (Exception ex) { 
      SendMessage("ClientStartInThread2: " + ex.ToString()); 
     } 
    } 

    private void ClientConnectCallback(IAsyncResult ar) { 
     try { 
      Socket socket = (Socket)ar.AsyncState; 
      socket.EndConnect(ar); 
      connectDone.Set(); 
     } 
     catch (Exception ex) { 
      SendMessage("ClientConnectCallback: " + ex.ToString()); 
     } 
    } 

    private void ClientReceiveCallback(IAsyncResult ar) { 
     StateObject state = (StateObject)ar.AsyncState; 
     Socket socket = state.socket; 
     if (socket == null || !socket.Connected) return; 
     try { 
      int bytesRead = socket.EndReceive(ar); 
      if (bytesRead > 0) { 
       state.sb.Append(encod.GetString(state.buffer, 0, bytesRead)); 
       socket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ClientReceiveCallback), state); 
      } 
      else { 
       socket.Close(); 
      } 
     } 
     catch (Exception ex) { 
      SendMessage("ClientReceiveCallback: " + ex.ToString()); 
      socket.Close(); 
     } 
    } 

    #endregion 

    private void SendMessage(string v) { 
     System.Diagnostics.Debug.WriteLine(v); 
    } 

    public static void Start() { 
     TestSockets server = new TestSockets(); 
     server.ServerStartInThread(); 
     TestSockets c1 = new TestSockets(); 
     c1.StartInThread(); 
     TestSockets c2 = new TestSockets(); 
     c2.StartInThread(); 
     TestSockets c3 = new TestSockets(); 
     c3.StartInThread(); 

    } 
} 
public class StateObject { 
    public Socket socket = null; 
    public const int BufferSize = 1024; 
    public byte[] buffer = new byte[BufferSize]; 
    public StringBuilder sb = new StringBuilder(); 
} 

}

+0

顺便说一句,谢谢你成为为堆栈溢出发布[tag:sockets]问题的少数人之一,这个问题实际上包含了一个可靠地重现问题的良好[mcve]。 (好吧,我确实需要将实际的调用添加到'TestSockets.Start()'...但这与我们通常在这里遇到的可怕的网络/套接字问题相比是一个小问题。) –

回答

0

你有一个您的代码中的竞争条件导致waitForConnection事件句柄被设置两次,第二次调用Set()不起作用,之后主服务器线程有机会通过BeginAccept()并重置句柄。这导致它错过第二组。

修复它的一种方法是切换到信号量对象。例如:

#region server 
Socket serverSocket; 
bool serverIsAlive; 
SemaphoreSlim waitForConnection = new SemaphoreSlim(0); 
private Encoding encod = Encoding.Unicode; 

public void ServerStartInThread() 
{ 
    byte[] bytes = new Byte[1024]; 
    IPAddress ipAddress = IPAddress.Parse("127.0.0.1"); 
    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, 5500); 
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
    serverSocket = socket; 
    try 
    { 
     socket.Bind(localEndPoint); 
     socket.Listen(100); 
     Thread pollThread = new Thread(delegate() { 
      serverIsAlive = true; // needs if reopen 
      SendMessage("Server Started"); 
      while (serverIsAlive) 
      { 
       try 
       { 
        SendMessage("Server is waiting for a connection..."); 
        socket.BeginAccept(new AsyncCallback(ServerOnClientConnection), socket); 
        waitForConnection.Wait(); 
       } 
       catch (Exception ex) 
       { 
        SendMessage("Server: " + ex.ToString()); 
       } 
      } 
      SendMessage("Server Stopped"); 
      socket.Close(); 
     }) 
     { 
      Name = "SocketServer" 
     }; 
     pollThread.Start(); 
    } 
    catch (Exception ex) 
    { 
     SendMessage("Server: " + ex.ToString()); 
    } 
} 

public void ServerOnClientConnection(IAsyncResult ar) 
{ 
    try 
    { 
     Socket listener = (Socket)ar.AsyncState; 
     Socket clientSocket = listener.EndAccept(ar); 
     SendMessage("ServerOnClientConnection Client: " + clientSocket.RemoteEndPoint.ToString()); 
     StateObject state = new StateObject() 
     { 
      socket = clientSocket 
     }; 
     clientSocket.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ServerReceiveCallback), state); 
     waitForConnection.Release(); 
    } 
    catch (Exception ex) 
    { 
     SendMessage("ServerOnClientConnection: " + ex.ToString()); 
    } 
} 

这将使ServerOnClientConnection()方法来增加信号计数,这样直到所有接受的连接已经观察到主服务器的线程可以继续循环。

但是,坦率地说,使用这些事件句柄(这是我假设你的代码是直接或间接基于的)的MSDN示例是恕我直言,只是破碎。它们引入了线程同步的复杂性,而实际上并不需要这些复杂性,这不必要地使代码复杂化并使得代码难以正常工作。

更惯用的方法是不会有额外的线程都在ServerStartInThread()方法调用一次BeginAccept()(你很可能重新命名只是ServerStart())和处理目前公认的客户端后,调用BeginAccept()ServerOnClientConnection()方法。即就像你现在处理BeginReceive()一样。

这就是说,恕我直言,即使是惯用的方法已经过时。它在API初次设计时运行得非常好,但自此以后.NET已经走过了很长的路,并且拥有更好的机制来处理这种异步操作。我在this answer上发布了一个简单的聊天服务器的例子,它展示了如何使用async/await以更简单,更易于阅读的方式实现这类事情。你可能想看看如何将相同的技术应用到自己的代码的想法。

+0

谢谢SemaphoreSlim解决方案非常直截了当,但我更喜欢BeginAccept()到ServerOnClientConnection更习惯性的接近。 – user3624787