I'm trying to implement some cross process communication that is between multiple computers and one server on the same network. What I'm trying right now is to use WCF with NetTcpBinding, hosted within my application which works on the same machine, but when I try to connect from another machine it throws a SSPI security error.
I've found lots of examples of doing this cross-machine, but all involve an app.config file which I would REALLY like to avoid. I want to be able to embed this functionality in a DLL that has not other dependencies (i.e. config files) for which I can just pass into it all of the necessary server addresses, etc and it will work. Is there anyway to setup this security (via the endpoints, etc) purely in code?
I'm testing this all out with the code below:
SERVER:
using System;
using System.ServiceModel;
namespace WCFServer
{
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
public class StringReverser : IStringReverser
{
public string ReverseString(string value)
{
char[] retVal = value.ToCharArray();
int idx = 0;
for (int i = value.Length - 1; i >= 0; i--)
retVal[idx++] = value[i];
string result = new string(retVal);
Console.WriteLine(value + " -> " + result);
return result;
}
}
class Program
{
static void Main(string[] args)
{
var uri = "net.tcp://" + System.Net.Dns.GetHostName() + ":9985";
Console.WriteLine("Opening connection on: " + uri);
using (ServiceHost host = new ServiceHost(
typeof(StringReverser),
new Uri[]{
new Uri("net.tcp://" + System.Net.Dns.GetHostName() + ":9985")
}))
{
host.AddServiceEndpoint(typeof(IStringReverser),
new NetTcpBinding(),
"TcpReverse");
host.Open();
Console.WriteLine("Service is available. " +
"Press <ENTER> to exit.");
Console.ReadLine();
host.Close();
}
}
}
}
CLIENT:
using System;
using System.ServiceModel;
using System.ServiceModel.Channels;
namespace WCFClient
{
[ServiceContract]
public interface IStringReverser
{
[OperationContract]
string ReverseString(string value);
}
class Program
{
static void Main(string[] args)
{
var ep = "net.tcp://SERVER:9985/TcpReverse";
ChannelFactory<IStringReverser> pipeFactory =
new ChannelFactory<IStringReverser>(
new NetTcpBinding(),
new EndpointAddress(
ep));
IStringReverser pipeProxy =
pipeFactory.CreateChannel();
Console.WriteLine("Connected to: " + ep);
while (true)
{
string str = Console.ReadLine();
Console.WriteLine("pipe: " +
pipeProxy.ReverseString(str));
}
}
}
}
Security is normally configured on the binding. You are using NetTcpBinding with its defaults which means that Transport security is enabled.
On both, server and client, you should assign the NetTcpBinding instance to a local variable so that you can change the security (and possibly other) settings, and then use that variable when calling AddServiceEndpoint or when creating the ChannelFactory.
Sample:
var binding = new NetTcpBinding();
// disable security:
binding.Security.Mode = SecurityMode.None;
This is probably an issue with the SPN that your service is running under. It's most likely a machine account instead of a domain account. There's more information in this thread.
UPDATE: There's information in there about setting the SPN programmatically, but it's buried a few clicks in... here's a direct link (see the last section of the page).
Related
I try to create application using Akka.NET.
The main goal is to make a server that can handle many client connections and requests at the same time. I chose Akka.NET for this. I have a cluster, which now consists only 1 node.
I also have cluster clients (ClusterClient) that are simultaneously starting to connect to the server. The logic of the client is simple: it connects to the server and subscribe an actor there. There are no problems with publish yet, customers are getting everything, of course, if the connection is stable. Somewhere near ~4000-5000 client connections, reconnects begin and the connection is lost accordingly. I have tried to add a second node to the cluster and make 3000 connections for each node, but this was unsuccessful.
The question is how to make a server on AKKA.Net, which would hold a large number of connections (for example, 100 000 - 1 000 000). And could I use cluster for this purpose?
Server
using Akka.Actor;
using Akka.Configuration;
using System;
using System.Configuration;
using Akka.Cluster.Tools.Client;
using System.Threading;
namespace CoreSPServer
{
class Program
{
static void Main(string[] args)
{
Config config = ConfigurationFactory.ParseString(ConfigurationManager.AppSettings["ClusterConfig"]);
ActorSystem system = ActorSystem.Create("ClusterSystem", config);
var publisher = system.ActorOf(Props.Create(() => new Publisher()), "Publisher");
var clientSub = system.ActorOf(Props.Create(() => new ClientSubscriber()), "Sub");
ClusterClientReceptionist.Get(system).RegisterService(clientSub);
if (Console.ReadLine() == "start")
{
publisher.Tell("test");
Thread.Sleep(10);
}
Console.ReadKey();
}
}
}
ClientSubscriber and publisher
class ClientSubscriber : ReceiveActor
{
public ClientSubscriber()
{
var mediator = DistributedPubSub.Get(Context.System).Mediator;
Receive<IActorRef>(senderToSub =>
{
mediator.Tell(new Subscribe("content", senderToSub));
});
}
}
public class Publisher : ReceiveActor
{
public Publisher()
{
var mediator = DistributedPubSub.Get(Context.System).Mediator;
Receive<string>(str =>
{
var upperCase = str.ToUpper();
mediator.Tell(new Publish("content", upperCase));
});
}
}
Client
static void Main(string[] args)
{
var config = ConfigurationFactory.ParseString(ConfigurationManager.AppSettings["ClientConf"]);
ActorSystem Sys = ActorSystem.Create("ClusterClient", config);
//Connection path to Cluster Node
var initialContacts = new List<ActorPath>(){
ActorPath.Parse("akka.tcp://ClusterSystem#localhost:5001/system/receptionist"),
}.ToImmutableHashSet();
var settings = ClusterClientSettings.Create(Sys).WithInitialContacts(initialContacts);
for(int i = 0; i < 5000; i++)
{
IActorRef c = Sys.ActorOf(ClusterClient.Props(settings), "client" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
var asd = Sys.ActorOf(Props.Create<Subscriber>(), "clientSub" + Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
c.Tell(new ClusterClient.Send("/user/Sub5001", asd));
Thread.Sleep(1/10);
}
Console.ReadKey();
}
Server config
akka {
extensions = ["Akka.Cluster.Tools.Client.ClusterClientReceptionistExtensionProvider, Akka.Cluster.Tools"]
actor.provider = cluster
remote {
dot-netty.tcp {
port = 5001
public-hostname = localhost
auto-down-unreachable-after = off
}
}
cluster {
seed-nodes = ["akka.tcp://ClusterSystem#localhost:5001"]
}
}
I'm having some difficulty setting up a WCF interface. I can connect fine, but as soon as I start sending certain data, I get the following exception:
The socket connection was aborted. This could be caused by an error processing your message or a receive timeout being exceeded by the remote host, or an underlying network resource issue. Local socket timeout was '01:39:59.9084817'.
From looking at a few other questions, I found that this could be due to inefficient timeout. So I increased the following parameters to 100 minutes just for kicks.
tcpBinding.ReceiveTimeout = TimeSpan.FromMinutes(100);
tcpBinding.OpenTimeout = TimeSpan.FromMinutes(100);
tcpBinding.SendTimeout = TimeSpan.FromMinutes(100);
However, this exception is thrown the same instant a certain function is called so I don't think that is the problem. The peculiar thing though is that the exception always shows a time almost the same as whatever I put into those parameters, regardless of how much time actually passed.
I found that I can send the standard types like object, string, int, etc but whenever I try to send a custom type, this error is thrown. That is telling me it might be a bad reference somewhere. Does this make sense?
UPDATE:
Had a chance to upload the code.
Here is the client side:
namespace GenericWCF
{
/// <summary>
/// TODO: Update summary.
/// </summary>
[ServiceContract()]
public interface SimWCFInterface
{
[OperationContract()]
void SimpleMessage(string message);
[OperationContract()]
List<CustomObj> GetDataPoints();
}
}
and the server side:
namespace DifferentNamespace
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// TODO: Update summary.
/// </summary>
[ServiceContract()]
public interface SimWCFInterface
{
[OperationContract()]
void SimpleMessage(string message);
[OperationContract()]
List<CustomObj> GetDataPoints();
}
and the actual implementation
namespace DifferentNamespace
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
/// <summary>
/// TODO: Update summary.
/// </summary>
[ServiceContract()]
public interface SimWCFInterface
{
public void SimpleMessage(string message)
{
Debug.WriteLine("This is a simple message wcf");
MessageBox.Show(message);
}
public List<CustomObj> GetDataPoints()
{
List<CustomObj> points = new List<CustomObj>();
return points;
}
}
So there really isn't anything special going on I don't think. SimpleMessage runs fine no problem. If I change GetDataPoints to return a regular object, it works fine as well. The problem only occurs when I return that CustomObj. One thing I noticed just now is that client and server side live in different namespaces, so could that be an issue? Also, I'm using a tcp conneciton. I'll post that code in a moment.
EDIT:
The tcp connection code:
client
private string endPointAddress;
private NetTcpBinding tcpBinding;
public void Connect(string ipAddress, string portNumber, string interfaceName)
{
try
{
interfaceName = interfaceName.Substring(interfaceName.LastIndexOf(".") + 1);
endPointAddress = "net.tcp://" + ipAddress + ":" + portNumber + "/" + interfaceName;
tcpBinding = new NetTcpBinding();
tcpBinding.TransactionFlow = false;
tcpBinding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
tcpBinding.Security.Mode = SecurityMode.None;
tcpBinding.ReceiveTimeout = TimeSpan.FromMinutes(100);
tcpBinding.OpenTimeout = TimeSpan.FromMinutes(100);
tcpBinding.SendTimeout = TimeSpan.FromMinutes(100);
tcpBinding.ReliableSession.Ordered = false;
tcpBinding.ReliableSession.InactivityTimeout = TimeSpan.MaxValue;
tcpBinding.MaxReceivedMessageSize = 10000000;
tcpBinding.MaxBufferPoolSize = 10000000;
tcpBinding.MaxBufferSize = 10000000;
EndpointAddress endpointAddress = new EndpointAddress(endPointAddress);
_SimChannelFactory = new ChannelFactory<SimWCFInterface>(tcpBinding, endpointAddress);
_SimWCFInterface = _SimChannelFactory.CreateChannel();
//test connection...working
_SimWCFInterface.SimpleMessage("what a message");
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
and the server
static public void CreateSimHost(string IPAdd, string port, Type interfaceType, Type serviceType)
{
try
{
string interfaceName = interfaceType.ToString();
interfaceName = interfaceName.Substring(interfaceName.LastIndexOf(".") + 1);
// Create the url that is needed to specify where the service should be started
urlService = "net.tcp://" + IPAdd + ":" + port + "/" + interfaceName;
host = new ServiceHost(serviceType);
// The binding is where we can choose what transport layer we want to use. HTTP, TCP ect.
tcpBinding = new NetTcpBinding
{
TransactionFlow = false
};
tcpBinding.Security.Transport.ProtectionLevel = ProtectionLevel.EncryptAndSign;
tcpBinding.Security.Transport.ClientCredentialType = TcpClientCredentialType.Windows;
tcpBinding.Security.Mode = SecurityMode.None; // <- Very crucial
tcpBinding.ReceiveTimeout = TimeSpan.MaxValue;
tcpBinding.SendTimeout = TimeSpan.MaxValue;
tcpBinding.ReliableSession.Ordered = false;
tcpBinding.ReliableSession.InactivityTimeout = TimeSpan.MaxValue;
// Add a endpoint
host.AddServiceEndpoint(interfaceType, tcpBinding, urlService);
host.Open();
}
catch (Exception ex)
{
Debug.WriteLine(ex);
}
}
Could you guys please point what I'm missing here ?
I have two sample projects. Both console applications (.Net 3.5). Here is "server" side:
class Program
{
static void Main()
{
var baseAddresses = new Uri("net.tcp://localhost:9000/WcfServiceLibrary/svc");
var host = new ServiceHost(typeof(UiWcfSession), baseAddresses);
var reliableSession = new ReliableSessionBindingElement { Ordered = true, InactivityTimeout = TimeSpan.MaxValue };
var binding = new CustomBinding(reliableSession, new TcpTransportBindingElement()) { ReceiveTimeout = TimeSpan.MaxValue };
host.AddServiceEndpoint(typeof(IClientFulfillmentPipeService), binding, k.WinSvcEndpointName);
host.Open();
Thread.CurrentThread.Join();
}
}
Not sure if it's important but here is small snippet of IClientFulfillmentPipeService
[ServiceContract(CallbackContract = typeof (IClientFulfillmentPipeServiceCallbacks), SessionMode = SessionMode.Required)]
public interface IClientFulfillmentPipeService
{
[OperationContract(IsOneWay = true)]
void Initialize(int uiProcessId, string key);
}
[ServiceContract]
public interface IClientFulfillmentPipeServiceCallbacks
{
[OperationContract(IsOneWay = true)]
void ShowBalloonTip(int timeout, string tipTitle, string tipText, BalloonTipIcon tipIcon);
}
and finally client
private void OpenConnection()
{
try
{
var ep = new EndpointAddress("net.tcp://localhost:9000/WcfServiceLibrary/svc");
var reliableSession = new ReliableSessionBindingElement {Ordered = true};
Binding binding = new CustomBinding(reliableSession, new TcpTransportBindingElement());
reliableSession.InactivityTimeout = TimeSpan.MaxValue;
var pipeFactory = new DuplexChannelFactory<IClientFulfillmentPipeService>(this, binding, ep);
commChannel = pipeFactory.CreateChannel();
((IChannel) commChannel).Open(OpenConnectionTimeout);
}
catch (Exception ex)
{
Log.ErrorFormat("The communication channel to the windows service could not be established. {0}", ex.Message);
}
}
The client fails with System.ServiceModel.EndpointNotFoundException exception which says: "There was no endpoint listening at net.tcp://localhost:9000/WcfServiceLibrary/svc that could accept the message. This is often caused by an incorrect address or SOAP action. See InnerException, if present, for more details."
This is modification of production code which is using named pipes and I want to convert it to Tcp transport.
Thanks!
I accidentally left k.WinSvcEndpointName in the endpoint definition in the server. That was the problem.
I have an application using discovery that may be deployed locally on the same PC or remote from the service it consumes. Is there any way to expose the named pipe binding via WCF discovery? If not I suppose I can negotiate after the service is discovered to determine the most suitable binding.
Yes, it's possible to do that. But since the address will be listed as "localhost" (the only possible address for net.pipe), you'll likely need some sort of test to verify that the service is actually running on the same machine as the discovery client.
public class StackOverflow_7068743
{
[ServiceContract]
public interface ITest
{
[OperationContract]
string Echo(string text);
}
public class Service : ITest
{
public string Echo(string text)
{
return text + " (via " + OperationContext.Current.IncomingMessageHeaders.To + ")";
}
}
public static void Test()
{
string baseAddressHttp = "http://" + Environment.MachineName + ":8000/Service";
string baseAddressPipe = "net.pipe://localhost/Service";
ServiceHost host = new ServiceHost(typeof(Service), new Uri(baseAddressHttp), new Uri(baseAddressPipe));
host.Description.Behaviors.Add(new ServiceDiscoveryBehavior());
host.AddServiceEndpoint(new UdpDiscoveryEndpoint());
host.AddServiceEndpoint(typeof(ITest), new BasicHttpBinding(), "");
host.AddServiceEndpoint(typeof(ITest), new NetNamedPipeBinding(NetNamedPipeSecurityMode.None), "");
host.Open();
Console.WriteLine("Host opened");
DiscoveryClient discoveryClient = new DiscoveryClient(new UdpDiscoveryEndpoint());
FindResponse findResponse = discoveryClient.Find(new FindCriteria(typeof(ITest)));
Console.WriteLine(findResponse.Endpoints.Count);
EndpointAddress address = null;
Binding binding = null;
foreach (var endpoint in findResponse.Endpoints)
{
if (endpoint.Address.Uri.Scheme == Uri.UriSchemeHttp)
{
address = endpoint.Address;
binding = new BasicHttpBinding();
}
else if (endpoint.Address.Uri.Scheme == Uri.UriSchemeNetPipe)
{
address = endpoint.Address;
binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
break; // this is the preferred
}
Console.WriteLine(endpoint.Address);
}
if (binding == null)
{
Console.WriteLine("No known bindings");
}
else
{
ChannelFactory<ITest> factory = new ChannelFactory<ITest>(binding, address);
ITest proxy = factory.CreateChannel();
Console.WriteLine(proxy.Echo("Hello"));
((IClientChannel)proxy).Close();
factory.Close();
}
Console.Write("Press ENTER to close the host");
Console.ReadLine();
host.Close();
}
}
I have a problem with my chat server implementation and I couldn't figure out why it doesn't work as intended.
The client could send messages to the server, but the server only sends the messages to itself instead of the client.
E.g. the client connects to the server, then types "hello" into the chat. The server successfully gets the message but then posts the message to its own console instead of sending it to the connected clients.
Well... maybe I have missed something as I'm very new to .Net remoting. Maybe someone could help me figure out what the problem is. Any help is appreciated!
The code:
I have a small interface for the chat implementation on the server
public class ChatService : MarshalByRefObject, IService
{
private Dictionary<string, IClient> m_ConnectedClients = new Dictionary<string, IClient>();
private static ChatService _Chat;
private ChatService()
{
Console.WriteLine("chat service created");
_Chat = this;
}
public bool Login(IClient user)
{
Console.WriteLine("logging in: " + user.GetIp());
if (!m_ConnectedClients.ContainsKey(user.GetIp()))
{
m_ConnectedClients.Add(user.GetIp(), user);
PostMessage(user.GetIp(), user.GetUserName() + " has entered chat");
return true;
}
return false;
}
public bool Logoff(string ip)
{
Console.WriteLine("logging off: " + ip);
IClient user;
if (m_ConnectedClients.TryGetValue(ip, out user))
{
PostMessage(ip, user + " has left chat");
m_ConnectedClients.Remove(ip);
return true;
}
return false;
}
public bool PostMessage(string ip, string text)
{
Console.WriteLine("posting message: " + text + " to: " + m_ConnectedClients.Values.Count);
foreach (var chatter in m_ConnectedClients.Values)
{
Console.WriteLine(chatter.GetUserName() + " : " + chatter.GetIp());
chatter.SendText(text);
}
return true;
}
}
My Server implements the chatservice as singleton:
RemotingConfiguration.RegisterWellKnownServiceType(typeof(ChatService), "chatservice", WellKnownObjectMode.Singleton);
My client is also simply straight forward:
[Serializable]
public class Chat_Client : IClient
{
private string m_IpAdresse;
private string m_UserName = "Jonny";
private string m_Input;
public Chat_Client(string ip, string username)
{
m_IpAdresse = ip;
m_UserName = username;
}
public bool HandleInput(string input)
{
if (input.Equals("exit"))
{
Client.m_ChatService.Logoff(m_IpAdresse);
return false;
}
m_Input = input;
Thread sendThread = new Thread(new ThreadStart(SendPostMessage));
sendThread.Start();
//Console.WriteLine("post message");
return true;
}
private void SendPostMessage()
{
Client.m_ChatService.PostMessage(m_IpAdresse, m_Input);
Thread thisThread = Thread.CurrentThread;
thisThread.Interrupt();
thisThread.Abort();
}
public void SendText(string text)
{
Console.WriteLine("send text got: " + text);
Console.WriteLine(text);
}
The main client connects to the server via:
public void Connect()
{
try
{
TcpChannel channel = new TcpChannel(0);
ChannelServices.RegisterChannel(channel, false);
m_ChatService = (IService)Activator.GetObject(typeof(IService), "tcp://" + hostname + ":9898/Host/chatservice");
System.Net.IPHostEntry hostInfo = Dns.GetHostEntry(Dns.GetHostName());
m_IpAdresse = hostInfo.AddressList[0].ToString();
Chat_Client client = new Chat_Client(m_IpAdresse, m_UserName);
Console.WriteLine("Response from Server: " + m_ChatService.Login(client));
string input = "";
while (m_Running)
{
input = Console.ReadLine();
m_Running = client.HandleInput(input);
}
}
}
#John: No, I wasn't aware of that. Thanks for the info, I'll look into it.
#Felipe: hostname is the dns name of the server I want to connect to.
I found a workaround to make this work. I added an additional TcpListener to the client to which the server connects when the client logs in. Over this second channel I transmit the chat messages back.
However I couldn't understand why the old solution does not work :<
Thanks for the hints guys.
The best thing to do with .NET remoting is to abandon it for WCF. If you read all the best practices for scalability, the way you end up using it is extremely compatible with the Web Services model, and web services is far easier to work with.
Remoting is technically fascinating and forms the basis of reflection, but falls apart once slow, unreliable connections are involved - and all connections are slow and unreliable compared to in-process messaging.