1

我有一个简单的客户端-服务器应用程序,它的工作原理如下:服务器总是在监听(在一个单独的线程中)客户端连接(它发送它希望服务器杀死的进程的名称)。

这是服务器:

private void btnStart_Click(object sender, EventArgs e)
{
  _port = int.Parse(comboBoxPorts.SelectedItem.ToString());

  _tcpListener = new TcpListener(_ipAddress, _port);

  _keepRunning = true;
  _listenerThread = new Thread(Listen);
  HandleListenerThreadStartListenEvent += HandleListenerThreadStartedEventMethod;
  ListenerThreadStartedEvent += HandleListenerThreadStartListenEvent;
  _listenerThread.Start();
}

private void btnStop_Click(object sender, EventArgs e)
{ 
  if (_tcpListener != null)
  {
    _keepRunning = false; 

    if (_tcpListener.Server.Connected)
    {
      _tcpListener.Server.Disconnect(true); 
    } 

    _tcpListener.Stop(); 
  }

  labelServerStatus.Text = "Server is stopped";   
  comboBoxPorts.Enabled = true; 
  btnStart.Enabled = true; 
  btnStop.Enabled = false; 
} 

private void Listen()
{
  try
  {
    _tcpListener.Start();
    OnListenerThreadStartListenEvent(); // just update the GUI
  }
  catch(Exception e)
  {
    MessageBox.Show("Port " + _port + " is NOT available." + Environment.NewLine +
                    "Please choose another one: " + e.Message);
    return;
  }

  _keepRunning = true;
  string ballonMessage = "Socket Server Running at " + _ipAddress + ", port: " + _port;
  notifyIcon1.ShowBalloonTip(2000, "Simplex Listener", ballonMessage, ToolTipIcon.Info);

  while (_keepRunning)
  {
    try
    {          
      #region using AcceptSocket()

      _clientSocket = _tcpListener.AcceptSocket();
      string checkString = string.Empty;
      IPAddress ipOfClient = ((IPEndPoint) _clientSocket.LocalEndPoint).Address;
      notifyIcon1.ShowBalloonTip(2000, "Simplex Listener", "New client has connected from ip " + ipOfClient, ToolTipIcon.Info);

      byte[] buffer = new byte[SIZE_OF_BUFFER];
      int bytesReceived = _clientSocket.Receive(buffer);

      // Concatenate chars as bytes to a received string.
      for (int i = 0; i < bytesReceived; i++)
        checkString += Convert.ToChar(buffer[i]);



      //..... getting the name of process and kill it (and than open it...
      RestartProcess(nameOfProcess, windowName, pathToExeFile);

      // Client is waiting to know operation is complete- so send him some char...
      ASCIIEncoding encoder = new ASCIIEncoding();
      _clientSocket.Send(encoder.GetBytes("v"));
      _clientSocket.Disconnect(true);
      _clientSocket.Close();
      #endregion
    }
    catch (Exception )
    {    
    }
  }
}

客户端:

public void RestartTheSoftwareInServerComputer(string ipOfServer, int portNumber)
{
  TcpClient client = new TcpClient();

  if (_serverEndPoint == null)
  {
    _serverEndPoint = new IPEndPoint(IPAddress.Parse(ipOfServer), portNumber);     
  }

  client.Connect(_serverEndPoint);

  // Send the command to the server:
  NetworkStream clientStream = client.GetStream();
  ASCIIEncoding encoder = new ASCIIEncoding();
  byte[] buffer = encoder.GetBytes("....detailsOfProcess....");

  clientStream.Write(buffer, 0, buffer.Length);
  clientStream.Flush();

  // Now, wait for the server's response [which means the process had been restart].
  NetworkStream stream = client.GetStream();
  byte[] bytes = new byte[5];
  stream.Read(bytes, 0, 5);
  string response = Encoding.UTF8.GetString(bytes, 0, 1);

  if (response.Equals("x"))
  {
    throw new Exception("Failed to restart X software.");
  }

  stream.Close();
  client.Close();
}

当我停止并重新启动服务器时(当没有客户端连接时),一切正常。

问题是当服务器连接了一些客户端并重新启动时,客户端已断开连接,需要重新启动服务器。当我们再次点击“START SERVER”时,它会得到异常:

每个套接字地址(协议/网络地址/端口)通常只允许使用一次。

我应该如何关闭端口?

4

1 回答 1

0

当您退出服务器时,您应该调用 _tcpListener.Stop() 来关闭服务器正在侦听的主套接字。

编辑:您也可以尝试在停止按钮单击中调用 _listenerThread.Join() ,以等待侦听器线程完成,然后再开始下一个。

private void btnStop_Click(object sender, EventArgs e)
{ 
  if (_tcpListener != null)
  {
    _keepRunning = false;

    if (_tcpListener.Server.Connected)
    {
      _tcpListener.Server.Disconnect(true); 
      _tcpListener.Stop();
      if (_clientSocket != null)
      {
        _clientSocket.Close();
        _clientSocket = null;
      }
      _listenerThread.Join();
    }
  }

  labelServerStatus.Text = "Server is stopped";   
  comboBoxPorts.Enabled = true; 
  btnStart.Enabled = true; 
  btnStop.Enabled = false; 
} 

编辑2:

这是一个类似于您的服务器的 Windows 窗体。我不需要客户端,只需在命令提示符下使用“telnet localhost 49152”来“假装”成为连接的客户端。

public partial class Form1 : Form
{
    private TcpListener _tcpListener;
    private bool _keepRunning;
    private Thread _listenerThread;
    private Socket _clientSocket;


    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var address = IPAddress.Parse("127.0.0.1");
        _tcpListener = new TcpListener(address, 49152);

        _keepRunning = true;
        _listenerThread = new Thread(Listen);
        _listenerThread.Start();

        button1.Enabled = false;
        button2.Enabled = true;
    }

    private void Listen()
    {
        try
        {
            _tcpListener.Start();
        }
        catch (Exception e)
        {
            MessageBox.Show(e.Message);
            return;
        }

        _keepRunning = true;

        while (_keepRunning)
        {
            try
            {
                _clientSocket = _tcpListener.AcceptSocket();

                var buffer = new byte[8192];
                var bytesReceived = _clientSocket.Receive(buffer);
                var checkString = String.Empty;

                if (_keepRunning)
                {
                    // bytesReceived can be 0 if the remote socket disconnected
                    if (bytesReceived > 0)
                    {
                        checkString = Encoding.ASCII.GetString(buffer, 0, bytesReceived);

                        // Client is waiting to know operation is complete- so send him some char...
                        var encoder = new ASCIIEncoding();
                        _clientSocket.Send(encoder.GetBytes("v"));
                    }
                    if (_clientSocket.Connected) _clientSocket.Disconnect(true);
                }
                _clientSocket.Close();
            }
            catch (Exception ex)
            {
                // really should do something with these exceptions
            }
        }
    }

    private void button2_Click(object sender, EventArgs e)
    {
        if (_tcpListener != null)
        {
            _keepRunning = false;

            if (_tcpListener.Server.Connected)
            {
                _tcpListener.Server.Disconnect(true);
            }
            _tcpListener.Stop();

            if (_clientSocket != null)
            {
                _clientSocket.Close();
                _clientSocket = null;
            }
            _listenerThread.Join();
        }

        button1.Enabled = true;
        button2.Enabled = false;
    }
}

这段代码有很多问题,例如跨线程共享变量等,但在我的机器上,Join 似乎不会阻塞任何时间。_keepRunning 的问题在于,在某些系统上,一个线程可能看不到从 true 到 false 的变化,因为它被优化或缓存了。您应该真正使用某种形式的线程同步,使其易失,或将其包裹在锁中等。我建议您在这里阅读有关此内容。我还建议您也阅读一下 Sockets,或者如果其他评论者提到,如果您对了解套接字和线程的所有特性不感兴趣,也许您应该寻找一个隐藏的更高级别的库这一切?

于 2013-02-07T07:21:05.620 回答