0
votes

I'm working on a basic socket server in C# that needs to run as part of a Windows Forms application. I started with the asynchronous socket server code from MSDN. Like many socket server code samples, this one is a console mode application. Here's a sample of the StartListening() method that gets called from Main():

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();
}

As you can see, there's a while(true) loop in there which uses a ManualResetEvent to wait until a client connection is established before entering the next iteration of the while loop. I'm not sure why they included the Console.WriteLine(\n"Press ENTER to continue...") statement since there's no way to break out of the while(true) loop.

Anyway, in thinking about converting this into a WinForms application, I would obviously need to run the server on another thread but when the user closes my application, I want to make sure that the socket server gets shutdown "properly". My best guess at what that means is closing any open client connections, breaking out of the listening loop and then shutting down the server socket (not necessarily in that order).

I've seen some suggestions that recommend just making the server thread a background thread and letting it get destroyed when the application is shutdown but that doesn't seem very "clean". If, for example, a client is connected and sending data when the application is closed, what does that do to the client? Can anyone suggest a "best practice" with regard to shutting down a socket server properly?

3

3 Answers

1
votes

Actually you don't need a thread, cause you're already listening asynchronously.

Just call serverSocket.BeginAccept again at the end of AcceptCallback. Then the shutdown of your server reduces to serverSocket.Close() (obviously, serverSocket needs to be a class field).

Finally, in AcceptCallback you would need to catch the exception of EndAccept. That, btw, is the reason, why there's Console.Writeline and Console.Readkey in the example: it's executed when an exception occurs.

1
votes

I would wrap that entire thing in a Task and use a cancellation token.

See https://msdn.microsoft.com/en-us/library/hh160373(v=vs.110).aspx for a good example on using tasks and cancellation tokens. You can make the cancellation token cancel the task when you want to shut down the socket server.

Inside the task an exception is raised. In the exception handler you call Socket.Close which will stop the BeginAccept call (EndAccept will be called but the Socket.Handle will be -1).

1
votes
  1. This block: Console.WriteLine("\nPress ENTER to continue..."); Console.Read(); is there because the way this method is written it will exit the loop if an exception is thrown.

  2. I would write it like this to fit your parameters:

    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();
     }
    
      }
    }