Imported Upstream version 3.6.0

Former-commit-id: da6be194a6b1221998fc28233f2503bd61dd9d14
This commit is contained in:
Jo Shields
2014-08-13 10:39:27 +01:00
commit a575963da9
50588 changed files with 8155799 additions and 0 deletions

View File

@ -0,0 +1,370 @@
//
// PeerDuplexChannel.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2009 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.ServiceModel;
using System.ServiceModel.Channels.NetTcp;
using System.ServiceModel.Description;
using System.ServiceModel.PeerResolvers;
using System.ServiceModel.Security;
using System.Threading;
using System.Xml;
namespace System.ServiceModel.Channels
{
// PeerDuplexChannel can be created either from PeerChannelFactory
// (as IOutputChannel) or PeerChannelListener (as IInputChannel).
//
// PeerNode has to be created before Open() (at least at client side).
// On open, it tries to resolve the nodes in the mesh (and do something
// - but what?). Then registers itself to the mesh and refreshes it.
internal class PeerDuplexChannel : DuplexChannelBase
{
enum RemotePeerStatus
{
None,
Connected,
Error,
}
class RemotePeerConnection
{
public RemotePeerConnection (PeerNodeAddress address)
{
Address = address;
}
public PeerNodeAddress Address { get; private set; }
public RemotePeerStatus Status { get; set; }
public LocalPeerReceiver Instance { get; set; }
public IPeerConnectorClient Channel { get; set; }
public ulong NodeId { get; set; }
}
class LocalPeerReceiver : IPeerConnectorContract
{
List<PeerNodeAddress> connections = new List<PeerNodeAddress> ();
AutoResetEvent connect_handle = new AutoResetEvent (false);
public event Action<WelcomeInfo> WelcomeReceived;
public LocalPeerReceiver (PeerDuplexChannel owner)
{
this.owner = owner;
}
PeerDuplexChannel owner;
public void Connect (ConnectInfo connect)
{
if (connect == null)
throw new ArgumentNullException ("connect");
var ch = OperationContext.Current.GetCallbackChannel<IPeerConnectorContract> ();
connections.Add (connect.Address);
// FIXME: check and reject if inappropriate. For example, maximum connection exceeded.
using (var octx = new OperationContextScope ((IContextChannel) ch)) {
OperationContext.Current.OutgoingMessageHeaders.To = new Uri (Constants.WsaAnonymousUri);
if (!owner.peers.Any (p => p.Address.EndpointAddress.Equals (connect.Address.EndpointAddress)))
owner.peers.Add (new RemotePeerConnection (connect.Address));
ch.Welcome (new WelcomeInfo () { NodeId = owner.node.NodeId });
}
}
internal void WaitForConnectResponse (TimeSpan timeout)
{
if (!connect_handle.WaitOne (timeout))
throw new TimeoutException ();
}
public void Disconnect (DisconnectInfo disconnect)
{
if (disconnect == null)
throw new ArgumentNullException ("disconnect");
// Console.WriteLine ("DisconnectInfo.Reason: " + disconnect.Reason);
// FIXME: handle disconnection in practice. So far I see nothing to do.
}
public void Welcome (WelcomeInfo welcome)
{
if (WelcomeReceived != null)
WelcomeReceived (welcome);
connect_handle.Set ();
}
public void Refuse (RefuseInfo refuse)
{
// FIXME: it should not probably actually throw an error.
connect_handle.Set ();
throw new InvalidOperationException ("Peer connection was refused");
}
public void LinkUtility (LinkUtilityInfo linkUtility)
{
throw new NotImplementedException ();
}
public void Ping ()
{
throw new NotImplementedException ();
}
public void SendMessage (Message msg)
{
int idx = msg.Headers.FindHeader ("PeerTo", Constants.NetPeer);
if (idx >= 0)
msg.Headers.To = msg.Headers.GetHeader<Uri> (idx);
// FIXME: anything to do for PeerVia?
owner.EnqueueMessage (msg);
}
}
interface IPeerConnectorClient : IClientChannel, IPeerConnectorContract
{
}
IChannelFactory<IDuplexSessionChannel> client_factory;
PeerTransportBindingElement binding;
PeerResolver resolver;
PeerNode node;
ServiceHost listener_host;
TcpChannelInfo info;
List<RemotePeerConnection> peers = new List<RemotePeerConnection> ();
PeerNodeAddress local_node_address;
public PeerDuplexChannel (IPeerChannelManager factory, EndpointAddress address, Uri via, PeerResolver resolver)
: base ((ChannelFactoryBase) factory, address, via)
{
binding = factory.Source;
this.resolver = factory.Resolver;
info = new TcpChannelInfo (binding, factory.MessageEncoder, null); // FIXME: fill properties correctly.
// It could be opened even with empty list of PeerNodeAddresses.
// So, do not create PeerNode per PeerNodeAddress, but do it with PeerNodeAddress[].
node = new PeerNodeImpl (RemoteAddress.Uri.Host, factory.Source.ListenIPAddress, factory.Source.Port);
}
public PeerDuplexChannel (IPeerChannelManager listener)
: base ((ChannelListenerBase) listener)
{
binding = listener.Source;
this.resolver = listener.Resolver;
info = new TcpChannelInfo (binding, listener.MessageEncoder, null); // FIXME: fill properties correctly.
node = new PeerNodeImpl (((ChannelListenerBase) listener).Uri.Host, listener.Source.ListenIPAddress, listener.Source.Port);
}
public override T GetProperty<T> ()
{
if (typeof (T).IsInstanceOfType (node))
return (T) (object) node;
return base.GetProperty<T> ();
}
// DuplexChannelBase
IPeerConnectorClient CreateInnerClient (RemotePeerConnection conn)
{
conn.Instance = new LocalPeerReceiver (this);
conn.Instance.WelcomeReceived += delegate (WelcomeInfo welcome) {
conn.NodeId = welcome.NodeId;
// FIXME: handle referrals
};
// FIXME: pass more setup parameters
var binding = new NetTcpBinding ();
binding.Security.Mode = SecurityMode.None;
var channel_factory = new DuplexChannelFactory<IPeerConnectorClient> (conn.Instance, binding);
channel_factory.Open ();
var ch = channel_factory.CreateChannel (new EndpointAddress ("net.p2p://" + node.MeshId + "/"), conn.Address.EndpointAddress.Uri);
ch.Closed += delegate {
channel_factory.Close ();
};
return ch;
}
public override void Send (Message message, TimeSpan timeout)
{
ThrowIfDisposedOrNotOpen ();
DateTime start = DateTime.Now;
// FIXME: give max buffer size
var mb = message.CreateBufferedCopy (0x10000);
for (int i = 0; i < peers.Count; i++) {
var pc = peers [i];
message = mb.CreateMessage ();
if (pc.Status == RemotePeerStatus.None) {
pc.Status = RemotePeerStatus.Error; // prepare for cases that it resulted in an error in the middle.
var inner = CreateInnerClient (pc);
pc.Channel = inner;
inner.Open (timeout - (DateTime.Now - start));
inner.OperationTimeout = timeout - (DateTime.Now - start);
inner.Connect (new ConnectInfo () { Address = local_node_address, NodeId = (uint) node.NodeId });
pc.Instance.WaitForConnectResponse (timeout - (DateTime.Now - start));
pc.Status = RemotePeerStatus.Connected;
}
pc.Channel.OperationTimeout = timeout - (DateTime.Now - start);
// see [MC-PRCH] 3.2.4.1
if (message.Headers.MessageId == null)
message.Headers.MessageId = new UniqueId ();
message.Headers.Add (MessageHeader.CreateHeader ("PeerTo", Constants.NetPeer, RemoteAddress.Uri));
message.Headers.Add (MessageHeader.CreateHeader ("PeerVia", Constants.NetPeer, RemoteAddress.Uri));
message.Headers.Add (MessageHeader.CreateHeader ("FloodMessage", Constants.NetPeer, "PeerFlooder"));
pc.Channel.SendMessage (message);
}
}
internal void EnqueueMessage (Message message)
{
queue.Enqueue (message);
receive_handle.Set ();
}
Queue<Message> queue = new Queue<Message> ();
AutoResetEvent receive_handle = new AutoResetEvent (false);
public override bool TryReceive (TimeSpan timeout, out Message message)
{
ThrowIfDisposedOrNotOpen ();
DateTime start = DateTime.Now;
if (queue.Count > 0 || receive_handle.WaitOne (timeout)) {
message = queue.Dequeue ();
return message == null;
} else {
message = null;
return false;
}
}
public override bool WaitForMessage (TimeSpan timeout)
{
ThrowIfDisposedOrNotOpen ();
throw new NotImplementedException ();
}
// CommunicationObject
protected override void OnAbort ()
{
if (client_factory != null) {
client_factory.Abort ();
client_factory = null;
}
OnClose (TimeSpan.Zero);
}
protected override void OnClose (TimeSpan timeout)
{
DateTime start = DateTime.Now;
if (client_factory != null)
client_factory.Close (timeout - (DateTime.Now - start));
peers.Clear ();
resolver.Unregister (node.RegisteredId, timeout - (DateTime.Now - start));
node.SetOffline ();
if (listener_host != null)
listener_host.Close (timeout - (DateTime.Now - start));
node.RegisteredId = null;
}
protected override void OnOpen (TimeSpan timeout)
{
DateTime start = DateTime.Now;
// FIXME: supply maxAddresses
foreach (var a in resolver.Resolve (node.MeshId, 3, timeout))
peers.Add (new RemotePeerConnection (a));
// FIXME: pass more configuration
var binding = new NetTcpBinding ();
binding.Security.Mode = SecurityMode.None;
int port = 0;
var rnd = new Random ();
for (int i = 0; i < 1000; i++) {
if (DateTime.Now - start > timeout)
throw new TimeoutException ();
try {
port = rnd.Next (50000, 51000);
var t = new TcpListener (port);
t.Start ();
t.Stop ();
break;
} catch (SocketException) {
continue;
}
}
string name = Dns.GetHostName ();
var uri = new Uri ("net.tcp://" + name + ":" + port + "/PeerChannelEndpoints/" + Guid.NewGuid ());
var peer_receiver = new LocalPeerReceiver (this);
listener_host = new ServiceHost (peer_receiver);
var sba = listener_host.Description.Behaviors.Find<ServiceBehaviorAttribute> ();
sba.InstanceContextMode = InstanceContextMode.Single;
sba.IncludeExceptionDetailInFaults = true;
var se = listener_host.AddServiceEndpoint (typeof (IPeerConnectorContract).FullName, binding, "net.p2p://" + node.MeshId + "/");
se.ListenUri = uri;
// FIXME: remove debugging code
listener_host.UnknownMessageReceived += delegate (object obj, UnknownMessageReceivedEventArgs earg) { Console.WriteLine ("%%%%% UNKOWN MESSAGE " + earg.Message); };
listener_host.Open (timeout - (DateTime.Now - start));
var nid = (ulong) new Random ().Next (0, int.MaxValue);
var ea = new EndpointAddress (uri);
var pna = new PeerNodeAddress (ea, new ReadOnlyCollection<IPAddress> (Dns.GetHostEntry (name).AddressList));
local_node_address = pna;
node.RegisteredId = resolver.Register (node.MeshId, pna, timeout - (DateTime.Now - start));
node.NodeId = nid;
// Add itself to the local list as well.
// FIXME: it might become unnecessary once it implemented new node registration from peer resolver service.
peers.Add (new RemotePeerConnection (pna));
node.SetOnline ();
}
}
}

View File

@ -0,0 +1,79 @@
//
// TcpChannelFactory.cs
//
// Author:
// Marcos Cobena (marcoscobena@gmail.com)
//
// Copyright 2007 Marcos Cobena (http://www.youcannoteatbits.org/)
//
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Security;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.ServiceModel.Security;
using System.Text;
using System.Xml;
namespace System.ServiceModel.Channels.NetTcp
{
internal class TcpChannelInfo
{
public TcpChannelInfo (TransportBindingElement element, MessageEncoder encoder, XmlDictionaryReaderQuotas readerQuotas)
{
this.BindingElement = element;
this.MessageEncoder = encoder;
this.ReaderQuotas = readerQuotas ?? new XmlDictionaryReaderQuotas ();
}
public TransportBindingElement BindingElement { get; private set; }
public MessageEncoder MessageEncoder { get; private set; }
public XmlDictionaryReaderQuotas ReaderQuotas { get; private set; }
}
internal class TcpChannelFactory<TChannel> : TransportChannelFactoryBase<TChannel>
{
TcpChannelInfo info;
public TcpChannelFactory (TcpTransportBindingElement source, BindingContext ctx)
: base (source, ctx)
{
XmlDictionaryReaderQuotas quotas = null;
foreach (BindingElement be in ctx.Binding.Elements) {
MessageEncodingBindingElement mbe = be as MessageEncodingBindingElement;
if (mbe != null) {
MessageEncoder = CreateEncoder<TChannel> (mbe);
quotas = mbe.GetProperty<XmlDictionaryReaderQuotas> (ctx);
break;
}
}
if (MessageEncoder == null)
MessageEncoder = new BinaryMessageEncoder ();
info = new TcpChannelInfo (source, MessageEncoder, quotas);
}
protected override TChannel OnCreateChannel (
EndpointAddress address, Uri via)
{
ThrowIfDisposedOrNotOpen ();
var targetUri = via ?? address.Uri;
if (info.BindingElement.Scheme != targetUri.Scheme)
throw new ArgumentException (String.Format ("Argument EndpointAddress has unsupported URI scheme: {0}", targetUri.Scheme));
Type t = typeof (TChannel);
if (t == typeof (IDuplexSessionChannel))
return (TChannel) (object) new TcpDuplexSessionChannel (this, info, address, targetUri);
if (t == typeof (IRequestChannel))
return (TChannel) (object) new TcpRequestChannel (this, info, address, targetUri);
throw new InvalidOperationException (String.Format ("Channel type {0} is not supported.", typeof (TChannel).Name));
}
}
}

View File

@ -0,0 +1,218 @@
//
// TcpChannelListener.cs
//
// Author:
// Marcos Cobena (marcoscobena@gmail.com)
// Atsushi Enomoto (atsushi@ximian.com)
//
// Copyright 2007 Marcos Cobena (http://www.youcannoteatbits.org/)
// Copyright 2009-2010 Novell, Inc (http://www.novell.com/)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.ServiceModel.Description;
using System.Text;
using System.Threading;
using System.Xml;
namespace System.ServiceModel.Channels.NetTcp
{
internal class TcpChannelListener<TChannel> : InternalChannelListenerBase<TChannel>
where TChannel : class, IChannel
{
BindingContext context;
TcpChannelInfo info;
TcpListener tcp_listener;
public TcpChannelListener (TcpTransportBindingElement source, BindingContext context)
: base (context)
{
XmlDictionaryReaderQuotas quotas = null;
foreach (BindingElement be in context.Binding.Elements) {
MessageEncodingBindingElement mbe = be as MessageEncodingBindingElement;
if (mbe != null) {
MessageEncoder = CreateEncoder<TChannel> (mbe);
quotas = mbe.GetProperty<XmlDictionaryReaderQuotas> (context);
break;
}
}
if (MessageEncoder == null)
MessageEncoder = new BinaryMessageEncoder ();
info = new TcpChannelInfo (source, MessageEncoder, quotas);
}
SynchronizedCollection<ManualResetEvent> accept_handles = new SynchronizedCollection<ManualResetEvent> ();
Queue<TcpClient> accepted_clients = new Queue<TcpClient> ();
SynchronizedCollection<TChannel> accepted_channels = new SynchronizedCollection<TChannel> ();
protected override TChannel OnAcceptChannel (TimeSpan timeout)
{
DateTime start = DateTime.Now;
// Close channels that are incorrectly kept open first.
var l = new List<TcpDuplexSessionChannel> ();
foreach (var tch in accepted_channels) {
var dch = tch as TcpDuplexSessionChannel;
if (dch != null && dch.TcpClient != null && !dch.TcpClient.Connected)
l.Add (dch);
}
foreach (var dch in l)
dch.Close (timeout - (DateTime.Now - start));
TcpClient client = AcceptTcpClient (timeout - (DateTime.Now - start));
if (client == null)
return null; // onclose
TChannel ch;
if (typeof (TChannel) == typeof (IDuplexSessionChannel))
ch = (TChannel) (object) new TcpDuplexSessionChannel (this, info, client);
else if (typeof (TChannel) == typeof (IReplyChannel))
ch = (TChannel) (object) new TcpReplyChannel (this, info, client);
else
throw new InvalidOperationException (String.Format ("Channel type {0} is not supported.", typeof (TChannel).Name));
((ChannelBase) (object) ch).Closed += delegate {
accepted_channels.Remove (ch);
};
accepted_channels.Add (ch);
return ch;
}
// TcpReplyChannel requires refreshed connection after each request processing.
internal TcpClient AcceptTcpClient (TimeSpan timeout)
{
DateTime start = DateTime.Now;
TcpClient client = accepted_clients.Count == 0 ? null : accepted_clients.Dequeue ();
if (client == null) {
var wait = new ManualResetEvent (false);
accept_handles.Add (wait);
if (!wait.WaitOne (timeout)) {
accept_handles.Remove (wait);
return null;
}
accept_handles.Remove (wait);
// recurse with new timeout, or return null if it's either being closed or timed out.
timeout -= (DateTime.Now - start);
return State == CommunicationState.Opened && timeout > TimeSpan.Zero ? AcceptTcpClient (timeout) : null;
}
// There might be bettwe way to exclude those TCP clients though ...
foreach (var ch in accepted_channels) {
var dch = ch as TcpDuplexSessionChannel;
if (dch == null || dch.TcpClient == null && !dch.TcpClient.Connected)
continue;
if (((IPEndPoint) dch.TcpClient.Client.RemoteEndPoint).Equals (client.Client.RemoteEndPoint))
// ... then it should be handled in another BeginTryReceive/EndTryReceive loop in ChannelDispatcher.
return AcceptTcpClient (timeout - (DateTime.Now - start));
}
return client;
}
[MonoTODO]
protected override bool OnWaitForChannel (TimeSpan timeout)
{
throw new NotImplementedException ();
}
// CommunicationObject
protected override void OnAbort ()
{
if (State == CommunicationState.Closed)
return;
ProcessClose (TimeSpan.Zero);
}
protected override void OnClose (TimeSpan timeout)
{
if (State == CommunicationState.Closed)
return;
ProcessClose (timeout);
}
void ProcessClose (TimeSpan timeout)
{
if (tcp_listener == null)
throw new InvalidOperationException ("Current state is " + State);
//tcp_listener.Client.Close (Math.Max (50, (int) timeout.TotalMilliseconds));
tcp_listener.Stop ();
var l = new List<ManualResetEvent> (accept_handles);
foreach (var wait in l) // those handles will disappear from accepted_handles
wait.Set ();
tcp_listener = null;
}
protected override void OnOpen (TimeSpan timeout)
{
IPAddress address;
if (string.Equals (Uri.Host, "localhost", StringComparison.InvariantCultureIgnoreCase))
address = IPAddress.Any;
else {
IPHostEntry entry = Dns.GetHostEntry (Uri.Host);
if (entry.AddressList.Length == 0)
throw new ArgumentException (String.Format ("Invalid listen URI: {0}", Uri));
address = entry.AddressList [0];
}
int explicitPort = Uri.Port;
tcp_listener = new TcpListener (address, explicitPort <= 0 ? TcpTransportBindingElement.DefaultPort : explicitPort);
tcp_listener.Start ();
tcp_listener.BeginAcceptTcpClient (TcpListenerAcceptedClient, tcp_listener);
}
void TcpListenerAcceptedClient (IAsyncResult result)
{
var listener = (TcpListener) result.AsyncState;
try {
var client = listener.EndAcceptTcpClient (result);
if (client != null) {
accepted_clients.Enqueue (client);
if (accept_handles.Count > 0)
accept_handles [0].Set ();
}
} catch {
/* If an accept fails, just ignore it. Maybe the remote peer disconnected already */
} finally {
if (State == CommunicationState.Opened) {
try {
listener.BeginAcceptTcpClient (TcpListenerAcceptedClient, listener);
} catch {
/* If this fails, we must have disposed the listener */
}
}
}
}
}
}

View File

@ -0,0 +1,243 @@
//
// TcpDuplexSessionChannel.cs
//
// Author:
// Marcos Cobena (marcoscobena@gmail.com)
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright 2007 Marcos Cobena (http://www.youcannoteatbits.org/)
//
// Copyright (C) 2009 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.ServiceModel.Channels;
using System.Text;
using System.Threading;
using System.Xml;
namespace System.ServiceModel.Channels.NetTcp
{
internal class TcpDuplexSessionChannel : DuplexChannelBase, IDuplexSessionChannel
{
class TcpDuplexSession : DuplexSessionBase
{
TcpDuplexSessionChannel owner;
internal TcpDuplexSession (TcpDuplexSessionChannel owner)
{
this.owner = owner;
}
public override TimeSpan DefaultCloseTimeout {
get { return owner.DefaultCloseTimeout; }
}
public override void Close (TimeSpan timeout)
{
owner.DiscardSession ();
}
}
TcpChannelInfo info;
TcpClient client;
bool is_service_side;
TcpBinaryFrameManager frame;
TcpDuplexSession session; // do not use this directly. Use Session instead.
EndpointAddress counterpart_address;
public TcpDuplexSessionChannel (ChannelFactoryBase factory, TcpChannelInfo info, EndpointAddress address, Uri via)
: base (factory, address, via)
{
is_service_side = false;
this.info = info;
// make sure to acquire TcpClient here.
int explicitPort = Via.Port;
client = new TcpClient (Via.Host, explicitPort <= 0 ? TcpTransportBindingElement.DefaultPort : explicitPort);
counterpart_address = GetEndpointAddressFromTcpClient (client);
}
public TcpDuplexSessionChannel (ChannelListenerBase listener, TcpChannelInfo info, TcpClient client)
: base (listener)
{
is_service_side = true;
this.client = client;
this.info = info;
counterpart_address = GetEndpointAddressFromTcpClient (client);
}
EndpointAddress GetEndpointAddressFromTcpClient (TcpClient client)
{
IPEndPoint ep = (IPEndPoint) client.Client.RemoteEndPoint;
return new EndpointAddress (new Uri ("net.tcp://" + ep));
}
public MessageEncoder Encoder {
get { return info.MessageEncoder; }
}
public override EndpointAddress RemoteAddress {
get { return base.RemoteAddress ?? counterpart_address; }
}
public override EndpointAddress LocalAddress {
get { return base.LocalAddress ?? counterpart_address; }
}
public IDuplexSession Session {
get {
if (session == null)
session = new TcpDuplexSession (this);
return session;
}
}
internal TcpClient TcpClient {
get { return client; }
}
void DiscardSession ()
{
if (client.Connected)
frame.WriteEndRecord ();
session = null;
}
public override void Send (Message message)
{
Send (message, DefaultSendTimeout);
}
public override void Send (Message message, TimeSpan timeout)
{
ThrowIfDisposedOrNotOpen ();
if (timeout <= TimeSpan.Zero)
throw new ArgumentException (String.Format ("Timeout value must be positive value. It was {0}", timeout));
if (!is_service_side) {
if (message.Headers.To == null)
message.Headers.To = RemoteAddress.Uri;
}
client.SendTimeout = (int) timeout.TotalMilliseconds;
Logger.LogMessage (MessageLogSourceKind.TransportSend, ref message, info.BindingElement.MaxReceivedMessageSize);
frame.WriteSizedMessage (message);
}
public override bool TryReceive (TimeSpan timeout, out Message message)
{
ThrowIfDisposedOrNotOpen ();
// FIXME: there seems to be some pipeline or channel-
// recycling issues, which could be mostly workarounded
// by delaying input receiver.
// This place is not ideal, but it covers both loops in
// ChannelDispatcher and DuplexClientRuntimeChannel.
Thread.Sleep (50);
if (timeout <= TimeSpan.Zero)
throw new ArgumentException (String.Format ("Timeout value must be positive value. It was {0}", timeout));
client.ReceiveTimeout = (int) timeout.TotalMilliseconds;
message = frame.ReadSizedMessage ();
// 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().
if (message == null) {
session = null;
return false;
}
Logger.LogMessage (MessageLogSourceKind.TransportReceive, ref message, info.BindingElement.MaxReceivedMessageSize);
return true;
}
public override bool WaitForMessage (TimeSpan timeout)
{
ThrowIfDisposedOrNotOpen ();
if (client.Available > 0)
return true;
DateTime start = DateTime.Now;
do {
Thread.Sleep (50);
if (client.Available > 0)
return true;
} while (DateTime.Now - start < timeout);
return false;
}
// CommunicationObject
[MonoTODO]
protected override void OnAbort ()
{
if (session != null)
session.Close (TimeSpan.FromTicks (0));
if (client != null)
client.Close ();
}
protected override void OnClose (TimeSpan timeout)
{
if (session != null)
session.Close (timeout);
if (client != null)
client.Close ();
}
protected override void OnOpen (TimeSpan timeout)
{
if (! is_service_side) {
NetworkStream ns = client.GetStream ();
frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.DuplexMode, ns, is_service_side) {
Encoder = this.Encoder,
Via = this.Via };
frame.ProcessPreambleInitiator ();
frame.ProcessPreambleAckInitiator ();
session = new TcpDuplexSession (this); // make sure to shutdown the session once it has initiated one.
} else {
// server side
Stream s = client.GetStream ();
frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.DuplexMode, s, is_service_side) { Encoder = this.Encoder };
// FIXME: use retrieved record properties in the request processing.
frame.ProcessPreambleRecipient ();
frame.ProcessPreambleAckRecipient ();
}
}
}
}

View File

@ -0,0 +1,150 @@
//
// TcpReplyChannel.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2009 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.ServiceModel;
using System.Text;
using System.Threading;
namespace System.ServiceModel.Channels.NetTcp
{
internal class TcpReplyChannel : InternalReplyChannelBase
{
TcpClient client;
TcpChannelInfo info;
TcpBinaryFrameManager frame;
public TcpReplyChannel (ChannelListenerBase listener, TcpChannelInfo info, TcpClient client)
: base (listener)
{
this.client = client;
this.info = info;
}
public MessageEncoder Encoder {
get { return info.MessageEncoder; }
}
public override RequestContext ReceiveRequest (TimeSpan timeout)
{
if (timeout <= TimeSpan.Zero)
throw new ArgumentException (String.Format ("Timeout value must be positive value. It was {0}", timeout));
DateTime start = DateTime.Now;
// FIXME: use timeout
if (client == null)
client = ((TcpChannelListener<IReplyChannel>) Manager).AcceptTcpClient (timeout);
NetworkStream ns = client.GetStream ();
frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.SingletonUnsizedMode, ns, true) { Encoder = this.Encoder };
// FIXME: use timeout
if (!frame.ProcessPreambleRecipient ())
return null;
frame.ProcessPreambleAckRecipient ();
var msg = frame.ReadUnsizedMessage (timeout);
Logger.LogMessage (MessageLogSourceKind.TransportReceive, ref msg, info.BindingElement.MaxReceivedMessageSize);
// LAMESPEC: it contradicts the protocol explanation at section 3.1.1.1.1 in [MC-NMF].
// Moving ReadEndRecord() after context's WriteUnsizedMessage() causes TCP connection blocking.
frame.ReadEndRecord ();
return new TcpRequestContext (this, msg);
}
class TcpRequestContext : InternalRequestContext
{
public TcpRequestContext (TcpReplyChannel owner, Message request)
: base (owner.Manager)
{
this.owner = owner;
this.request = request;
}
TcpReplyChannel owner;
Message request;
public override Message RequestMessage {
get { return request; }
}
public override void Abort ()
{
Close (TimeSpan.Zero);
}
public override void Close (TimeSpan timeout)
{
}
public override void Reply (Message message, TimeSpan timeout)
{
Logger.LogMessage (MessageLogSourceKind.TransportSend, ref message, owner.info.BindingElement.MaxReceivedMessageSize);
DateTime start = DateTime.Now;
owner.frame.WriteUnsizedMessage (message, timeout);
// FIXME: consider timeout here too.
owner.frame.WriteEndRecord ();
}
}
public override bool TryReceiveRequest (TimeSpan timeout, out RequestContext context)
{
try {
DateTime start = DateTime.Now;
context = ReceiveRequest (timeout);
return context != null;
} catch (Exception ex) {
// FIXME: log it?
// Console.WriteLine (ex);
context = null;
return false;
}
}
public override bool WaitForRequest (TimeSpan timeout)
{
throw new NotImplementedException ();
}
protected override void OnClose (TimeSpan timeout)
{
client.Close ();
client = null;
base.OnClose (timeout);
}
protected override void OnOpen (TimeSpan timeout)
{
}
}
}

View File

@ -0,0 +1,112 @@
//
// TcpRequestChannel.cs
//
// Author:
// Atsushi Enomoto <atsushi@ximian.com>
//
// Copyright (C) 2009 Novell, Inc. http://www.novell.com
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.ServiceModel;
using System.ServiceModel.Description;
using System.Threading;
using System.Xml;
namespace System.ServiceModel.Channels.NetTcp
{
internal class TcpRequestChannel : RequestChannelBase
{
TcpChannelInfo info;
TcpClient client;
TcpBinaryFrameManager frame;
public TcpRequestChannel (ChannelFactoryBase factory, TcpChannelInfo info, EndpointAddress address, Uri via)
: base (factory, address, via)
{
this.info = info;
}
public MessageEncoder Encoder {
get { return info.MessageEncoder; }
}
protected override void OnAbort ()
{
OnClose (TimeSpan.Zero);
}
protected override void OnClose (TimeSpan timeout)
{
if (client != null)
client.Close ();
}
protected override void OnOpen (TimeSpan timeout)
{
CreateClient (timeout);
}
void CreateClient (TimeSpan timeout)
{
int explicitPort = Via.Port;
client = new TcpClient (Via.Host, explicitPort <= 0 ? TcpTransportBindingElement.DefaultPort : explicitPort);
NetworkStream ns = client.GetStream ();
frame = new TcpBinaryFrameManager (TcpBinaryFrameManager.SingletonUnsizedMode, ns, false) {
Encoder = this.Encoder,
Via = this.Via };
}
public override Message Request (Message input, TimeSpan timeout)
{
DateTime start = DateTime.Now;
// FIXME: use timeouts.
frame.ProcessPreambleInitiator ();
frame.ProcessPreambleAckInitiator ();
if (input.Headers.To == null)
input.Headers.To = RemoteAddress.Uri;
if (input.Headers.MessageId == null)
input.Headers.MessageId = new UniqueId ();
Logger.LogMessage (MessageLogSourceKind.TransportSend, ref input, int.MaxValue); // It is not a receive buffer
frame.WriteUnsizedMessage (input, timeout - (DateTime.Now - start));
// LAMESPEC: it contradicts the protocol described at section 3.1.1.1.1 in [MC-NMF].
// Moving this WriteEndRecord() after ReadUnsizedMessage() causes TCP connection blocking.
frame.WriteEndRecord ();
var ret = frame.ReadUnsizedMessage (timeout - (DateTime.Now - start));
Logger.LogMessage (MessageLogSourceKind.TransportReceive, ref ret, info.BindingElement.MaxReceivedMessageSize);
frame.ReadEndRecord (); // both
return ret;
}
}
}