1 // 2 // TcpDuplexSessionChannel.cs 3 // 4 // Author: 5 // Marcos Cobena (marcoscobena@gmail.com) 6 // Atsushi Enomoto <atsushi@ximian.com> 7 // 8 // Copyright 2007 Marcos Cobena (http://www.youcannoteatbits.org/) 9 // 10 // Copyright (C) 2009 Novell, Inc (http://www.novell.com) 11 // 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: 19 // 20 // The above copyright notice and this permission notice shall be 21 // included in all copies or substantial portions of the Software. 22 // 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. 30 // 31 32 using System; 33 using System.Collections.Generic; 34 using System.IO; 35 using System.Net; 36 using System.Net.Sockets; 37 using System.Runtime.Serialization; 38 using System.Runtime.Serialization.Formatters.Binary; 39 using System.ServiceModel.Channels; 40 using System.Text; 41 using System.Threading; 42 using System.Xml; 43 44 namespace System.ServiceModel.Channels.NetTcp 45 { 46 internal class TcpDuplexSessionChannel : DuplexChannelBase, IDuplexSessionChannel 47 { 48 class TcpDuplexSession : DuplexSessionBase 49 { 50 TcpDuplexSessionChannel owner; 51 TcpDuplexSession(TcpDuplexSessionChannel owner)52 internal TcpDuplexSession (TcpDuplexSessionChannel owner) 53 { 54 this.owner = owner; 55 } 56 57 public override TimeSpan DefaultCloseTimeout { 58 get { return owner.DefaultCloseTimeout; } 59 } 60 Close(TimeSpan timeout)61 public override void Close (TimeSpan timeout) 62 { 63 owner.DiscardSession (); 64 } 65 } 66 67 TcpChannelInfo info; 68 TcpClient client; 69 bool is_service_side; 70 TcpBinaryFrameManager frame; 71 TcpDuplexSession session; // do not use this directly. Use Session instead. 72 EndpointAddress counterpart_address; 73 TcpDuplexSessionChannel(ChannelFactoryBase factory, TcpChannelInfo info, EndpointAddress address, Uri via)74 public TcpDuplexSessionChannel (ChannelFactoryBase factory, TcpChannelInfo info, EndpointAddress address, Uri via) 75 : base (factory, address, via) 76 { 77 is_service_side = false; 78 this.info = info; 79 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); 84 } 85 TcpDuplexSessionChannel(ChannelListenerBase listener, TcpChannelInfo info, TcpClient client)86 public TcpDuplexSessionChannel (ChannelListenerBase listener, TcpChannelInfo info, TcpClient client) 87 : base (listener) 88 { 89 is_service_side = true; 90 this.client = client; 91 this.info = info; 92 counterpart_address = GetEndpointAddressFromTcpClient (client); 93 } 94 GetEndpointAddressFromTcpClient(TcpClient client)95 EndpointAddress GetEndpointAddressFromTcpClient (TcpClient client) 96 { 97 IPEndPoint ep = (IPEndPoint) client.Client.RemoteEndPoint; 98 return new EndpointAddress (new Uri ("net.tcp://" + ep)); 99 } 100 101 public MessageEncoder Encoder { 102 get { return info.MessageEncoder; } 103 } 104 105 public override EndpointAddress RemoteAddress { 106 get { return base.RemoteAddress ?? counterpart_address; } 107 } 108 109 public override EndpointAddress LocalAddress { 110 get { return base.LocalAddress ?? counterpart_address; } 111 } 112 113 public IDuplexSession Session { 114 get { 115 if (session == null) 116 session = new TcpDuplexSession (this); 117 return session; 118 } 119 } 120 121 internal TcpClient TcpClient { 122 get { return client; } 123 } 124 DiscardSession()125 void DiscardSession () 126 { 127 if (client.Connected) 128 frame.WriteEndRecord (); 129 session = null; 130 } 131 Send(Message message)132 public override void Send (Message message) 133 { 134 Send (message, DefaultSendTimeout); 135 } 136 Send(Message message, TimeSpan timeout)137 public override void Send (Message message, TimeSpan timeout) 138 { 139 ThrowIfDisposedOrNotOpen (); 140 141 if (timeout <= TimeSpan.Zero) 142 throw new ArgumentException (String.Format ("Timeout value must be positive value. It was {0}", timeout)); 143 144 if (!is_service_side) { 145 if (message.Headers.To == null) 146 message.Headers.To = RemoteAddress.Uri; 147 } 148 149 client.SendTimeout = (int) timeout.TotalMilliseconds; 150 151 Logger.LogMessage (MessageLogSourceKind.TransportSend, ref message, info.BindingElement.MaxReceivedMessageSize); 152 153 frame.WriteSizedMessage (message); 154 } 155 TryReceive(TimeSpan timeout, out Message message)156 public override bool TryReceive (TimeSpan timeout, out Message message) 157 { 158 ThrowIfDisposedOrNotOpen (); 159 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. 165 Thread.Sleep (50); 166 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 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) { 174 session = null; 175 return false; 176 } 177 178 Logger.LogMessage (MessageLogSourceKind.TransportReceive, ref message, info.BindingElement.MaxReceivedMessageSize); 179 180 return true; 181 } 182 WaitForMessage(TimeSpan timeout)183 public override bool WaitForMessage (TimeSpan timeout) 184 { 185 ThrowIfDisposedOrNotOpen (); 186 187 if (client.Available > 0) 188 return true; 189 190 DateTime start = DateTime.UtcNow; 191 do { 192 Thread.Sleep (50); 193 if (client.Available > 0) 194 return true; 195 } while (DateTime.UtcNow - start < timeout); 196 return false; 197 } 198 199 // CommunicationObject 200 201 [MonoTODO] OnAbort()202 protected override void OnAbort () 203 { 204 if (session != null) 205 session.Close (TimeSpan.FromTicks (0)); 206 207 if (client != null) 208 client.Close (); 209 } 210 OnClose(TimeSpan timeout)211 protected override void OnClose (TimeSpan timeout) 212 { 213 if (session != null) 214 session.Close (timeout); 215 216 if (client != null) 217 client.Close (); 218 } 219 OnOpen(TimeSpan timeout)220 protected override void OnOpen (TimeSpan timeout) 221 { 222 if (! is_service_side) { 223 NetworkStream ns = client.GetStream (); 224 frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.DuplexMode, ns, is_service_side) { 225 Encoder = this.Encoder, 226 Via = this.Via }; 227 frame.ProcessPreambleInitiator (); 228 frame.ProcessPreambleAckInitiator (); 229 session = new TcpDuplexSession (this); // make sure to shutdown the session once it has initiated one. 230 } else { 231 // server side 232 Stream s = client.GetStream (); 233 234 frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.DuplexMode, s, is_service_side) { Encoder = this.Encoder }; 235 236 // FIXME: use retrieved record properties in the request processing. 237 238 frame.ProcessPreambleRecipient (); 239 frame.ProcessPreambleAckRecipient (); 240 } 241 } 242 } 243 } 244