Distributed shared core services using TCP-IP socket clients and servers

TCP-IP Remoting

NetworkA powerfull way to enable complex, specialised or resource intensive server bound processes to be utilized by networked (thin) clients. Exposing the process functions and methods via a TCP-IP socket server allows remote clients to connect as if they were connected directly to the process server. This makes it possible for clients to use the full or managed potential of the heavy or resource intensive server abilities.

Basic Requirements

TCP-IP Socket Server

In order to expose server functionality we need to create a socket service that will listen on a specific TCP-IP port for incoming connection requests.

Imports System.Net.Sockets

Imports System.Threading

Imports System.Diagnostics

Imports System.Runtime.InteropServices

Imports System.Runtime.InteropServices.Marshal

Imports System.Net

Imports System.Reflection

Imports System.Runtime.Remoting.Metadata.W3cXsd2001

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Net.Sockets;
using System.Threading;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.Marshal;
using System.Net;
using System.Reflection;
using System.Runtime.Remoting.Metadata.W3cXsd2001;

If working with unmanaged com classes on the target process, ensure the above name spaces are imported.

TCP-IP Server Listener

Create a class to serve as TCP port listener

Public Class TCPGateway

    Public ProcessRunning As Boolean = True

    Dim serverSocket As TcpListener

    Dim serverSocketActive As Boolean = True

    Dim WithEvents clientSocket As TcpClient

    Public clientCounter As Integer = 0

    Shared _EngineStatus As String = "Free"

    Shared _CurrentOwner As String = ""

 

    Public Shared Property EngineStatus() As String

        Get

            EngineStatus = _EngineStatus

        End Get

        Set(ByVal value As String)

            _EngineStatus = value

        End Set

    End Property

 

    Public Shared Property CurrentOwner() As String

        Get

            CurrentOwner = _CurrentOwner

        End Get

        Set(ByVal value As String)

            _CurrentOwner = value

        End Set

    End Property

 

 

    Private Sub Form1_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing

        ProcessRunning = False

    End Sub

 

 

    Private Sub Form1_Load(sender As Object, e As System.EventArgs) Handles Me.Load

        Timer1.Start()

        StartTCP_IP()

    End Sub

 

 

#Region "TCP-IP"

    Sub StartTCP_IP()

        Dim myServerIP As IPAddress = IPAddress.Parse("xxx.xxx.xxx.xxx")

        Dim myServerPort As Integer = yyyy

        serverSocket = New TcpListener(myServerIP, myServerPort)

        Try

            serverSocket.Start()

            '// Start Client Connection Listener

            Dim tcpListener As New Threading.Thread(AddressOf Listener)

            tcpListener.Start()

            RichTextBox1.Text = RichTextBox1.Text & "Gateway running: " & myServerIP.ToString & " port: " & myServerPort & vbCrLf

        Catch ex As Exception

            RichTextBox1.Text = RichTextBox1.Text & ex.ToString & vbCrLf

            serverSocketActive = False

        End Try

    End Sub

 

    Delegate Sub WriteTextDelegate(ByVal Text As String)

    Public WriteTextInstance As New WriteTextDelegate(AddressOf WriteText)

    Sub WriteText(ByVal Text As String)

        Writer(Text)

    End Sub

    Sub Writer(ByVal text As String)

        Me.RichTextBox1.Text = Me.RichTextBox1.Text & text & vbCrLf

    End Sub

 

    Sub Listener()

        Do While True

            Threading.Thread.Sleep(10)

            Try

                clientCounter += 1

                clientSocket = serverSocket.AcceptTcpClient()

                clientSocket.LingerState.Enabled = False

                clientSocket.LingerState.LingerTime = 0

                clientSocket.ReceiveTimeout = 5000

                clientSocket.SendTimeout = 5000

                Dim client As New handleTCPClient

                client.startClient(clientSocket, Convert.ToString(clientCounter))

            Catch ex As Exception

                RichTextBox1.Text = RichTextBox1.Text & ex.ToString & vbCrLf

            End Try

            If Not ProcessRunning Then

                Exit Sub

            End If

        Loop

    End Sub

 

    Private Sub Timer1_Tick(ByVal sender As Object, ByVal e As System.EventArgs) Handles Timer1.Tick

        Label1.Text = handleTCPClient.State

    End Sub

#End Region

End Class

    public class TCPGateway

{

    public bool ProcessRunning = true;

    TcpListener serverSocket;

    bool serverSocketActive = true;

    TcpClient clientSocket;

    public int clientCounter = 0;

    static string _EngineStatus = "Free";

    static string _CurrentOwner = "";

 

    public static string EngineStatus {

        get { return _EngineStatus; }

        set { _EngineStatus = value; }

    }

 

    public static string CurrentOwner {

        get { return _CurrentOwner; }

        set { _CurrentOwner = value; }

    }

 

    private void Form1_FormClosing(object sender, System.Windows.Forms.FormClosingEventArgs e)

    {

        ProcessRunning = false;

    }

 

    private void Form1_Load(object sender, System.EventArgs e)

    {

        Timer1.Start();

        StartTCP_IP();

    }

 

    #region "TCP-IP"

    public void StartTCP_IP()

    {

        IPAddress myServerIP = IPAddress.Parse("xxx.xxx.xxx.xxx");

        int myServerPort = yyyy;

        serverSocket = new TcpListener(myServerIP, myServerPort);

        try {

            serverSocket.Start();

            //// Start Client Connection Listener

            System.Threading.Thread tcpListener = new System.Threading.Thread(Listener);

            tcpListener.Start();

            RichTextBox1.Text = RichTextBox1.Text + "Gateway running: " + myServerIP.ToString + " port: " + myServerPort + Environment.NewLine;

 

        } catch (Exception ex) {

            RichTextBox1.Text = RichTextBox1.Text + ex.ToString() + Environment.NewLine;

 

            serverSocketActive = false;

        }

    }

 

    public delegate void WriteTextDelegate(string Text);

    public WriteTextDelegate WriteTextInstance = new WriteTextDelegate(WriteText);

 

    public void WriteText(string Text)

    {

        Writer(Text);

    }

 

    public void Writer(string text)

    {

        this.RichTextBox1.Text = this.RichTextBox1.Text + text + Environment.NewLine;

    }

 

    public void Listener()

    {

        while (true) {

            System.Threading.Thread.Sleep(10);

            try {

                clientCounter += 1;

                clientSocket = serverSocket.AcceptTcpClient();

                clientSocket.LingerState.Enabled = false;

                clientSocket.LingerState.LingerTime = 0;

                clientSocket.ReceiveTimeout = 5000;

                clientSocket.SendTimeout = 5000;

                handleTCPClient client = new handleTCPClient();

                client.startClient(clientSocket, Convert.ToString(clientCounter));

            } catch (Exception ex) {

                RichTextBox1.Text = RichTextBox1.Text + ex.ToString() + Environment.NewLine;

            }

            if (!ProcessRunning) {

                return;

            }

        }

    }

 

    private void Timer1_Tick(object sender, System.EventArgs e)

    {

        Label1.Text = handleTCPClient.State;

    }

    public TCPGateway()

    {

        Load += Form1_Load;

        FormClosing += Form1_FormClosing;

    }

    #endregion

}

The above code is launched when the application is started. The main routine StartTCP_IP() starts a new TcpListener on the IP xxx.xxx.xxx.xxx port yyyy.

An endless While loop keeps polling for incoming connections. The boolean 'ProcessRunning' flag is used to stop the process on application shutdown.

Once a connection is made, it hands it off to the class handleTCPClient, this spawns a new thread that initiates receiving and sending information. This class is also responsible for interfacing and communicating with the processes on the main server (the target application functionality).

TCP-IP Socket Client

In order to utilize server functionality we need to create a socket service that will call and connect on a specific TCP-IP port to the socket server.

Imports System.Net.Sockets

Imports System.Runtime.InteropServices

Imports System.Web.Script.Serialization

using System.Net.Sockets;

using System.Runtime.InteropServices;

using System.Web;

using System.Web.Script.Serialization;

On the client application ensure the above namespaces are loaded.

TCP-IP Client

Client functionality is not as complex as the server side as all it needs to do is make a connection to the server. The code below shows a simple function that requests the remote severs status.

 Const FailCounter As Integer = 100

    Public Shared Function GerTCPServerStatus() As String

        Dim status As String = "Unknown"

        Dim CommandString As String = "Status#" & Environment.MachineName

        Dim Timeout As Integer = 100

        Dim RemoteAddress As String = ConfigurationManager.AppSettings("BridgeAddress")

        Dim RemotePort As String = ConfigurationManager.AppSettings("BridgePort")

        If ServerAvailable(RemoteAddress, RemotePort) Then

            Dim TcpClient As TcpClient

            Dim NetworkStream As NetworkStream

            TcpClient = New TcpClient

            TcpClient.SendTimeout = 5

            TcpClient.ReceiveTimeout = 5

            TcpClient.Connect(Net.IPAddress.Parse(RemoteAddress), ConfigurationManager.AppSettings("BridgePort"))

            If TcpClient.Connected Then

                TcpClient.NoDelay = True

                TcpClient.LingerState.Enabled = True

                TcpClient.LingerState.LingerTime = 50

                HttpContext.Current.Session.Contents("TcpClient") = TcpClient

                NetworkStream = TcpClient.GetStream()

                NetworkStream.ReadTimeout = 50

                HttpContext.Current.Session.Contents("NetworkStream") = NetworkStream

                If NetworkStream.CanWrite And NetworkStream.CanRead Then

                    '// Send request

                    Dim sendBytes As [Byte]() = Encoding.ASCII.GetBytes(CommandString)

                    NetworkStream.Write(sendBytes, 0, sendBytes.Length)

                    NetworkStream.Flush()

                    Threading.Thread.Sleep(10)

                    '// Get answer

wait:               Dim bytes(TcpClient.Available) As Byte

                    NetworkStream.Read(bytes, 0, CInt(TcpClient.Available))

                    Dim ReturnData As String = Encoding.ASCII.GetString(bytes)

                    Dim Array() As String = Split(ReturnData, "#")

                    If Array.Length < 2 Then

                        ' If Split(ReturnData, "#")(0) <> Environment.MachineName Then

                        Threading.Thread.Sleep(5)

                        GoTo wait

                    End If

                    status = Array(0)

                End If

                NetworkStream.Close()

                NetworkStream.Dispose()

            Else

                status = "No Engine Available"

            End If

        Else

            status = "Server not found."

        End If

        Return status

    End Function

const int FailCounter = 100;

public static string GerTCPServerStatus()

{

    string status = "Unknown";

    string CommandString = "Status#" + Environment.MachineName;

    int Timeout = 100;

    string RemoteAddress = ConfigurationManager.AppSettings("BridgeAddress");

    string RemotePort = ConfigurationManager.AppSettings("BridgePort");

 

    if (ServerAvailable(RemoteAddress, RemotePort)) {

        TcpClient TcpClient = default(TcpClient);

        NetworkStream NetworkStream = default(NetworkStream);

        TcpClient = new TcpClient();

        TcpClient.SendTimeout = 5;

        TcpClient.ReceiveTimeout = 5;

        TcpClient.Connect(System.Net.IPAddress.Parse(RemoteAddress), ConfigurationManager.AppSettings("BridgePort"));

 

        if (TcpClient.Connected) {

            TcpClient.NoDelay = true;

            TcpClient.LingerState.Enabled = true;

            TcpClient.LingerState.LingerTime = 50;

            HttpContext.Current.Session.Contents("TcpClient") = TcpClient;

            NetworkStream = TcpClient.GetStream();

            NetworkStream.ReadTimeout = 50;

            HttpContext.Current.Session.Contents("NetworkStream") = NetworkStream;

 

            if (NetworkStream.CanWrite & NetworkStream.CanRead) {

                //// Send request

                Byte[] sendBytes = Encoding.ASCII.GetBytes(CommandString);

                NetworkStream.Write(sendBytes, 0, sendBytes.Length);

                NetworkStream.Flush();

                System.Threading.Thread.Sleep(10);

                wait:

                //// Get answer

                byte[] bytes = new byte[TcpClient.Available + 1];

                NetworkStream.Read(bytes, 0, Convert.ToInt32(TcpClient.Available));

                string ReturnData = Encoding.ASCII.GetString(bytes);

                string[] Array = Strings.Split(ReturnData, "#");

                if (Array.Length < 2) {

                    // If Split(ReturnData, "#")(0) <> Environment.MachineName Then

                    System.Threading.Thread.Sleep(5);

                    goto wait;

                }

                status = Array[0];

            }

            NetworkStream.Close();

            NetworkStream.Dispose();

        } else {

            status = "No Engine Available";

        }

 

    } else {

        status = "Server not found.";

    }

    return status;

}

How the Client works

The IP address and port, in this case obtained from the application configuration settings must point the remote server IP and port.

The TCPClient will attempt to make a connection to the server. If successful the client't (now connected) stream is attached to a network stream.

If the networkstream is open for read and write operations, a command (string) is converted to a byte array and sent to the server. In this case the command is "Status#MachineName" (where MachineName is the name of the client computer, this is used by the server to identify the connected party).

After sending the command, the client waits for a reply, checking to make sure the information received is for it. In This case, responses from the server contain the MachineName of the client, so these should (must) match.

TCP-IP Remoting Demo

A live demo (if the development server is not in use for other purposes) of TCP remoting. It adds an extra twist to remote async services.

Purpose: Enable a web user to use remote value added services.

  • In this case we will make a simple 3D cube and use SolidWorks to achieve this, returning an image of the cube.

  • Width
    Depth
    Height