【问题标题】:Proper way to shut down socket server in WinForms application?在 WinForms 应用程序中关闭套接字服务器的正确方法?
【发布时间】:2015-06-24 08:28:04
【问题描述】:

我正在使用 C# 编写一个基本的套接字服务器,它需要作为 Windows 窗体应用程序的一部分运行。我从MSDN 的异步套接字服务器代码开始。像许多套接字服务器代码示例一样,这是一个控制台模式应用程序。下面是从 Main() 调用的 StartListening() 方法的示例:

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

    IPAddress ipAddress = IPAddress.Parse(serverIpAddress);
    IPEndPoint localEndPoint = new IPEndPoint(ipAddress, portNumber);

    // create TCP/IP socket
    Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

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

        while (true)
        {
            // set the allDone ManualResetEvent to nonsignaled state
            allDone.Reset();

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

            // wait until a connection is made before continuing
            allDone.WaitOne();
        }

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

    Console.WriteLine("\nPress ENTER to continue...");
    Console.Read();
}

如您所见,其中有一个 while(true) 循环,它使用 ManualResetEvent 等待客户端连接建立,然后再进入 while 循环的下一次迭代。我不确定他们为什么包含 Console.WriteLine(\n"Press ENTER to continue...") 语句,因为没有办法跳出 while(true) 循环。

无论如何,在考虑将其转换为 WinForms 应用程序时,我显然需要在另一个线程上运行服务器,但是当用户关闭我的应用程序时,我想确保套接字服务器“正确”关闭。我对这意味着什么的最佳猜测是关闭任何打开的客户端连接,打破侦听循环,然后关闭服务器套接字(不一定按此顺序)。

我看到了一些建议,建议只将服务器线程设置为后台线程,并在应用程序关闭时让它被销毁,但这似乎不是很“干净”。例如,如果一个客户端在应用程序关闭时连接并发送数据,这对客户端有什么影响?任何人都可以提出关于正确关闭套接字服务器的“最佳实践”吗?

【问题讨论】:

    标签: c# winforms sockets server application-shutdown


    【解决方案1】:
    1. 这个块: Console.WriteLine("\n按 ENTER 继续..."); Console.Read(); 是因为这个方法的编写方式,如果抛出异常,它将退出循环。

    2. 我会这样写来适应你的参数:

      using System;
      using System.Collections.Generic;
      using System.ComponentModel;
      using System.Data;
      using System.Drawing;
      using System.Linq;
      using System.Net;
      using System.Net.Sockets;
      using System.Text;
      using System.Threading.Tasks;
      using System.Windows.Forms;
      
      namespace WindowsFormsApplication1 {
      public partial class Form1 : Form {
          private Socket serverSocket;
          public Form1() {
              InitializeComponent();
          }
      
          private void Form1_Load(object sender, EventArgs e) {
              StartListening();
          }
      
          private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
              serverSocket.Close();
          }
      
          public void StartListening() {
              byte[] bytes = new byte[1024];
      
              IPAddress ipAddress = IPAddress.Parse(serverIpAddress);
              IPEndPoint localEndPoint = new IPEndPoint(ipAddress, portNumber);
      
      
              serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
      
              try {
                serverSocket.Bind(localEndPoint);
                serverSocket.Listen(100);
      
                while(true) {
                   allDone.Reset();
      
      
                   Console.WriteLine("Waiting for a connection...");
                   serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), serverSocket);
      
             // wait until a connection is made before continuing
                   allDone.WaitOne();
                 }
      
               }
               catch(Exception e) {
                   Console.WriteLine(e.ToString());
               }
              finally {
                   serverSocket.Close();
              }
      
              Console.WriteLine("\nPress ENTER to continue...");
              Console.Read();
       }
      
        }
      }
      

    【讨论】:

      【解决方案2】:

      其实你不需要线程,因为你已经在异步监听了。

      只需在AcceptCallback 末尾再次调用serverSocket.BeginAccept。 然后你的服务器的关闭减少到serverSocket.Close()(显然serverSocket需要是一个类字段)。

      最后,在AcceptCallback 中,您需要捕获EndAccept 的异常。顺便说一句,这就是为什么在示例中有Console.WritelineConsole.Readkey 的原因:它在发生异常时执行。

      【讨论】:

      • 噢!不敢相信我查看了 Console.WriteLine() 语句,并没有意识到这是为了防止控制台窗口在引发异常时关闭!感谢您指出这一点!
      【解决方案3】:

      我会将整个内容包装在一个任务中并使用取消令牌。

      请参阅https://msdn.microsoft.com/en-us/library/hh160373(v=vs.110).aspx,了解有关使用任务和取消令牌的好例子。当您想关闭套接字服务器时,您可以使取消令牌取消任务。

      在任务内部引发异常。在异常处理程序中调用 Socket.Close,它将停止 BeginAccept 调用(将调用 EndAccept 但 Socket.Handle 将为 -1)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-10-13
        • 1970-01-01
        • 2012-02-08
        • 2016-05-12
        相关资源
        最近更新 更多