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.NetTcp
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;
149 client.SendTimeout = (int) timeout.TotalMilliseconds;
151 Logger.LogMessage (MessageLogSourceKind.TransportSend, ref message, info.BindingElement.MaxReceivedMessageSize);
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;
171 message = frame.ReadSizedMessage ();
172 // 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().
173 if (message == null) {
178 Logger.LogMessage (MessageLogSourceKind.TransportReceive, ref message, info.BindingElement.MaxReceivedMessageSize);
183 public override bool WaitForMessage (TimeSpan timeout)
185 ThrowIfDisposedOrNotOpen ();
187 if (client.Available > 0)
190 DateTime start = DateTime.Now;
193 if (client.Available > 0)
195 } while (DateTime.Now - start < timeout);
199 // CommunicationObject
202 protected override void OnAbort ()
205 session.Close (TimeSpan.FromTicks (0));
211 protected override void OnClose (TimeSpan timeout)
214 session.Close (timeout);
220 protected override void OnOpen (TimeSpan timeout)
222 if (! is_service_side) {
223 NetworkStream ns = client.GetStream ();
224 frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.DuplexMode, ns, is_service_side) {
225 Encoder = this.Encoder,
227 frame.ProcessPreambleInitiator ();
228 frame.ProcessPreambleAckInitiator ();
229 session = new TcpDuplexSession (this); // make sure to shutdown the session once it has initiated one.
232 Stream s = client.GetStream ();
234 frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.DuplexMode, s, is_service_side) { Encoder = this.Encoder };
236 // FIXME: use retrieved record properties in the request processing.
238 frame.ProcessPreambleRecipient ();
239 frame.ProcessPreambleAckRecipient ();