2 // TcpDuplexSessionChannel.cs
5 // Marcos Cobena (marcoscobena@gmail.com)
6 // Atsushi Enomoto <atsushi@ximian.com>
8 // Copyright 2007 Marcos Cobena (http://www.youcannoteatbits.org/)
10 // Copyright (C) 2009 Novell, Inc (http://www.novell.com)
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using System
.Collections
.Generic
;
36 using System
.Net
.Sockets
;
37 using System
.Runtime
.Serialization
;
38 using System
.Runtime
.Serialization
.Formatters
.Binary
;
39 using System
.ServiceModel
.Channels
;
41 using System
.Threading
;
44 namespace System
.ServiceModel
.Channels
46 internal class TcpDuplexSessionChannel
: DuplexChannelBase
, IDuplexSessionChannel
48 class TcpDuplexSession
: DuplexSessionBase
50 TcpDuplexSessionChannel owner
;
52 internal TcpDuplexSession (TcpDuplexSessionChannel owner
)
57 public override TimeSpan DefaultCloseTimeout
{
58 get { return owner.DefaultCloseTimeout; }
61 public override void Close (TimeSpan timeout
)
63 owner
.DiscardSession ();
70 TcpBinaryFrameManager frame
;
71 TcpDuplexSession session
; // do not use this directly. Use Session instead.
72 EndpointAddress counterpart_address
;
74 public TcpDuplexSessionChannel (ChannelFactoryBase factory
, TcpChannelInfo info
, EndpointAddress address
, Uri via
)
75 : base (factory
, address
, via
)
77 is_service_side
= false;
80 // make sure to acquire TcpClient here.
81 int explicitPort
= Via
.Port
;
82 client
= new TcpClient (Via
.Host
, explicitPort
<= 0 ? TcpTransportBindingElement
.DefaultPort
: explicitPort
);
83 counterpart_address
= GetEndpointAddressFromTcpClient (client
);
86 public TcpDuplexSessionChannel (ChannelListenerBase listener
, TcpChannelInfo info
, TcpClient client
)
89 is_service_side
= true;
92 counterpart_address
= GetEndpointAddressFromTcpClient (client
);
95 EndpointAddress
GetEndpointAddressFromTcpClient (TcpClient client
)
97 IPEndPoint ep
= (IPEndPoint
) client
.Client
.RemoteEndPoint
;
98 return new EndpointAddress (new Uri ("net.tcp://" + ep
));
101 public MessageEncoder Encoder
{
102 get { return info.MessageEncoder; }
105 public override EndpointAddress RemoteAddress
{
106 get { return base.RemoteAddress ?? counterpart_address; }
109 public override EndpointAddress LocalAddress
{
110 get { return base.LocalAddress ?? counterpart_address; }
113 public IDuplexSession Session
{
116 session
= new TcpDuplexSession (this);
121 internal TcpClient TcpClient
{
122 get { return client; }
125 void DiscardSession ()
127 if (client
.Connected
)
128 frame
.WriteEndRecord ();
132 public override void Send (Message message
)
134 Send (message
, DefaultSendTimeout
);
137 public override void Send (Message message
, TimeSpan timeout
)
139 ThrowIfDisposedOrNotOpen ();
141 if (timeout
<= TimeSpan
.Zero
)
142 throw new ArgumentException (String
.Format ("Timeout value must be positive value. It was {0}", timeout
));
144 if (!is_service_side
) {
145 if (message
.Headers
.To
== null)
146 message
.Headers
.To
= RemoteAddress
.Uri
;
148 if (message
.Headers
.RelatesTo
== null && OperationContext
.Current
.IncomingMessageHeaders
!= null)
149 message
.Headers
.RelatesTo
= OperationContext
.Current
.IncomingMessageHeaders
.MessageId
;
152 client
.SendTimeout
= (int) timeout
.TotalMilliseconds
;
153 frame
.WriteSizedMessage (message
);
156 public override bool TryReceive (TimeSpan timeout
, out Message message
)
158 ThrowIfDisposedOrNotOpen ();
160 // FIXME: there seems to be some pipeline or channel-
161 // recycling issues, which could be mostly workarounded
162 // by delaying input receiver.
163 // This place is not ideal, but it covers both loops in
164 // ChannelDispatcher and DuplexClientRuntimeChannel.
167 if (timeout
<= TimeSpan
.Zero
)
168 throw new ArgumentException (String
.Format ("Timeout value must be positive value. It was {0}", timeout
));
169 client
.ReceiveTimeout
= (int) timeout
.TotalMilliseconds
;
170 message
= frame
.ReadSizedMessage ();
171 // FIXME: this may not be precise, but connection might be reused for some weird socket state transition (that's what happens). So as a workaround, avoid closing the session by sending EndRecord from this channel at OnClose().
172 if (message
== null) {
179 public override bool WaitForMessage (TimeSpan timeout
)
181 ThrowIfDisposedOrNotOpen ();
183 if (client
.Available
> 0)
186 DateTime start
= DateTime
.Now
;
189 if (client
.Available
> 0)
191 } while (DateTime
.Now
- start
< timeout
);
195 // CommunicationObject
198 protected override void OnAbort ()
201 session
.Close (TimeSpan
.FromTicks (0));
207 protected override void OnClose (TimeSpan timeout
)
210 session
.Close (timeout
);
216 protected override void OnOpen (TimeSpan timeout
)
218 if (! is_service_side
) {
219 NetworkStream ns
= client
.GetStream ();
220 frame
= new TcpBinaryFrameManager (TcpBinaryFrameManager
.DuplexMode
, ns
, is_service_side
) {
221 Encoder
= this.Encoder
,
223 frame
.ProcessPreambleInitiator ();
224 frame
.ProcessPreambleAckInitiator ();
227 Stream s
= client
.GetStream ();
229 frame
= new TcpBinaryFrameManager (TcpBinaryFrameManager
.DuplexMode
, s
, is_service_side
) { Encoder = this.Encoder }
;
231 // FIXME: use retrieved record properties in the request processing.
233 frame
.ProcessPreambleRecipient ();
234 frame
.ProcessPreambleAckRecipient ();