2 // TcpChannelListener.cs
5 // Marcos Cobena (marcoscobena@gmail.com)
6 // Atsushi Enomoto (atsushi@ximian.com)
8 // Copyright 2007 Marcos Cobena (http://www.youcannoteatbits.org/)
9 // Copyright 2009-2010 Novell, Inc (http://www.novell.com/)
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 using System.Collections.Generic;
35 using System.Net.Sockets;
36 using System.ServiceModel.Description;
38 using System.Threading;
41 namespace System.ServiceModel.Channels.NetTcp
43 internal class TcpChannelListener<TChannel> : InternalChannelListenerBase<TChannel>
44 where TChannel : class, IChannel
46 BindingContext context;
48 TcpListener tcp_listener;
50 public TcpChannelListener (TcpTransportBindingElement source, BindingContext context)
53 XmlDictionaryReaderQuotas quotas = null;
55 foreach (BindingElement be in context.Binding.Elements) {
56 MessageEncodingBindingElement mbe = be as MessageEncodingBindingElement;
58 MessageEncoder = CreateEncoder<TChannel> (mbe);
59 quotas = mbe.GetProperty<XmlDictionaryReaderQuotas> (context);
64 if (MessageEncoder == null)
65 MessageEncoder = new BinaryMessageEncoder ();
67 info = new TcpChannelInfo (source, MessageEncoder, quotas);
70 SynchronizedCollection<ManualResetEvent> accept_handles = new SynchronizedCollection<ManualResetEvent> ();
71 Queue<TcpClient> accepted_clients = new Queue<TcpClient> ();
72 SynchronizedCollection<TChannel> accepted_channels = new SynchronizedCollection<TChannel> ();
74 protected override TChannel OnAcceptChannel (TimeSpan timeout)
76 DateTime start = DateTime.Now;
78 // Close channels that are incorrectly kept open first.
79 var l = new List<TcpDuplexSessionChannel> ();
80 foreach (var tch in accepted_channels) {
81 var dch = tch as TcpDuplexSessionChannel;
82 if (dch != null && dch.TcpClient != null && !dch.TcpClient.Connected)
85 foreach (var dch in l)
86 dch.Close (timeout - (DateTime.Now - start));
88 TcpClient client = AcceptTcpClient (timeout - (DateTime.Now - start));
90 return null; // onclose
94 if (typeof (TChannel) == typeof (IDuplexSessionChannel))
95 ch = (TChannel) (object) new TcpDuplexSessionChannel (this, info, client);
96 else if (typeof (TChannel) == typeof (IReplyChannel))
97 ch = (TChannel) (object) new TcpReplyChannel (this, info, client);
99 throw new InvalidOperationException (String.Format ("Channel type {0} is not supported.", typeof (TChannel).Name));
101 ((ChannelBase) (object) ch).Closed += delegate {
102 accepted_channels.Remove (ch);
104 accepted_channels.Add (ch);
109 // TcpReplyChannel requires refreshed connection after each request processing.
110 internal TcpClient AcceptTcpClient (TimeSpan timeout)
112 DateTime start = DateTime.Now;
114 TcpClient client = accepted_clients.Count == 0 ? null : accepted_clients.Dequeue ();
115 if (client == null) {
116 var wait = new ManualResetEvent (false);
117 accept_handles.Add (wait);
118 if (!wait.WaitOne (timeout)) {
119 accept_handles.Remove (wait);
122 accept_handles.Remove (wait);
123 // recurse with new timeout, or return null if it's either being closed or timed out.
124 timeout -= (DateTime.Now - start);
125 return State == CommunicationState.Opened && timeout > TimeSpan.Zero ? AcceptTcpClient (timeout) : null;
128 // There might be bettwe way to exclude those TCP clients though ...
129 foreach (var ch in accepted_channels) {
130 var dch = ch as TcpDuplexSessionChannel;
131 if (dch == null || dch.TcpClient == null && !dch.TcpClient.Connected)
133 if (((IPEndPoint) dch.TcpClient.Client.RemoteEndPoint).Equals (client.Client.RemoteEndPoint))
134 // ... then it should be handled in another BeginTryReceive/EndTryReceive loop in ChannelDispatcher.
135 return AcceptTcpClient (timeout - (DateTime.Now - start));
142 protected override bool OnWaitForChannel (TimeSpan timeout)
144 throw new NotImplementedException ();
147 // CommunicationObject
149 protected override void OnAbort ()
151 if (State == CommunicationState.Closed)
153 ProcessClose (TimeSpan.Zero);
156 protected override void OnClose (TimeSpan timeout)
158 if (State == CommunicationState.Closed)
160 ProcessClose (timeout);
163 void ProcessClose (TimeSpan timeout)
165 if (tcp_listener == null)
166 throw new InvalidOperationException ("Current state is " + State);
167 //tcp_listener.Client.Close (Math.Max (50, (int) timeout.TotalMilliseconds));
168 tcp_listener.Stop ();
169 var l = new List<ManualResetEvent> (accept_handles);
170 foreach (var wait in l) // those handles will disappear from accepted_handles
175 protected override void OnOpen (TimeSpan timeout)
177 IPHostEntry entry = Dns.GetHostEntry (Uri.Host);
179 if (entry.AddressList.Length ==0)
180 throw new ArgumentException (String.Format ("Invalid listen URI: {0}", Uri));
182 int explicitPort = Uri.Port;
183 tcp_listener = new TcpListener (entry.AddressList [0], explicitPort <= 0 ? TcpTransportBindingElement.DefaultPort : explicitPort);
184 tcp_listener.Start ();
185 tcp_listener.BeginAcceptTcpClient (TcpListenerAcceptedClient, tcp_listener);
188 void TcpListenerAcceptedClient (IAsyncResult result)
190 var listener = (TcpListener) result.AsyncState;
192 var client = listener.EndAcceptTcpClient (result);
193 if (client != null) {
194 accepted_clients.Enqueue (client);
195 if (accept_handles.Count > 0)
196 accept_handles [0].Set ();
199 /* If an accept fails, just ignore it. Maybe the remote peer disconnected already */
201 if (State == CommunicationState.Opened) {
203 listener.BeginAcceptTcpClient (TcpListenerAcceptedClient, listener);
205 /* If this fails, we must have disposed the listener */