在 WinForms 应用程序中关闭套接字服务器的正确方法?

Proper way to shut down socket server in WinForms application?

我正在使用 C# 开发一个基本的套接字服务器,它需要 运行 作为 Windows Forms 应用程序的一部分。我从 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 应用程序时,我显然需要 运行 另一个线程上的服务器但是当用户关闭我的应用程序时,我想确保套接字服务器得到关机 "properly"。我最好的猜测是关闭所有打开的客户端连接,打破监听循环,然后关闭服务器套接字(不一定按此顺序)。

我看到一些建议只将服务器线程设为后台线程并让它在应用程序关闭时被销毁,但这似乎不太 "clean"。例如,如果客户端已连接并在应用程序关闭时发送数据,那么这对客户端有何影响?任何人都可以建议 "best practice" 关于正确关闭套接字服务器的建议吗?

我会将整个事情包装在一个任务中并使用取消标记。

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

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

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

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

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

  1. 这块: Console.WriteLine("\n按回车键继续..."); 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();
     }
    
      }
    }