Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / PHP

Flexible TCP/IP Server with Web Service using DLL

4.98/5 (19 votes)
10 Dec 2015CPOL5 min read 55.1K   2.8K  
Flexible server that use User Created DLL to handle incoming/outgoing client packets.

Table of contents

 

 

Introduction  

I wanted to do this project to don't have to redo every time a server base, but just writing packets method i will need in a DLL. 

Imagine switching from a chat server to a remote mouse control or whatever in less than a minute. Wouldn't it be cool? Just replace the DLL or change the DLL name in the server configuration with the new one and it's done! 

The project contains:

  • Flexible Server - Server
  • Chat Client - Simple chat client to test if the server works
  • Client Utils - Contains the class to help you connect to a server
  • Logging - Class for better console readability using log level 
  • MethodResponse - Class used in both server and DLL to communicate each other
  • TestDLL - Simple DLL to handle incoming chat client Packet
  • (Optional) MysqlConnector - Contains the class to help you connect to a mysql server and perform queries 

Logic diagram

Image 1

Server screenshot

Image 2

Pros and cons 

Pros 

 

  • Easier
  • You only have to write your DLL with the methods you need
  • You can change the server purpose in less than a minute, by changing the DLL  

 

Cons  

 

 

What's the goal?  

We are going to create a small Chat Server/Client to test how the server works  

Server 

Load DLL  

The server loads the assembly of the specified DLL (In this example the DLL is TestDLL.dll). 

The DLL must contains PacketHandler class. PacketHandler must contains OnClientConnect and OnClientDisconnect methods, we will use them if we want to do something when a client connects or disconnects.  

The server loads only public methods with MethodResponse return type. MethodResponse is used by both server and dll to communicate each other. 

Finally the server stores the methods information inside a list to invoke them later 

C#
//Get User Created DLL
string handlerDLL = GetConfig().data["packetHandlerDLL"];

Assembly packetHandlerDllAssembly = null;
//Check if User Created DLL exists else close Server
if (File.Exists(handlerDLL))
{
    //Load User Created DLL Assembly
    packetHandlerDllAssembly = 
      Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + handlerDLL); 
    Logging.WriteLine("Loading Packet Handler DLL", LogLevel.Information);

    //Get PacketHandler Class
    Type Class = packetHandlerDllAssembly.GetType("PacketHandler");
    try
    {
        //Create a instance of PacketHandler Class
        dllInstance = Activator.CreateInstance(Class);
    }
    catch (Exception e)
    {
        Logging.WriteLine("User Created DLL must have " + 
          "PacketHandler Class. Closing..", LogLevel.Error);
        Thread.Sleep(5000);
        Environment.Exit(0);
    }

    int MethodsCount = 0;
    //Create a list of methods        
    RegisteredMethods = new List<Method>();                 

    bool OnClientConnectMethodFound = false;
    bool OnClientDisconnectMethodFound = false;
    //Get methods created by user
    foreach (MethodInfo MethodInfo in Class.GetMethods(BindingFlags.DeclaredOnly | 
             BindingFlags.Public | BindingFlags.Instance)) 
    {
        //Check if OnClientConnect and OnClientDisconnect methods exist
        if (MethodInfo.Name == "OnClientConnect")
        {
            OnClientConnectMethodFound = true;
            continue;
        }
        
        if (MethodInfo.Name == "OnClientDisconnect")
        {
            OnClientDisconnectMethodFound = true;
            continue;
        }

        //Only load methods with MethodResponse return type
        if (MethodInfo.ReturnType != typeof(MethodResponse))
        {
            Logging.WriteLine("Method: " + MethodInfo.Name + 
              " must return MethodResponse currently: " + 
              MethodInfo.ReturnType.Name, LogLevel.Error);
            Logging.WriteLine("Method: " + MethodInfo.Name + 
              " not registered", LogLevel.Error);
            continue;
        }
        string param = "";
        //Create a new method class. MethodInfo is necessary for future invokes of DLL Methods
        Method Method = new Method(MethodInfo.Name, MethodInfo);
        //Method must have connID(int) Param
        bool connIDParameterFound = false;
        //Get method parameters
        foreach (ParameterInfo pParameter in MethodInfo.GetParameters())
        {
            //Add Parameter
            Method.AddParameter(pParameter.Name, pParameter.ParameterType);
            param += pParameter.Name + " (" + pParameter.ParameterType.Name + ") ";
            if (pParameter.Name.ToLower() == "connid" && 
                     pParameter.ParameterType == typeof(int))
            {
                connIDParameterFound = true;
            }
        }

        if (!connIDParameterFound)
        {
            Logging.WriteLine("Method: " + MethodInfo.Name + 
              " must have a connID(int) param", LogLevel.Error);
            Logging.WriteLine("Method: " + MethodInfo.Name + 
              " not registered", LogLevel.Error);
            continue;
        }

        if (param == "")
            param = "none ";

        //Add method to the registered methods list
        RegisteredMethods.Add(Method);

        Logging.WriteLine("Method name: " + MethodInfo.Name + 
          " parameters: " + param + "registered", LogLevel.Information);
        MethodsCount++;
    }

    if (!OnClientConnectMethodFound || !OnClientDisconnectMethodFound)
    {
        Logging.WriteLine("PacketHandler must contain OnClientConnect and " + 
          "OnClientDisconnect methods. Closing..", LogLevel.Error);
        Thread.Sleep(5000);
        Environment.Exit(0);
    }

    //Close server if there is any registered method
    if (MethodsCount == 0)
    {
        Logging.WriteLine("Any method loaded. Closing..", LogLevel.Information);
        Thread.Sleep(5000);
        Environment.Exit(0);
    }
    Logging.WriteLine("Registered " + MethodsCount + " Methods", LogLevel.Information);
    Logging.WriteLine("Loaded Packet Handler DLL", LogLevel.Information);
}
else
{
    Logging.WriteLine("Unable to locate Packet Handler DLL named: " + 
      handlerDLL + ". Closing..", LogLevel.Error);
    Thread.Sleep(5000);
    Environment.Exit(0);
}

New message from the client

When the server receives a new message from the client first it parses message to the Packet class and then it passes the Packet to the HandlePacket method 

C#
/// <summary>
/// On Packet received callback</summary>
/// <param name="result">Status of asynchronous operation</param>           
/// </summary>
private void ReceiveCallback(IAsyncResult result)
{
    //get our connection from the callback
    Connection conn = (Connection)result.AsyncState;

    try
    {
        //Grab our buffer and count the number of bytes receives
        int bytesRead = conn.socket.EndReceive(result);

        if (bytesRead > 0)
        {
            HandlePacket(ParseMessage(Encoding.ASCII.GetString(conn.buffer, 0, bytesRead), conn), conn);
          
            //Queue the next receive
            conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, 
              SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
        }
        else //Client disconnected
        {
            Core.GetLogging().WriteLine("[" + conn.connID + 
              "] Connection lost from " + 
              ((IPEndPoint)conn.socket.RemoteEndPoint).Address, LogLevel.Debug);

            OnClientDisconnect(conn);

            conn.socket.Close();
            lock (_sockets)
            {
                _sockets.Remove(conn);
            }
        }
    }
    catch (SocketException e)
    {
        Core.GetLogging().WriteLine("[" + conn.connID + 
          "] Connection lost from " + 
          ((IPEndPoint)conn.socket.RemoteEndPoint).Address, LogLevel.Debug);

        OnClientDisconnect(conn);

        if (conn.socket != null)
        {
            conn.socket.Close();
            lock (_sockets)
            {
                _sockets.Remove(conn);
            }
        }
    }
} 

OnClientConnect and OnClientDisconnect

These methods will invoke OnClientConnect/OnClientDisconnect passing the id of the client connection in the DLL 

C#
/// <summary>
/// Invoke OnClientConnect on User Created Dll</summary>      
/// <param name="conn">Client connection</param>
/// </summary>     
private void OnClientConnect(Connection conn)
{
    Core.dllInstance.GetType().GetMethod("OnClientConnect").Invoke(
      Core.dllInstance, new object[] { conn.connID });
}

/// <summary>
/// Invoke OnClientDisconnect on User Created Dll</summary>      
/// <param name="conn">Client connection</param>
/// </summary>     
private void OnClientDisconnect(Connection conn)
{
    Core.dllInstance.GetType().GetMethod("OnClientDisconnect").Invoke(
      Core.dllInstance, new object[] { conn.connID });
} 

Parse the incoming client message

Parse the incoming client message to the Packet class.

First we get the Packet Header/name 

Then we need to parse the Packet body values type from string to the correct type (int, float etc).  This is needed because otherwise we will get the exception "parameter type mismatch".

C#
/// <summary>
/// Parse message string to Packet class</summary>
/// <param name="message">Packet string</param>   
/// <param name="conn">Client connection</param>
/// </summary>
private Packet ParseMessage(string Message, Connection conn)
{
    string PacketHeader = Message.Split(Delimiter)[0];

    Packet Packet = new Packet(PacketHeader);

    Message = Message.Substring(Message.IndexOf(Delimiter) + 1); //Only Packet Body

    //Parse type from incoming packet body values
    foreach (string Parameter in Message.Split(Delimiter))
    {
        //TO-DO more type parsing
        int intN;
        bool boolN;
        if (int.TryParse(Parameter, out intN))
        {
            Packet.AddInt32(intN);
        }
        else if (Boolean.TryParse(Parameter, out boolN))
        {
            Packet.AddBoolean(boolN);
        }
        else
        {
            Packet.AddString(Parameter);
        }
    }

    //Always add connID to Packet to get client id on User Created DLL
    Packet.AddInt32(conn.connID);

    return Packet;
} 

Handle Packet

Handle parsed Packet

The server invokes the respective Packet method in the DLL giving the parameters that has been parsed before

The invoke method returns an object holding the return value of the method that needs to be parsed to the MethodResponse type

Finally it loops Packets contained in MethodResponse and sends the message back to the client/s 

 

C#
/// <summary>
/// Invoke the packet-associated method and send response packets contained in MethodResponse</summary>    
/// <param name="Packet">The incoming packet</param>
/// <param name="conn">Client connection</param>
/// </summary>
private void HandlePacket(Packet Packet, Connection conn)
{
    Core.GetLogging().WriteLine("Received Packet: " + Packet.GetPacketString(), LogLevel.Debug);
    //Get associated Packet method using packet header/name
    Method Method = Core.GetMethodByName(Packet.Header.ToLower());
    if (Method != null)
    {
        //Packet body values count must match with method parameters count
        if (Method.GetParametersCount() != Packet.bodyValues.Count)
        {
            Core.GetLogging().WriteLine("Method: " + Method.GetName() + 
              " has " + Method.GetParametersCount() + 
              " params but client request has " + 
              Packet.bodyValues.Count + " params", LogLevel.Error);
        }
        else
        {
            MethodResponse result = null;
            try
            {
                //Try invoke associated method given packet body values as parameters
                result = (MethodResponse)Method.GetMethodInfo().Invoke(
                  Core.dllInstance, Packet.bodyValues.ToArray());
            }
            catch (Exception e)
            {
                Core.GetLogging().WriteLine("Error handling Method: " + 
                  Method.GetName() + " Exception Message: " + e.Message, LogLevel.Error);
            }
            if (result != null)
            {                      
                Core.GetLogging().WriteLine("Handled Method: " + 
                  Method.GetName() + ". Sending response..", LogLevel.Information);

                //Invoke succeed! now read Packets contained
                //in MethodResponse and send them to the specified clients
                foreach (Packet PacketToSend in result.Packets)
                {
                    string PacketString = PacketToSend.GetPacketString();
                    if (PacketToSend.sendToAll) //Send to all clients
                    {
                        sendToAll(StrToByteArray(PacketString));
                        Core.GetLogging().WriteLine("Sent response: " + 
                          PacketString + " to all clients", LogLevel.Debug);
                    }
                    else if (PacketToSend.sendTo != null) //Only send to clients specified in a list
                    {
                        foreach (int connID in PacketToSend.sendTo)
                        {
                            Send(StrToByteArray(PacketString), _sockets[connID]);
                            Core.GetLogging().WriteLine("Sent response: " + 
                              PacketString + " to client id: " + connID, LogLevel.Debug);
                        }
                    }
                    else //Send to sender
                    {
                        Send(StrToByteArray(PacketString), conn);
                        Core.GetLogging().WriteLine("Sent response: " + 
                          PacketString + " to client id: " + conn.connID, LogLevel.Debug);
                    }
                }
            }
        }
    }
    else Core.GetLogging().WriteLine("Invoked Method: " + 
      Packet.Header + " does not exist", LogLevel.Error);
}

DLL 

The DLL is where you can customize your server to your needs. Here you can write your custom methods 

You can use the private flag on the methods you don't want the server load

PacketHandler must contains both OnClientConnect and OnClientDisconnect methods 

Every public method must have a return type of MethodResponse 

C#
//PacketHandler class must be public to let server reads methods
public class PacketHandler
{
    public static List<User> Users;
    private Logging Logging;

    /// <summary>
    /// Initialize variables  
    ///  </summary>
    public PacketHandler()
    {
        Users = new List<User>();
        Logging = new Logging();
        Logging.MinimumLogLevel = 0;

    }

    /// <summary>
    /// Return Chat User by Connection ID
    /// <param name="connID">Connection ID</param>     
    /// </summary>

    //Prevent server to load these methods using private flag
    private User GetUserByConnID(int connID)
    {
        foreach (User u in Users)
        {
            if (u.connID == connID)
                return u;
        }
        return null;
    }

    /// <summary>
    /// Return Chat User by Name
    /// <param name="Name">User Name</param>     
    /// </summary>
    private User GetUserByName(string Name)
    {
        foreach (User u in Users)
        {
            if (u.Name == Name)
                return u;
        }
        return null;
    }

    /// <summary>
    /// Handle Chat User Login
    /// <param name="username">Username given by Chat User</param>     
    /// <param name="password">Password given by Chat User</param>     
    /// <param name="connID">Connection ID provided by server</param>     
    /// </summary>
 
    //Public method must return a result of type MethodResponse 
    public MethodResponse Login(string username, string password, int connID)
    {
        //Create a new MethodResponse
        MethodResponse MethodResponse = new MethodResponse();

        bool loginFailed = true;

        if (password == "password")
            loginFailed = false;

        if (loginFailed)
        {
            //Create a new Packet LOGIN_RESPONSE and send Packet to the sender
            Packet LoginResponse = new Packet("LOGIN_RESPONSE");
            //Add a boolean value to Packet. It means login failed
            LoginResponse.AddBoolean(false);
            //Add Packet to MethodResponse
            MethodResponse.AddPacket(LoginResponse);
        }
        else
        {
            Packet LoginResponse = new Packet("LOGIN_RESPONSE");
            LoginResponse.AddBoolean(true);//It means successful login
            //Add a int value to Packet. It provides client the connection ID for future use
            LoginResponse.AddInt32(connID);

            //Announce to all clients a new user joined
            //Set sendToAll parameter to true (default false)
            //if you want to send Packet to all clients
            Packet UserJoin = new Packet("USER_JOIN", true);
            //Add the name of the Chat User joined
            UserJoin.AddString(username);

            //Add Packets to MethodResponse
            MethodResponse.AddPacket(LoginResponse);
            MethodResponse.AddPacket(UserJoin);

            Users.Add(new User(connID, username)); //Add the Chat User to a List

            //Write on server console from dll
            Logging.WriteLine("User: " + username + " has joined the chat", LogLevel.Information);
        }

        return MethodResponse; //Return MethodResponse to Server
    }

    ...Other chat methods...

    /// <summary>
    /// Must always be declared. it will be called when a client disconnect  
    /// <param name="connID">Connection ID provided by server</param>     
    /// </summary>
    public void OnClientDisconnect(int connID)
    {
        if (GetUserByConnID(connID) != null)
        {
            Logging.WriteLine("User: " + GetUserByConnID(connID).Name + 
              " has left the chat", LogLevel.Information);
            Users.Remove(GetUserByConnID(connID));
        }
    }

    /// <summary>
    /// Must always be declared. it will be called when a client connect  
    /// <param name="connID">Connection ID provided by server</param>     
    /// </summary>
    public void OnClientConnect(int connID)
    {

    }
} 

Client

This is the client of a simple Chat program

You can connect to the server and do the login. Then you will be able to send a message to all the users connected or send a whisper 

C#
static void Main(string[] args)
{
    conn = new ServerConnection();
    //Connect to the server on 127.0.0.1:8888
    conn.Connect(IPAddress.Parse("127.0.0.1"), 8888);
    //Handle received Packet
    conn.onPacketReceive += new ServerConnection.onPacketReceiveHandler(HandlePacket);
    
    
    ...
} 
/// <summary>
/// Ask Login
/// </summary>
static void Login()
{
    Console.WriteLine("Write username");
    username = Console.ReadLine(); //Read user input
    Console.WriteLine("Write password");
    string password = Console.ReadLine(); //Read user input

    Packet Login = new Packet("LOGIN"); //Create a new Packet LOGIN
    Login.AddString(username); //Add the username to Packet
    Login.AddString(password); //Add the password to Packet

    conn.Send(Login); //Send the Packet to the server
}  
/// <summary>
/// Handle the received Packet
/// <param name="sender">Class on which the event has been fired</param>     
/// <param name="Packet">The received packet</param>     
/// </summary>
static void HandlePacket(object sender, Packet Packet)
{
    switch (Packet.Header)
    {
        case "LOGIN_RESPONSE": //Received LOGIN_RESPONSE Packet
            {
                bool loginResponse = Convert.ToBoolean(Packet.bodyValues[0]);
                //Get Login Response from Packet Body

                if (!loginResponse)
                {
                    Console.WriteLine("Login failed");
                    Login(); //Ask login until logged
                }
                else
                {
                    id = int.Parse(Packet.bodyValues[1].ToString());
                    //Get Connection ID from Packet Body

                    Console.WriteLine("Login Successful");
                    Logged = true; //User has logged in
                }
            }
            break;
            
            ...
    }
} 

Screenshot

Image 3 

Web Service 

I also implemented a Flexible Web Service to create a sort of web server panel

Simple web server panel for the chat

webServiceConnector.php 

This script performs the login on the Web Service and it calls GetUserCount and GetUserList methods. 

The response is expressed in json format

PHP
<?php
if (isset($_GET['readData']))
{
    //Connect to WebService
    $client = new SoapClient("http://localhost:8000/FlexibleServer/?wsdl",array(
    'login' => "admin", 'password' => "password"));

    try 
    {         
	//Get user count
        $response = $client->__soapCall("GetUserCount",array());
        $arr=objectToArray($response);
	//Get user list
        $response2 = $client->__soapCall("GetUserList",array());
        $arr2=objectToArray($response2);
	//Merge results
        $result = array_merge($arr,$arr2);
	//Encode array to json
        echo json_encode($result);
    } 
    catch (SoapFault $exception)
    {
        trigger_error("SOAP Fault: (faultcode: {$exception->faultcode}, faultstring:
        {$exception->faultstring})");

        var_dump($exception);
    }
}
?>

Javascript function

This function performs an ajax request calling webServiceConnector.php to get the json response. 

JavaScript
function read()
{
    var xmlhttp;
    xmlhttp = GetXmlHttpObject();
    if(xmlhttp == null)
    {
      alert("Boo! Your browser doesn't support AJAX!");
      return;
    }
    xmlhttp.onreadystatechange = stateChanged;
	
    //Get page source
    xmlhttp.open("GET", "http://127.0.0.1/webServiceConnector.php?readData", true);
    xmlhttp.send(null);

    function stateChanged()
    {	  
      if(xmlhttp.readyState == 4)
      {        
	//Parse json from source
        var obj = jQuery.parseJSON(xmlhttp.responseText);    
	//Refresh gage with user count value
        g1.refresh(obj["GetUserCountResult"]);
	//Get textarea element
        var txtarea = document.getElementById("txtarea");
        if (obj["GetUserListResult"]["string"] != null)
        {
            var length = obj["GetUserListResult"]["string"].length;
            
            var s = "";
	    //Append Users' Names
            for (var i = 0; i < length; i++) {
              s += obj["GetUserListResult"]["string"][i];
	    }    
	    //Display names
	    txtarea.innerHTML = s;
	    txtarea.scrollTop = txtarea.scrollHeight;
        }
        else
        {
            txtarea.innerHTML = "";
            txtarea.scrollTop = txtarea.scrollHeight;
        }
        
	//Refresh every second
        setTimeout("read()",1000);    
      }
    }
    function GetXmlHttpObject()
    {     
      if(window.XMLHttpRequest){
        return new XMLHttpRequest();
      }

      if(window.ActiveXObject){
        return new ActiveXObject("Microsoft.XMLHTTP");
      }
      return null;
    }
}  

Inside the DLL

Methods that will be called from a php script 

C#
public class WebserviceHandler : IWebservice
{
    public string[] GetUserList()
    {
        List<string> Names = new List<string>();
        foreach (User User in PacketHandler.Users)
            Names.Add(User.Name + "\n");
        return Names.ToArray();
    }

    public int GetUserCount()
    {
        return PacketHandler.Users.Count;
    }
}


[ServiceContract]
public interface IWebservice
{
    [OperationContract]
    string[] GetUserList();
    [OperationContract]
    int GetUserCount();
}

Server code to start the Web Service

C#
/// <summary>
/// Start webService
/// </summary>     
public void Start()
{
    Uri baseAddress = new Uri("http://" + IP.ToString() + ":" + Port + "/FlexibleServer/");

    //Get WebserviceHandler from User Created DLL
    Type Webservice = packetHandlerDllAssembly.GetType("WebserviceHandler");
    //Get Webservice interface from User Created DLL
    Type Interface = packetHandlerDllAssembly.GetType("IWebservice");
    //Get webService methods created by user
    foreach (MethodInfo m in Interface.GetMethods(BindingFlags.DeclaredOnly | 
             BindingFlags.Public | BindingFlags.Instance)) 
    {
        string param = "";

        //Get method parameters
        foreach (ParameterInfo pParameter in m.GetParameters())
        {
            param += pParameter.Name + " (" + pParameter.ParameterType.Name + ") ";
        }

        if (param == "")
            param = "none ";

        Core.GetLogging().WriteLine("webService Method name: " + m.Name + 
          " parameters: " + param + "registered", LogLevel.Information);
    }

    // Create the ServiceHost. Bind on http://ip:port/FlexibleServer/
    ServiceHost selfHost = new ServiceHost(Webservice, baseAddress);

    //Binding to configure endpoint
    BasicHttpBinding http = new BasicHttpBinding();

    //Set a basic username/password authentication
    http.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;

    http.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

    try
    {
         //Add the endpoint to the service host
        ServiceEndpoint endpoint = selfHost.AddServiceEndpoint(
          Interface, http, "RemoteControlService");
        //Add the Custom webService Behavior to endpoint
        endpoint.Behaviors.Add(new webServiceEvent());

        //Set the custom username/password validation
        selfHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = 
          UserNamePasswordValidationMode.Custom;
        selfHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = 
          new LoginValidator();

        // Enable metadata publishing.
        ServiceMetadataBehavior smb = 
          selfHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
        if (smb == null)
        {
            smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            selfHost.Description.Behaviors.Add(smb);
        }

        try
        {
            //Start webService
            selfHost.Open();
            Core.GetLogging().WriteLine("webService is ready on http://" + 
              IP.ToString() + ":" + Port + "/FlexibleServer/", LogLevel.Information);
        }
        catch (Exception e)
        {
            if (e is AddressAccessDeniedException)
            {
                Core.GetLogging().WriteLine("Could not register url: http://" + IP + 
                  ":" + Port + ". Start server as administrator", LogLevel.Error);
            }

            if (e is AddressAlreadyInUseException)
            {
                Core.GetLogging().WriteLine("Could not register url: http://" + 
                  IP + ":" + Port + ". Address already in use", LogLevel.Error);
            }

            Core.GetLogging().WriteLine("webService aborted due to an exception", LogLevel.Error);
        }
    }
    catch (CommunicationException ce)
    {
        Console.WriteLine("An exception occurred: {0}", ce.Message);
        selfHost.Abort();               
    }
}

Screenshot

Image 4

How to - MysqlConnector 

Add a reference to MysqlConnector.dll and MySql.Data.dll in TestDLL 

Add 

C#
using System.Data;
using MysqlConnector;    

Example: Initialize mysql connection

C#
public class PacketHandler
{
    public static List<User> Users;
    private Logging Logging;
    public Mysql MysqlConn;
    /// <summary>
    /// Initialize variables  
    ///  </summary>
    public PacketHandler()
    {
        Users = new List<User>();
        Logging = new Logging();
        Logging.MinimumLogLevel = 0;

        MysqlConn = new Mysql();
        MysqlConn.Connect("127.0.0.1", 3306, "root", "password", "databasename");

        MysqlConn.GetClient();
    }
	
	...
} 

Chat login method using MySql

C#
/// <summary>
/// Handle Chat User Login
/// <param name="username">Username given by Chat User</param>     
/// <param name="password">Password given by Chat User</param>     
/// <param name="connID">Connection ID provided by server</param>     
/// </summary>

//Public method must return a result of type MethodResponse 
public MethodResponse Login(object username, object password, int connID)
{
	//Create a new MethodResponse
	MethodResponse MethodResponse = new MethodResponse();

	//Check if user exists from mysql
	DataRow Row = MysqlConn.ReadDataRow("SELECT * FROM users where username = '" + username + "' AND password = '" + password + "'");

	bool loginFailed = true;

	if (Row != null)
	{
		loginFailed = false;
	}

	if (loginFailed)
	{
		//Create a new Packet LOGIN_RESPONSE and send Packet to the sender
		Packet LoginResponse = new Packet("LOGIN_RESPONSE");
		//Add a boolean value to Packet. It means login failed
		LoginResponse.AddBoolean(false);
		//Add Packet to MethodResponse
		MethodResponse.AddPacket(LoginResponse);
	}
	else
	{
		Packet LoginResponse = new Packet("LOGIN_RESPONSE");
		LoginResponse.AddBoolean(true);//It means successful login
		//Add a int value to Packet. It provides client the connection ID for future use
		LoginResponse.AddInt32(connID);

		//Announce to all clients a new user joined
		//Set sendToAll parameter to true (default false) if you want to send Packet
		//to all clients
		Packet UserJoin = new Packet("USER_JOIN", true);
		//Add the name of the Chat User joined
		UserJoin.AddString(username.ToString());

		//Add Packets to MethodResponse
		MethodResponse.AddPacket(LoginResponse);
		MethodResponse.AddPacket(UserJoin);

		Users.Add(new User(connID, username.ToString())); //Add the Chat User to a List

		//Write on server console from dll
		Logging.WriteLine("User: " + username + " has joined the chat", LogLevel.Information);
	}

	return MethodResponse; //Return MethodResponse to Server
}  

 

  • Compile TestDLL 
  • Move TestDLL.dll, MysqlConnector.dll, MySql.Data.dll to the server directory 

 

 

How to test the project 

 

  • Open FlexibleServer.exe located in /Flexible Server/bin/Debug as administrator 
  • Open one or more ChatClient.exe located in /Chat Client/bin/Debug
  • Insert an username 
  • The password is "password" 
  • To send a whisper write "whisper target message" (Replace target with the name of the user you want to send the message to)

Create your own DLL

 

  • Create a new DLL project in Visual Studio
  • Add reference to System.ServiceModel
  • Add reference to Logging.dll to write on console using LogLevel
  • (Optional) Add reference to MysqlConnector.dll and MySql.Data.dll if you want add Mysql support
  • Write DLL base code
C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ServiceModel;
//using MysqlConnector; 

//PacketHandler class must be public to let server reads methods
public class PacketHandler
{
    /// <summary>
    /// Initialize variables/mysql connection etc..
    ///  </summary>
    public PacketHandler()
    {
      
    }
	
    public MethodResponse Yourincomingpacketname(string incomingpacketparameter, etc)
    {
	MethodResponse MethodResponse = new MethodResponse();
	
	... Your code ....
	
	return MethodResponse();
    }
    /// <summary>
    /// Must always be declared. it will be called when a client disconnect  
    /// <param name="connID">Connection ID provided by server</param>     
    /// </summary>
    public void OnClientDisconnect(int connID)
    {
      
    }

    /// <summary>
    /// Must always be declared. it will be called when a client connect  
    /// <param name="connID">Connection ID provided by server</param>     
    /// </summary>
    public void OnClientConnect(int connID)
    {

    }
} 

 

  • Compile DLL 
  • Move DLL and its reference to server directory 
  • Change the packetHandlerDLL value inside flexibleserver-config.conf with your DLL name 

 

Points of interest 

Configuration file

Contains all server settings 

## Flexible Server Configuration File
## Must be edited for the server to work

## Server Configuration
MinimumLogLevel=0
tcp.bindip=127.0.0.1
tcp.port=8888
packetHandlerDLL=TestDLL.dll
enableWebService=1
webservice.bindip=127.0.0.1
webservice.port=8000
webservice.username=admin
webservice.password=password 

 

Logging 

Class for better console readability using log level

 

Debug 
Information  
Warning 
Error 
Success  4  

WriteLine Code

C#
/// <summary>
/// Write Line to Console with specified Log Level
/// <param name="Line">Line text</param>
/// <param name="Level">LogLevel</param>
/// </summary>
public void WriteLine(string Line, LogLevel Level)
{
 	//Don't write line to Console if LogLevel is lower than MinimumLogLevel
	if (Level >= MinimumLogLevel)
	{
		DateTime _DTN = DateTime.Now;
		StackFrame _SF = new StackTrace().GetFrame(1); 
		Console.Write("[");
		Console.ForegroundColor = ConsoleColor.Green;
		//Write current Class.Method
		Console.Write(_SF.GetMethod().ReflectedType.Name + "." + _SF.GetMethod().Name);
		Console.ForegroundColor = ConsoleColor.Gray;
		Console.Write("] ยป ");

		//Change color based on log level
		if (Level == LogLevel.Debug)
			Console.ForegroundColor = ConsoleColor.Gray;
		else if (Level == LogLevel.Error)
			Console.ForegroundColor = ConsoleColor.Red;
		else if (Level == LogLevel.Information)
			Console.ForegroundColor = ConsoleColor.Yellow;
		else if (Level == LogLevel.Success)
			Console.ForegroundColor = ConsoleColor.Green;
		Console.WriteLine(Line);
		Console.ForegroundColor = ConsoleColor.Gray;
	}
} 

 

 

 

MethodResponse 

This class allows the server and dll to communicate with each other 

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

public class Packet
{
    public string Header; //Name of the Packet
    public List<object> bodyValues; //List of Values
    public bool sendToAll; //Send to all Clients?
	
    //List of Connection ID to which the server will send the Packet
    public List<int> sendTo;
	
    //Delimit Packet header and Packet body values using char(1)
    private char Delimiter = (char)1;

    /// <summary>
    /// New Packet</summary>
    /// <param name="Header">The Packet header/name</param>     
    /// <param name="sendToAll">Send to all Clients?</param> 
    /// <param name="sendTo">List of Connection ID to which the server will send the Packet</param> 
    /// </summary>
    public Packet(string Header, bool sendToAll = false, List<int> sendTo = null)
    {       
        this.Header = Header;
        this.bodyValues = new List<object>();
        this.sendToAll = sendToAll;
        this.sendTo = sendTo;
    }

    /// <summary>
    /// Add integer value to Packet body</summary>
    /// <param name="Value">Integer value</param>     
    /// </summary>
    public void AddInt32(int Value) //Add a integer to Packet body
    {
        bodyValues.Add(Value);
    }

    /// <summary>
    /// Add string value to Packet body</summary>
    /// <param name="Value">String value</param>    
    /// </summary>
    public void AddString(string Value) //Add a string to Packet body
    {
        bodyValues.Add(Value);
    }

    /// <summary>
    /// Add boolean value to Packet body</summary>
    /// <param name="Value">Boolean value</param>     
    /// </summary>
    public void AddBoolean(bool Value) //Add a boolean to Packet body
    {
        bodyValues.Add(Value);
    }

    /// <summary>
    /// Return the final string value to be sent to client
    /// </summary>  
    public string GetPacketString()
    {
        string PacketString = Header;
        foreach (object o in bodyValues)
        {
			//Add delimiter to each Packet body value
            PacketString += Delimiter.ToString() + o.ToString();
        }
        return PacketString;
    }
}

public class MethodResponse
{    
    public List<Packet> Packets; //List of Packets

    public MethodResponse()
    {        
        Packets = new List<Packet>();
    }

    /// <summary>
    /// Add new Packet to MethodResponse</summary>
    /// <param name="Packet">Packet</param>     
    public void AddPacket(Packet Packet)
    {
        Packets.Add(Packet);
    }
}

About perfomance

Normal server

 

Image 5 

It tooks about 12ms to parse packet and sending response

 

Flexible server 

Image 6

It tooks 27ms to parse packet, invoke the method in the DLL and send the packets contained in MethodResponse 

Conclusion: Flexible server has to do more operation. Of course it will slower. it's not recommended for large projects. 

 

Conclusion

Thanks for reading this article; I hope you liked it.

Let me know if you find bugs or you have suggestions to improve it, i'll be happy to fix/implement them  

History        

  • Version 1.0 - 19/07/2013: Initial release  
  • 20/07/2013: (optional) MysqlConnector including examples, How to create your own DLL, Points of interest, added code comments on php/javascript scripts 
  • 23/07/2013:  Added table of contents, pros and cons, talk about perfomance, conclusion 

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)