Flexible TCP/IP Server with Web Service using DLL






4.98/5 (19 votes)
Flexible server that use User Created DLL to handle incoming/outgoing client packets.
- Download source - 182.8 KB
- Download demo/executables - 71.3 KB
- (Optional) Download MysqlConnector - 203 KB
Table of contents
- Introduction
- Pros and cons
- What's the goal?
- Server
- DLL
- Client
- Web Service
- How to - Mysql Connector
- How to test the project
- Create your own DLL
- Points of interest
- About perfomance
- Conclusion
- History
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 levelMethodResponse
- 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
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
- A little slower. Read more in About perfomance chapter
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
//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
/// <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
/// <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".
/// <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 Packet
s contained in MethodResponse
and sends the message back to the 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 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
//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
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 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
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.
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 the 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 a reference to MysqlConnector.dll and MySql.Data.dll in TestDLL
Add
using System.Data;
using MysqlConnector;
Example: Initialize mysql connection
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 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
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 | 0 |
Information | 1 |
Warning | 2 |
Error | 3 |
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