45f9def56ec5ecc4354012ed9f0916d92334a9db
[mono.git] / mcs / class / System.ServiceModel / System.ServiceModel.Channels.NetTcp / TcpDuplexSessionChannel.cs
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
52                         internal TcpDuplexSession (TcpDuplexSessionChannel owner)
53                         {
54                                 this.owner = owner;
55                         }
56
57                         public override TimeSpan DefaultCloseTimeout {
58                                 get { return owner.DefaultCloseTimeout; }
59                         }
60
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                 
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                 
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
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
125                 void DiscardSession ()
126                 {
127                         if (client.Connected)
128                                 frame.WriteEndRecord ();
129                         session = null;
130                 }
131
132                 public override void Send (Message message)
133                 {
134                         Send (message, DefaultSendTimeout);
135                 }
136                 
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                 
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                 
183                 public override bool WaitForMessage (TimeSpan timeout)
184                 {
185                         ThrowIfDisposedOrNotOpen ();
186
187                         if (client.Available > 0)
188                                 return true;
189
190                         DateTime start = DateTime.Now;
191                         do {
192                                 Thread.Sleep (50);
193                                 if (client.Available > 0)
194                                         return true;
195                         } while (DateTime.Now - start < timeout);
196                         return false;
197                 }
198                 
199                 // CommunicationObject
200                 
201                 [MonoTODO]
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
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                 
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 }