2009-07-31 Atsushi Enomoto <atsushi@ximian.com>
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels / TcpChannelListener.cs
index 19ba19a3f5f9b155c0a67855797878e15a0305e3..5cefba88394b03f84e18d552dc78c147d587f6df 100644 (file)
@@ -14,6 +14,7 @@ using System.Net;
 using System.Net.Sockets;
 using System.ServiceModel.Description;
 using System.Text;
+using System.Threading;
 using System.Xml;
 
 namespace System.ServiceModel.Channels
@@ -21,14 +22,12 @@ namespace System.ServiceModel.Channels
        internal class TcpChannelListener<TChannel> : InternalChannelListenerBase<TChannel> 
                where TChannel : class, IChannel
        {
-               List<IChannel> channels = new List<IChannel> ();
                BindingContext context;
                TcpChannelInfo info;
                IDuplexSession session;
                Uri listen_uri;
                TcpListener tcp_listener;
                
-               [MonoTODO]
                public TcpChannelListener (TcpTransportBindingElement source, 
                                           BindingContext context) : base (context.Binding)
                {
@@ -61,23 +60,79 @@ namespace System.ServiceModel.Channels
                public override Uri Uri {
                        get { return listen_uri; }
                }
-               
-               [MonoTODO]
+
+               List<ManualResetEvent> accept_handles = new List<ManualResetEvent> ();
+               List<TChannel> accepted_channels = new List<TChannel> ();
+
                protected override TChannel OnAcceptChannel (TimeSpan timeout)
                {
-                       TChannel channel = PopulateChannel (timeout);
-                       channels.Add (channel);
-                       return channel;
+                       DateTime start = DateTime.Now;
+
+                       // Close channels that are incorrectly kept open first.
+                       var l = new List<TcpDuplexSessionChannel> ();
+                       foreach (var tch in accepted_channels) {
+                               var dch = tch as TcpDuplexSessionChannel;
+                               if (dch != null && dch.TcpClient != null && !dch.TcpClient.Connected)
+                                       l.Add (dch);
+                       }
+                       foreach (var dch in l)
+                               dch.Close (timeout - (DateTime.Now - start));
+
+                       TcpClient client = AcceptTcpClient (timeout - (DateTime.Now - start));
+                       if (client == null)
+                               return null; // onclose
+
+                       TChannel ch;
+
+                       if (typeof (TChannel) == typeof (IDuplexSessionChannel))
+                               ch = (TChannel) (object) new TcpDuplexSessionChannel (this, info, client);
+                       else if (typeof (TChannel) == typeof (IReplyChannel))
+                               ch = (TChannel) (object) new TcpReplyChannel (this, info, client);
+                       else
+                               throw new InvalidOperationException (String.Format ("Channel type {0} is not supported.", typeof (TChannel).Name));
+
+                       ((ChannelBase) (object) ch).Closed += delegate {
+                               accepted_channels.Remove (ch);
+                               };
+                       accepted_channels.Add (ch);
+
+                       return ch;
                }
-               
-               TChannel PopulateChannel (TimeSpan timeout)
+
+               // TcpReplyChannel requires refreshed connection after each request processing.
+               internal TcpClient AcceptTcpClient (TimeSpan timeout)
                {
-                       // FIXME: pass delegate or something to remove the channel instance from "channels" when it is closed.
-                       if (typeof (TChannel) == typeof (IDuplexSessionChannel))
-                               return (TChannel) (object) new TcpDuplexSessionChannel (this, info, tcp_listener, timeout);
+                       DateTime start = DateTime.Now;
 
-                       // FIXME: To implement more.
-                       throw new NotImplementedException ();
+                       TcpClient client = null;
+                       if (tcp_listener.Pending ()) {
+                               client = tcp_listener.AcceptTcpClient ();
+                       } else {
+                               var wait = new ManualResetEvent (false);
+                               tcp_listener.BeginAcceptTcpClient (delegate (IAsyncResult result) {
+                                       client = tcp_listener.EndAcceptTcpClient (result);
+                                       wait.Set ();
+                                       accept_handles.Remove (wait);
+                               }, null);
+                               if (State == CommunicationState.Closing)
+                                       return null;
+                               accept_handles.Add (wait);
+                               wait.WaitOne (timeout);
+                       }
+
+                       // This may be optional though ...
+                       if (client != null) {
+                               foreach (var ch in accepted_channels) {
+                                       var dch = ch as TcpDuplexSessionChannel;
+                                       if (dch == null || dch.TcpClient == null)
+                                               continue;
+                                       if (((IPEndPoint) dch.TcpClient.Client.RemoteEndPoint).Equals (client.Client.RemoteEndPoint))
+                                               // ... then it should be handled in another BeginTryReceive/EndTryReceive loop in ChannelDispatcher.
+                                               return AcceptTcpClient (timeout - (DateTime.Now - start));
+                               }
+                       }
+
+                       return client;
                }
 
                [MonoTODO]
@@ -106,11 +161,14 @@ namespace System.ServiceModel.Channels
                {
                        if (tcp_listener == null)
                                throw new InvalidOperationException ("Current state is " + State);
+                       lock (accept_handles) {
+                               foreach (var wait in accept_handles)
+                                       wait.Set ();
+                       }
                        tcp_listener.Stop ();
                        tcp_listener = null;
                }
 
-               [MonoTODO]
                protected override void OnOpen (TimeSpan timeout)
                {
                        IPHostEntry entry = Dns.GetHostEntry (listen_uri.Host);