SocketAsyncEventArgs を使ったサーバーを作る

まぁ、MS の公式サイトに載ってるのをほぼぱくっただけですが。
Begin/End パターンに見慣れているので、StartAccept を BeginAccept にしたってだけ。受け入れたクライアントを閉じる処理はしていない。OnAccept は virtual にしておいた。

  • Server.Start() -> AcceptClient イベント
  • Server を継承したクラスで OnAccept を override

という使い方ができるかな。

using System;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;

namespace Samples
{
    delegate void AcceptClient(object sender, Socket client);

    class ServerBase
    {
        Socket listener;
        IPEndPoint endpoint;
        EventHandler<SocketAsyncEventArgs> acceptCallback;

        public bool IsListening { get; private set; }
        public event AcceptClient AcceptClient;

        public ServerBase(IPEndPoint endpoint)
        {
            if (endpoint == null)
                throw new ArgumentNullException("endpoint");

            this.endpoint = endpoint;
            this.acceptCallback = new EventHandler<SocketAsyncEventArgs>(AcceptCompleted);
            this.IsListening = false;
        }

        public void Start()
        {
            if (IsListening) // リスニング中
                return;

            // 新しく作る
            listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            listener.Bind(endpoint);
            listener.Listen(10);
            IsListening = true; // true にしたあと BeginAccept

            BeginAccept(null); // 受け入れ開始
        }

        public void Stop()
        {
            if (!IsListening) // 既に止まってる
                return;
            IsListening = false;
            listener.Close();
            listener = null;
        }

        protected virtual void OnAccept(Socket client)
        {
            if (AcceptClient != null)
                AcceptClient(this, client);
        }

        private void BeginAccept(SocketAsyncEventArgs args)
        {
            if (IsListening)
            {
                if (args == null) // 1回だけ作る。後は再利用する。
                {
                    args = new SocketAsyncEventArgs();
                    args.Completed += acceptCallback;
                }
                else
                {
                    args.AcceptSocket = null;
                }

                if (!listener.AcceptAsync(args))
                    EndAccept(args); // 同期的に完了した場合
            }
            else
            {
                if (args != null)
                    args.Dispose();
            }
        }

        private void AcceptCompleted(object sender, SocketAsyncEventArgs args)
        {
            EndAccept(args);
        }

        private void EndAccept(SocketAsyncEventArgs args)
        {
            Socket client = args.AcceptSocket; // 取り出してすぐ次の受け入れへ
            BeginAccept(args);

            if (args.SocketError == SocketError.Success)
            {
                OnAccept(client); // event を発生させる
            }
            else
            {
                if (client != null && client.Connected)
                    client.Close();
            }
        }
    }
}