none
非同期ソケットのサーバー側の解放について RRS feed

  • 質問

  • おかげさまで非同期通信がある程度まで出来るようになりました。

    そこである問題が発生しています。

    クライアント側からサーバーに接続、切断が何度も出来ることを確認するコードを書きました。

    サーバー側は非同期、クライアントは同期です。

    using System;
    using System.Net;
    using System.Net.Sockets;
    class Backend {
        static void OnAccept(Object sender,SocketAsyncEventArgs e) {
            var AcceptSocket=e.AcceptSocket;
            var プール=e.UserToken as プール;
            プール.AcceptSocket=AcceptSocket;
            AcceptSocket.ReceiveAsync(プール.Receive);
        }
        static void OnReceive(Object sender,SocketAsyncEventArgs e) {
            var プール=e.UserToken as プール;
            var AcceptSocket=プール.AcceptSocket;
            var Buffer=e.Buffer;
            var BytesTransferred=e.BytesTransferred;
            if(BytesTransferred==0) {
                AcceptSocket.DisconnectAsync(プール.Disconnect);
                return;
            } else {
                Console.WriteLine("受信されないのでここは通過しないはず。");
            }
        }
        static void OnDisconnect(Object sender,SocketAsyncEventArgs e) {
            var プール=e.UserToken as プール;
            try {
                listenSocket.AcceptAsync(プール.Accept);
            } catch(Exception ex) {
                Console.WriteLine(ex.Message);
            }
        }
        public class プール {
            public SocketAsyncEventArgs Accept;
            public SocketAsyncEventArgs Receive;
            public SocketAsyncEventArgs Disconnect;
            public Socket AcceptSocket;
            public Byte[] Receiveバイト数Buffer;
            public プール() {
                this.Accept=new SocketAsyncEventArgs();
                this.Receive=new SocketAsyncEventArgs();
                this.Disconnect=new SocketAsyncEventArgs();
                Accept.Completed+=OnAccept;
                Receive.Completed+=OnReceive;
                Disconnect.Completed+=OnDisconnect;
                Accept.UserToken=this;
                Receive.UserToken=this;
                Disconnect.UserToken=this;
                Receive.SetBuffer(new Byte[4],0,4);
            }
        }
        static Socket listenSocket;
        static void Main(string[] args) {
            //サーバー
            listenSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            listenSocket.Bind(new IPEndPoint(IPAddress.Any,1000));
            listenSocket.Listen(1);
            listenSocket.AcceptAsync(new プール().Accept);
            //クライアント
            Action ConnectClose=() => {
                try {
                    while(true) {
                        var F=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
                        F.Connect("localhost",1000);
                        F.Shutdown(SocketShutdown.Both);
                        F.Disconnect(false);
                    }
                } catch(Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            };
            ConnectClose.BeginInvoke(null,null);
            Console.ReadKey();
        }
    }
    
    クライアントは接続後何もせずに切断するとサーバーは0バイト受信したことになりソケットを閉じます。
    再び受け入れる処理になります。
    しかしTcpListener.start(1)でしていた値、ここでは1個までしか接続できません。
    サーバーでの解放処理に何か足りないものがある気がしますがお分かりいただけるでしょうか?

    2012年4月26日 1:17

回答

  • Listen()の引数に依らず、AcceptAsync()メソッドはCompletedイベントを1度しか発生させないはずです。複数のクライアント接続を受け付けるには、AcceptAsync()を複数回呼び出す必要があります。
    このことはAccept()メソッドやBeginAccept() / EndAccept()メソッドからも想像つくと思いますが…。

    ところで.NET Micro Frameworkをお使いでしたっけ…?

    DisconectのたびにAcceptAsyncを呼び出しているのでうまくいくと考えてました。
    会社のPCだとうまくいき、自宅だとうまくいかず、更に調査すると非同期メソッドの戻り値trueだと動機で終了しCompleateイベントが発生しないという仕様が関係している気がします。まだ調査中です。それを想定したテストコードです↓

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Diagnostics;
    class P {
        public static Socket ListenerSocket;
        public class プール {
            internal SocketAsyncEventArgs AcceptAsync;
            private SocketAsyncEventArgs ReceiveAsync;
            private SocketAsyncEventArgs DisconnectAsync;
            private Socket Socket;
            public プール() {
                this.AcceptAsync=new SocketAsyncEventArgs();
                this.ReceiveAsync=new SocketAsyncEventArgs();
                this.DisconnectAsync=new SocketAsyncEventArgs();
                AcceptAsync.Completed+=OnAcceptAsync;
                ReceiveAsync.Completed+=OnReceiveAsync;
                DisconnectAsync.Completed+=OnDisconnectAsync;
                DisconnectAsync.DisconnectReuseSocket=true;
                ReceiveAsync.SetBuffer(new Byte[4],0,4);
            }
            private void OnAcceptAsync(Object sender,SocketAsyncEventArgs e) {
                this.Socket=e.AcceptSocket;
                if(this.Socket.ReceiveAsync(this.ReceiveAsync)) return;
                this.OnReceiveAsync(sender,this.ReceiveAsync);
            }
            private void OnReceiveAsync(Object sender,SocketAsyncEventArgs e) {
                var Socket=this.Socket;
                var Buffer=e.Buffer;
                var BytesTransferred=e.BytesTransferred;
                if(BytesTransferred==0) {
                    if(Socket.DisconnectAsync(this.DisconnectAsync)) return;
                    this.OnDisconnectAsync(sender,this.DisconnectAsync);
                } else {
                    Console.WriteLine("受信されないのでここは通過しないはず。");
                }
            }
            private void OnDisconnectAsync(Object sender,SocketAsyncEventArgs e) {
                try {
                    if(ListenerSocket.AcceptAsync(this.AcceptAsync)) return;
                    this.OnAcceptAsync(sender,this.AcceptAsync);
                } catch(Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            }
        }
        static Int32 カウント;
        static void Main(string[] args) {
            var a=new プール();
            ListenerSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            ListenerSocket.Bind(new IPEndPoint(IPAddress.Any,1000));
            ListenerSocket.Listen(10);
            ListenerSocket.AcceptAsync(a.AcceptAsync);
            var s=Stopwatch.StartNew();
        再試行:
            try {
                while(true) {
                    var F=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
                    F.Connect("localhost",1000);
                    F.Shutdown(SocketShutdown.Both);
                    F.Close();
                    Console.WriteLine(カウント.ToString());
                    カウント++;
                }
            } catch(SocketException ex) {
                if(ex.SocketErrorCode==SocketError.ConnectionRefused) goto 再試行;
                throw ex;
            }
        }
    }

    しばらく実行すると

    システムのバッファー領域が不足しているか、またはキューがいっぱいなため、ソケット操作を実行できませんでした。 127.0.0.1:1000

    とでます。原因と対策がまだわかrません。→http://terutomi.seesaa.net/article/11847591.html

    に原因がありました。根本的な解決法を考えてみます。.NETは普通の.NET Framework4.0です。





    • 編集済み 和和和 2012年4月29日 4:05
    • 回答としてマーク 和和和 2012年5月1日 21:33
    2012年4月28日 12:33

すべての返信

  • Listen()の引数に依らず、AcceptAsync()メソッドはCompletedイベントを1度しか発生させないはずです。複数のクライアント接続を受け付けるには、AcceptAsync()を複数回呼び出す必要があります。
    このことはAccept()メソッドやBeginAccept() / EndAccept()メソッドからも想像つくと思いますが…。

    ところで.NET Micro Frameworkをお使いでしたっけ…?

    • 編集済み 佐祐理 2012年4月26日 2:13
    2012年4月26日 1:37
  • Listen()の引数に依らず、AcceptAsync()メソッドはCompletedイベントを1度しか発生させないはずです。複数のクライアント接続を受け付けるには、AcceptAsync()を複数回呼び出す必要があります。
    このことはAccept()メソッドやBeginAccept() / EndAccept()メソッドからも想像つくと思いますが…。

    ところで.NET Micro Frameworkをお使いでしたっけ…?

    DisconectのたびにAcceptAsyncを呼び出しているのでうまくいくと考えてました。
    会社のPCだとうまくいき、自宅だとうまくいかず、更に調査すると非同期メソッドの戻り値trueだと動機で終了しCompleateイベントが発生しないという仕様が関係している気がします。まだ調査中です。それを想定したテストコードです↓

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Threading;
    using System.Diagnostics;
    class P {
        public static Socket ListenerSocket;
        public class プール {
            internal SocketAsyncEventArgs AcceptAsync;
            private SocketAsyncEventArgs ReceiveAsync;
            private SocketAsyncEventArgs DisconnectAsync;
            private Socket Socket;
            public プール() {
                this.AcceptAsync=new SocketAsyncEventArgs();
                this.ReceiveAsync=new SocketAsyncEventArgs();
                this.DisconnectAsync=new SocketAsyncEventArgs();
                AcceptAsync.Completed+=OnAcceptAsync;
                ReceiveAsync.Completed+=OnReceiveAsync;
                DisconnectAsync.Completed+=OnDisconnectAsync;
                DisconnectAsync.DisconnectReuseSocket=true;
                ReceiveAsync.SetBuffer(new Byte[4],0,4);
            }
            private void OnAcceptAsync(Object sender,SocketAsyncEventArgs e) {
                this.Socket=e.AcceptSocket;
                if(this.Socket.ReceiveAsync(this.ReceiveAsync)) return;
                this.OnReceiveAsync(sender,this.ReceiveAsync);
            }
            private void OnReceiveAsync(Object sender,SocketAsyncEventArgs e) {
                var Socket=this.Socket;
                var Buffer=e.Buffer;
                var BytesTransferred=e.BytesTransferred;
                if(BytesTransferred==0) {
                    if(Socket.DisconnectAsync(this.DisconnectAsync)) return;
                    this.OnDisconnectAsync(sender,this.DisconnectAsync);
                } else {
                    Console.WriteLine("受信されないのでここは通過しないはず。");
                }
            }
            private void OnDisconnectAsync(Object sender,SocketAsyncEventArgs e) {
                try {
                    if(ListenerSocket.AcceptAsync(this.AcceptAsync)) return;
                    this.OnAcceptAsync(sender,this.AcceptAsync);
                } catch(Exception ex) {
                    Console.WriteLine(ex.Message);
                }
            }
        }
        static Int32 カウント;
        static void Main(string[] args) {
            var a=new プール();
            ListenerSocket=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
            ListenerSocket.Bind(new IPEndPoint(IPAddress.Any,1000));
            ListenerSocket.Listen(10);
            ListenerSocket.AcceptAsync(a.AcceptAsync);
            var s=Stopwatch.StartNew();
        再試行:
            try {
                while(true) {
                    var F=new Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.Tcp);
                    F.Connect("localhost",1000);
                    F.Shutdown(SocketShutdown.Both);
                    F.Close();
                    Console.WriteLine(カウント.ToString());
                    カウント++;
                }
            } catch(SocketException ex) {
                if(ex.SocketErrorCode==SocketError.ConnectionRefused) goto 再試行;
                throw ex;
            }
        }
    }

    しばらく実行すると

    システムのバッファー領域が不足しているか、またはキューがいっぱいなため、ソケット操作を実行できませんでした。 127.0.0.1:1000

    とでます。原因と対策がまだわかrません。→http://terutomi.seesaa.net/article/11847591.html

    に原因がありました。根本的な解決法を考えてみます。.NETは普通の.NET Framework4.0です。





    • 編集済み 和和和 2012年4月29日 4:05
    • 回答としてマーク 和和和 2012年5月1日 21:33
    2012年4月28日 12:33