Click here to Skip to main content
Click here to Skip to main content

Flexible TCP/IP Server with Web Service using DLL

, 22 Jul 2013 CPOL
Rate this:
Please Sign up or sign in to vote.
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 changing the DLL name in the server configuration with the new one and done! 

The project contains:

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

Logic diagram

Server screenshot

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 server and by the dll to communicate each other. 

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

//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 client first it parses message to Packet class and then it passes the Packet to HandlePacket method 

/// <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 client connection id as parameter in the DLL 

/// <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 message from client to Packet class

Parse the incoming client message to Packet class.

First we get Packet Header/name 

Then we need to parse the Packet body values type from string else we will get exception "parameter type mismatch" when the server will invoke DLL method if a parameter is type of int and it passes a string 

/// <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

Server parses invoke response value to MethodResponse

Finally it loops Packets contained in MethodResponse and sends them to client/s 

/// <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 user will only write this to make the server working

Use private flag on the method to prevent the server loads these methods 

PacketHandler must contains OnClientConnect and OnClientDisconnect methods 

Every public method must return type MethodResponse 

//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

The client of a simple Chat

Connect to the server, ask login until logged, send message to all or send a whisper 

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

 

Web Service 

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

Simple web server panel for the chat

webServiceConnector.php 

Performs login on Web Service and call GetUserCount and GetUserList

Echo the result using json 

<?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

Performs ajax request to get the result and display them  

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 

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 Web Service

/// <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

How to - MysqlConnector 

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

Add 

using System.Data;
using MysqlConnector;    

 Initialize mysql connection example 

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

/// <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 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 a username 
  • Password is "password" 
  • To send a whisper write "whisper target message"

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
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 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

/// <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 

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

 

It tooks about 12ms to parse packet and sending response

Flexible server 

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)

Share

About the Author

Rogai Lorenzo
Student
Italy Italy

Comments and Discussions

 
QuestionNice Pinmembergmav21-Jul-13 20:50 
AnswerRe: Nice PinmemberRogai Lorenzo21-Jul-13 21:11 
GeneralMy vote of 5 PinmemberKyrosWeb20-Jul-13 1:35 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.141030.1 | Last Updated 23 Jul 2013
Article Copyright 2013 by Rogai Lorenzo
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid