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