図 2 クライアントとサーバーのための標準的な Socket 呼び出し

 

サーバー クライアント
Socket.Socket Socket.Socket
Socket.Bind Socket.Bind (オプション)
Socket.Listen Socket.Connect
Socket.Accept  
Socket.Read/Socket.Write Socket.Read/Socket.Write
Socket.Shutdown (オプション) Socket.Shutdown (オプション)
Socket.Close Socket.Close

図 3 重要なエラー コード

 

エラー番号 Winsock の値 SocketError の値 意味
10004 WSAEINTR Interrupted システム呼び出しが中断されました。ソケット呼び出しを実行中で、ソケットが閉じられた時に、この状況が発生します。
10048 WSAEADDRINUSE AddressAlreadyInUse 結合または聞き入れようとしているアドレスは、使用中です。
10053 WSACONNABORTED ConnectionAborted 接続がローカルで中断されました。
10054 WSAECONNRESET ConnectionReset 接続は、他のエンドによってリセットされました。
10061 WSAECONNREFUSED ConnectionRefused リモート ホストによって、接続が拒否されました。リモート ホストが起動していないか、ビジーでリスニング キューが一杯なために接続を受け入れられない場合に発生します。

図 4 サーバー ソケットの設定
private Socket _serverSocket;

private void SetupServerSocket()
{
    // ローカル マシンの情報を解析します。
    IPHostEntry localMachineInfo = 
        Dns.GetHostEntry(Dns.GetHostName());
    IPEndPoint myEndpoint = new IPEndPoint(
        localMachineInfo.AddressList[0], _port);

    // ソケットを作成し、結合し、聞き取りを開始します。
    _serverSocket = new Socket(myEndpoint.Address.AddressFamily, 
        SocketType.Stream, ProtocolType.Tcp);
    _serverSocket.Bind(myEndpoint);
    _serverSocket.Listen((int)SocketOptionName.MaxConnections);
}

図 5 単純なスレッデッド サーバー
class ThreadedServer
{
    private Socket _serverSocket;
    private int _port;

    public ThreadedServer(int port) { _port = port; }

    private class ConnectionInfo
    {
        public Socket Socket;
        public Thread Thread;
    }

    private Thread _acceptThread;
    private List<ConnectionInfo> _connections = 
        new List<ConnectionInfo>();

    public void Start()
    {
        SetupServerSocket();
        _acceptThread = new Thread(AcceptConnections);
        _acceptThread.IsBackground = true;
        _acceptThread.Start();
    }

    private void SetupServerSocket()
    {
        // ローカル マシンの情報を解析します。
        IPHostEntry localMachineInfo = 
            Dns.GetHostEntry(Dns.GetHostName());
        IPEndPoint myEndpoint = new IPEndPoint(
            localMachineInfo.AddressList[0], _port);

        // ソケットを作成し、結合し、聞き取りを開始します。
        _serverSocket = new Socket(myEndpoint.Address.AddressFamily, 
            SocketType.Stream, ProtocolType.Tcp);
        _serverSocket.Bind(myEndpoint);
        _serverSocket.Listen((int)SocketOptionName.MaxConnections);
    }

    private void AcceptConnections()
    {
        while (true)
        {
            // 接続を受け入れます。
            Socket socket = _serverSocket.Accept();
            ConnectionInfo connection = new ConnectionInfo();
            connection.Socket = socket;

            // 受信用スレッドを作成します。
            connection.Thread = new Thread(ProcessConnection);
            connection.Thread.IsBackground = true;
            connection.Thread.Start(connection);

            // ソケットを保存します。
            lock (_connections) _connections.Add(connection);
        }
    }

    private void ProcessConnection(object state)
    {
        ConnectionInfo connection = (ConnectionInfo)state;
        byte[] buffer = new byte[255];
        try
        {
            while (true)
            {
                int bytesRead = connection.Socket.Receive(buffer);
                if (bytesRead > 0)
                {
                    lock (_connections)
                    {
                        foreach (ConnectionInfo conn in _connections)
                        {
                            if (conn != connection) 
                            {
                                conn.Socket.Send(
                                    buffer, bytesRead, SocketFlags.None);
                            }
                        }
                    }
                }
                else if (bytesRead == 0) return;
            }
        }
        catch (SocketException exc)
        {
            Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
        }
        catch (Exception exc)
        {
            Console.WriteLine("Exception: " + exc);
        }
        finally
        {
            connection.Socket.Close();
            lock (_connections) _connections.Remove(connection);
        }
    }
}

図 6 Select ベース サーバー
class SelectBasedServer
{
    ... // ThreadedServer と SetupServerSocket は同じ要因です。

    public void Start()
    {
        Thread selectThread = new Thread(ProcessSockets);
        selectThread.IsBackground = true;
        selectThread.Start();
    }

    private void ProcessSockets()
    {
        byte[] buffer = new byte[255];
        List<Socket> readSockets = new List<Socket>();
        List<Socket> connectedSockets = new List<Socket>();
        try
        {
            SetupServerSocket();
            while (true)
            {
                // 読み出しリストを満たします。
                readSockets.Clear();
                readSockets.Add(_serverSocket);
                readSockets.AddRange(connectedSockets);

                // 何か実行することを待ちます。
                Socket.Select(readSockets, null, null, int.MaxValue);

                // 何か実行することを持つ各ソケットを処理します
                foreach (Socket readSocket in readSockets)
                {
                    if (readSocket == _serverSocket)
                    {
                        // 新しいクライアントのソケットを受け入れて保存します。
                        Socket newSocket = readSocket.Accept();
                        connectedSockets.Add(newSocket);
                    }
                    else
                    {
                        // データを読み出し、適切に処理します。
                        int bytesRead = readSocket.Receive(buffer);
                        if (0 == bytesRead)
                        {
                            connectedSockets.Remove(readSocket);
                            readSocket.Close();
                        }
                        else
                        {
                            foreach (Socket connectedSocket in 
                                connectedSockets)
                            {
                                if (connectedSocket != readSocket)
                                {
                                    connectedSocket.Send(buffer, 
                                        bytesRead, SocketFlags.None);
                                }
                            }
                        }
                    }
                }
            }
        }
        catch (SocketException exc)
        {
            Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
        }
        catch (Exception exc)
        {
            Console.WriteLine("Exception: " + exc);
        }
        finally
        {
            foreach (Socket s in connectedSockets) s.Close();
            connectedSockets.Clear();
        }
    }
}

図 7 非同期サーバー
class AsynchronousIoServer
{
    ... // ThreadedServer と SetupServerSocket は同じ要因です。

    private class ConnectionInfo
    {
        public Socket Socket;
        public byte[] Buffer;
    }

    private List<ConnectionInfo> _connections = 
        new List<ConnectionInfo>();

    public void Start()
    {
        SetupServerSocket();
        for (int i = 0; i < 10; i++)
            _serverSocket.BeginAccept(
                new AsyncCallback(AcceptCallback), _serverSocket);
    }

    private void AcceptCallback(IAsyncResult result)
    {
        ConnectionInfo connection = new ConnectionInfo();
        try
        {
            // Accpet を終了します。
            Socket s = (Socket)result.AsyncState;
            connection.Socket = s.EndAccept(result);
            connection.Buffer = new byte[255];
            lock (_connections) _connections.Add(connection);

            // Receive と新しい Accept を開始します。
            connection.Socket.BeginReceive(connection.Buffer, 0, 
                connection.Buffer.Length, SocketFlags.None, 
                new AsyncCallback(ReceiveCallback), connection);
            _serverSocket.BeginAccept(new AsyncCallback(AcceptCallback), 
                result.AsyncState);
        }
        catch (SocketException exc)
        {
            CloseConnection(connection);
            Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
        }
        catch (Exception exc)
        {
            CloseConnection(connection);
            Console.WriteLine("Exception: " + exc);
        }
    }

    private void ReceiveCallback(IAsyncResult result)
    {
        ConnectionInfo connection = (ConnectionInfo)result.AsyncState;
        try
        {
            int bytesRead = connection.Socket.EndReceive(result);
            if (0 != bytesRead)
            {
                lock (_connections)
                {
                    foreach (ConnectionInfo conn in _connections)
                    {
                        if (connection != conn)
                        {
                            conn.Socket.Send(connection.Buffer, bytesRead, 
                                SocketFlags.None);
                        }
                    }
                }
                connection.Socket.BeginReceive(connection.Buffer, 0, 
                    connection.Buffer.Length, SocketFlags.None, 
                    new AsyncCallback(ReceiveCallback), connection);
            }
            else CloseConnection(connection);
        }
        catch (SocketException exc)
        {
            CloseConnection(connection);
            Console.WriteLine("Socket exception: " + exc.SocketErrorCode);
        }
        catch (Exception exc)
        {
            CloseConnection(connection);
            Console.WriteLine("Exception: " + exc);
        }
    }

    private void CloseConnection(ConnectionInfo ci)
    {
        ci.Socket.Close();
        lock (_connections) _connections.Remove(ci);
    }
}

図 8 構成ファイル
<configuration>
  <system.diagnostics>
    <sources>
      <source name="System.Net.Sockets">
        <listeners>
          <add name="Sockets"/>
        </listeners>
      </source>
    </sources>
    <switches>
      <add name="System.Net.Sockets" value="31" />
    </switches>
    <sharedListeners>
    <add name="Sockets" type="System.Diagnostics.TextWriterTraceListener" 
      initializeData="Sockets.log"/>
    </sharedListeners>
    <trace autoflush="true" />
  </system.diagnostics>
</configuration>