Dailycode.info

Short solution for short problems

WCF: Which protocol to use? HTTP or TCP?

My overall conclusion of reading information and using both protocols is that you have to consider these to topics before choosing the protocol to use!

  • Are the services used internally or will they be used by external applications?
  • Is it an internet application?

When you answer yes to one of the previous questions, using the HTPP protocol will be the best choice. But when you develop a client server application for use within the company, use TCP.  TCP is in my opinion the best protocol to use when client and server use WCF and the application is running on a local network. The services can also be accessed by other applications running on this network and using WCF. So I believe that in many cases people just tend to HTTP without really looking why. Next I list a part from a Microsoft article, which explains this for you clear and brief:

When to Use HTTP Transport 

HTTP is a request/response protocol between clients and servers. The most common application consists of Web-browser clients that communicate with a Web server. The client sends a request to a server, which listens for client request messages. When the server receives a request, it returns a response, which contains the status of the request. If successful, optional data, such as a Web page, an error message, or other information is returned. For more information about the HTTP protocol, see HTTP - Hypertext Transfer Protocol.

The HTTP protocol is not connection-based—once the response is sent, no state is maintained. To handle multiple-page transactions, the application must persist any necessary state.

In WCF, the HTTP transport binding is optimized for interoperability with legacy non-WCF systems. If all communicating parties are using WCF, the TCP-based or named pipes-based bindings are faster. For more information, see NetTcpBinding and NetNamedPipeBinding.

When to Use the TCP Transport 

TCP is a connection-based, stream-oriented delivery service with end-to-end error detection and correction. Connection-based means that a communication session between hosts is established before exchanging data. A host is any device on a TCP/IP network identified by a logical IP address.

TCP provides reliable data delivery and ease of use. Specifically, TCP notifies the sender of packet delivery, guarantees that packets are delivered in the same order in which they are sent, retransmits lost packets, and ensures that data packets are not duplicated. Note that this reliable delivery applies between two TCP/IP nodes, and is not the same thing as WS-ReliableMessaging, which applies between endpoints, no matter how many intermediate nodes they may include.

The WCF TCP transport is optimized for the scenario where both ends of the communication are using WCF. This binding is the fastest WCF binding for scenarios that involve communicating between different machines. The message exchanges use the BinaryMessageEncodingBindingElement for optimized message transfer. TCP provides duplex communication and so can be used to implement duplex contracts, even if the client is behind network address translation (NAT).

 

Fast way to test your service is to open the visual studio command prompt and type in WCFTESTCLIENT This will open the WCFTestclient.exe and there you can test your endpoints.


Channel factory implementation (Easy client switch from http to tcp)

In an application I created, I am using WCF to make the connection from the process layer (Client side) to the business layer (Server side).

Since the environment started out using .Net Remoting, I initially used Transmission Control Protocol (TCP). But to make the best use of WCF, I enabled the possibility to switch to HTTP. WCF provides the ideal interface for this. Still some configuration needs to be done. On server side I handle everything via configuration files.

On client side however, I will not use the configuration file but  handle this in the program itself. Leaving a simple configuration switch (HTTP or TCP) in the configuration file to switch from one into the other protocol.

 

So I developed a class that handles this and initiates the proxy objects using the channel factory.

 

When the class is called (Class is cached in a Singleton class), this class will decide on which protocol to use to initiate the proxy classes:

 

For example:

To get some user information, I need to initiate the usermanger proxy class. The first check I will do is to check if the class is not yet initiated, if not, I will initiate it. Else I will pass the usermanger object.

The second check is to check the configuration, if we would use HTTP or TCP. In my case I get this during startup and put it in the singleton class property called CommunicationProtocol.  

 

public IUserManager UserManager
{
    get 
    {
        if (_userMgrProxy==null||(_userMgrProxy as ICommunicationObject).State != CommunicationState.Opened)
        {
            if (GenericSingleton<GeneralParams>.GetInstance().CommunicationProtocol == ProtocolMode.HTTP)
            {
                var binding = GetHttpBinding();
                _userMgrProxy = ChannelFactory<IUserManager>.CreateChannel(binding, new EndpointAddress(userEndPoint));
            }
            else
            {
                var binding = GetTcpBinding();
                _userMgrProxy = ChannelFactory<IUserManager>.CreateChannel(binding, new EndpointAddress(userEndPoint));
            }
        }
        return _userMgrProxy; 
    }
}

 

Depending on the communication protocol I will get the corresponding binding.

Below are functions to get the bindings.

 

private WSHttpBinding GetHttpBinding()
{
    return new WSHttpBinding
    {
        ReaderQuotas =
        {
            MaxDepth = 10000000,
            MaxArrayLength = 10000000,
            MaxBytesPerRead = 10000000,
            MaxNameTableCharCount = 10000000,
            MaxStringContentLength = 10000000
        },
        MaxReceivedMessageSize = 10000000,
        MessageEncoding = WSMessageEncoding.Mtom,
        MaxBufferPoolSize = 10000000
    };
}
 
private NetTcpBinding GetTcpBinding()
{
    return new NetTcpBinding
    {
        ReaderQuotas =
        {
            MaxDepth = 10000000,
            MaxArrayLength = 10000000,
            MaxBytesPerRead = 10000000,
            MaxNameTableCharCount = 10000000,
            MaxStringContentLength = 10000000
        },
        MaxReceivedMessageSize = 10000000,
        MaxBufferPoolSize = 10000000
    };
}

 

When the program starts up, I will initiate all proxy classes by default, the check when I call one of the proxies to check if its initiated or not is an extra check if for some reason the managers are called before being initialized.

Here the code I use to initialize the proxy manager objects:

 

public void InitChannels()
{
    //Set tcp or http
    if (GenericSingleton<GeneralParams>.GetInstance().CommunicationProtocol == ProtocolMode.HTTP)
    {
        var binding = GetHttpBinding();
        _animalMgrProxy = ChannelFactory<IAnimalManager>.CreateChannel(binding, new EndpointAddress(animalEndPoint));
        _configMgrpProxy = ChannelFactory<IConfigurationManager>.CreateChannel(binding, new EndpointAddress(configEndPoint));
        _studyMgrProxy = ChannelFactory<IStudyManager>.CreateChannel(binding, new EndpointAddress(studyEndPoint));
        _userMgrProxy = ChannelFactory<IUserManager>.CreateChannel(binding, new EndpointAddress(userEndPoint));
    }
    else
    {
        var binding = GetTcpBinding();
        _animalMgrProxy = ChannelFactory<IAnimalManager>.CreateChannel(binding, new EndpointAddress(animalEndPoint));
        _configMgrpProxy = ChannelFactory<IConfigurationManager>.CreateChannel(binding, new EndpointAddress(configEndPoint));
        _studyMgrProxy = ChannelFactory<IStudyManager>.CreateChannel(binding, new EndpointAddress(studyEndPoint));
        _userMgrProxy = ChannelFactory<IUserManager>.CreateChannel(binding, new EndpointAddress(userEndPoint));
    }

 

One last thing before I forget.  The endpoint URL’s are also collected from the configuration file. This is also done in this class and looks like this:

 

string animalEndPoint = GenericSingleton<GeneralParams>.GetInstance().CommunicationProtocolAddress+ "AnimalManager/AnimalMgr";
string configEndPoint = GenericSingleton<GeneralParams>.GetInstance().CommunicationProtocolAddress + "ConfigurationManager/ConfigurationMgr";
string studyEndPoint = GenericSingleton<GeneralParams>.GetInstance().CommunicationProtocolAddress + "StudyManager/StudyMgr";
string userEndPoint = GenericSingleton<GeneralParams>.GetInstance().CommunicationProtocolAddress + "UserManager/UserMgr";
string auditEndPoint = GenericSingleton<GeneralParams>.GetInstance().AuditProtocolAddress;

 

Then finally I can show the configuration on the client side:

 

  <appSettings>
    
    <!--<add key="CommunicationProtocol" value="HTTP"/>
    <add key="CPBaseAddress" value="http://localhost:7000/LabCollectWCFService/"/>-->
    
    <add key="CommunicationProtocol" value="TCP"/>
    <add key="CPBaseAddress" value="net.tcp://localhost:8000/LabCollectWCFService/"/>
    
    <add key="AuditBaseAddress" value="net.msmq://localhost/private/LabCollectAudit"/>

 

If I want to switch to HTTP I only need to uncomment the first to lines and comment the next 2 lines. That’s it.

 

 

 


TcpListener .AcceptSocket() hangs

 First I will sketch the situation. We have a number of devices connected to the network through RS32. Devices like a barcode scanner, weigh scales, RFID scanners … When a user uses a device, the data gets send through the network using TCP. When the data is sent on the network it needs to be received by client pc. There is mapping data stored in the database that controls what devices should communicate with which client pc. This is where the device manager comes in place. 

The device manager polls the port from the devices and passes the received information to the client devices. The device manager is a .Net service on that runs continuously on a server.  

The problem that kept occurring was that when a socket could not be reached the TCPClient hanged. The only possibility was to restart the service. On some cases we had to reboot the server because the service could not be stopped and we couldn’t stop the task with task management.   

The line of code that was giving the problem was this: 

try
{
      	if (_tcpListener != null)      
      	{
            	_tcpListener.Stop();
      	}
	_tcpListener = new TcpListener(IPAddress.Any, _inTcpPort);
	_tcpListener.Start();
       _socket = _tcpListener.AcceptSocket();
       return (_socket.Connected);
}
catch (Exception exc)
{
	ExceptionManager.Publish(exc);
      	return false;

}

 

When the socket can not be reached it will keep looking for it and blocks the thread. I tried to look for a solution on the internet but couldn’t find a decent one. So I started to test with multi threading. I created a multi threading solution with a timeout releasing the objects if the timeout was reached. While looking I managed to use a part of the threading logic that was exposed by the TCPListener itself. The main thread will be synchronic, so we will never try to poll 2 sockets at the same time. It is possible, when you have 1000’s of devices to run the socket connections asynchrone, but you’ll have to change the socket connected checking. Since we are using no more the 50 devices it will take a few minutes to start up the service, which is very acceptable and easy manageable. 

The line of code I presented above changed into this piece of code:

if (_tcpListener != null)
{
_tcpListener.Stop();
}
_tcpListener = new TcpListener(IPAddress.Any, _inTcpPort);
_tcpListener.Start();
if (!_tcpListener.Pending())
{
            //This is were we start the socket connection logic in a different thread.
IAsyncResult ar = 
_tcpListener.BeginAcceptSocket(new AsyncCallback(DoAcceptSocketCallback), _tcpListener);
 
//Here I’ll wait for 5 seconds, if the socket could not be found, I release the tcpListener 
Thread.Sleep(5000);
 
 
if (_socket != null && _socket.Connected)
{
// these parameters are only of use after the socket is connected.
_socket.SendTimeout = 20000;
// If the device you are polling sends and recieves commands/information then 
// do not use this parameter._socket.Blocking = false;
return (_socket.Connected);
}
else
{
if (_socket != null)
{
_socket.Close();
}
if (_tcpListener != null)
{
_tcpListener.Stop();
_tcpListener = null;
}
return false;
}
}

 It is actually the BeginAcceptSocket who starts a asychronous proces to listen to the port. This proces looks like this: 

// Process the client connection.
public void DoAcceptSocketCallback(IAsyncResult ar)
{
try
{
// Get the listener that handles the client request.
TcpListener listener = (TcpListener)ar.AsyncState;
 
if (listener != null)
{
            //Here we connect to the socket.
_socket = listener.EndAcceptSocket(ar);
 
string message = string.Format("Device {0} {1}: 'Startpolling()' success.", 
_deviceType, _inTcpPort);
EventLogWriter.Instance.WriteToLog(message, EventLogEntryType.Information);
}
}
catch
{
_socket = null;
}
}

 So the system tries to connect to the socket in a different thread and the main thread is set to sleep for about 5 sec (I’ll put this in a config file to make it configurable). If the _socket is not connected after these 5 seconds, I release the tcplistener with this code: 

if (_socket != null)
{
_socket.Close();
}
if (_tcpListener != null)
{
_tcpListener.Stop();
_tcpListener = null;
}

 When you release the tcplistener the DoAcceptSocketCallback function will be called, so make your check if its not null. Now the system is stable. I haven’t had 1 hang or instability. It always stops and starts perfect and when a device fails to connect, I write a nice entry in the eventlog explaning which device on which port and to which client PC failed to connect. 

//The StartCommunication() function calls the logic where I call the BeginAcceptSocket method mentioned in the 
//beginning of the post. It will return socket.Connected or 
//in case of error (socket could not be found or reached or is not free) it return false. Before I call the 
//device.StartCommunication I write a log entry to inform that the  
//system will try to connect socket ... on device ... and with destination ...
if (device.StartCommunication())
{
 
message = string.Format("Added device.\rName: {0} ({3})\rDestination / port: {1} / {2}", name, 
DestinationIp, tcpPort, (DeviceType)Type);
EventLogWriter.Instance.WriteToLog(message, EventLogEntryType.Information);
}
else
{
message = string.Format("Device {0}-{1}: 'Startpolling()' failed on port: {2}.", device.Guid, 
device.Name, device.TCPPort);
EventLogWriter.Instance.WriteToLog(message, EventLogEntryType.Error);
}

 Works great!!!