سرور TCP/IP انعطاف پذیر با وب سرویس توسط DLL

یکشنبه 27 دی 1394

سرور انعطاف پذیر از یک DLL ایجاد شده برای مدیریت درخواست های ورودی و خروجی کلاینت استفاده می کند. در این نمونه برنامه می خواهیم از نوشتن مجدد یک سرور پایه در هر بار جلوگیری کرده و فقط با نوشتن بسته متدهای از قبل ایجاد شده، یک DLL اضافه کنیم.

سرور TCP/IP انعطاف پذیر با وب سرویس توسط DLL

تصور کنید می خواهیم از یک سرور چت به کنترل از راه دور یک موس سوئیچ کنیم و این کار باید در کمتر از یک دقیقه انجام شود. به نظرتون عالی نیست؟ برای این کار فقط یک DLL را جایگزین کرده یا نام DLL را در تنظیمات سرور با یک DLL جدید تغییر می دهیم.

این پروژه شامل موار زیر است:

سرور انعطاف پذیر- سرور

چت سمت کلاینت- یک چت ساده برای تست این که سرور کار می کند یا خیر ایجاد می کنیم.

Client Utils- که شامل کلاس هایی برای کمک به اتصال به سرور می باشد.

ورود به سیستم(Logging)- کلاسی برای خوانایی بهتر کنسول با استفاده از سطح ورود به سیستم می باشد.

MethodResponse- کلاسی است که در سرور و DLL ایجاد شده تا با یکدیگر ارتباط برقرار کنند.

TestDLL- یک DLL ساده برای مدیریت ورودی ها به چت کلاینت می باشد. 

MysqlConnector(اختیاری)- شامل کلاسی برای کمک به اتصال به سرور mysql می باشد و Query ها را انجام می دهد.

نحوه عملکرد پروژه

و خروجی کار به صورت زیر می باشد:

معرفی مزایا و معایب استفاده از سرور انعطاف پذیر یا Felexible Server

مزایا

- استفاده از آن آسان تر است.

- برای استفاده از آن کافیست DLL های خود را با متدهایی که نیاز دارید بنویسید.

- شما می توانید هدف سرور را در کمتر از یک دقیقه با جایگزینی DLL مورد نظر تغییر دهید.

معایب

- کمی کند عمل می کند.

هدف ما در این برنامه چیست؟

ما می خواهیم یک چت کوچک بین کلاینت/سرور ایجاد کنیم تا ببینیم این سرور چگونه کار می کند.

سرور

بارگذاری DLL

این سرور مونتاژی از DLL مشخص را بارگذاری می کند( در این مثال DLL مورد نظر TestDLL.dll می باشد.)

این DLL باید شامل کلاس PacketHandler باشد. PacketHandler باید شامل متدهای OnClientConnect و OnClientDisconnect باشد، و موقعی از آنها استفاده می کنیم که بخواهیم برخی کارها را در زمانی که اتصال یک کلاینت وصل یا قطع است، انجام دهیم.

این سرور فقط متدهای عمومی(Public) را با نوع بازگشتی MethodResponse بارگذاری می کند.

MethodResponse هم توسط سرور و هم dll برای اتصال با یکدیگر استفاده می شود. در نهایت سرور اطلاعات متدهای داخل یک لیست برای فراخوانی آنها ذخیره می کند:

//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);
}

پیام جدید از کلاینت

زمانی که سرور یک پیام جدید از کلاینت دریافت میکند، اولین بار پیام را به کلاس Packet تجزیه کرده و سپس این بسته را به متد HandlePacket  ارسال می کند:

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

متدهای OnClientConnect/OnClientDisconnect با ارسال id اتصال کلاینت در 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 });
}

تجزیه پیام های وارده از سمت کلاینت

پیام های دریافتی از سمت مشتری به کلاس Packet تجزیه می شوند.

در ابتدا ما یک نام یا هدر بسته(Packet) را دریافت می کنیم، سپس باید نوع مقادیر بدنه Packet را به نوع درست و صحیح تجزیه کنیم(به طور مثال Int، float و غیره). این کار لازم است، زیرا در غیر این صورت  یک استثنا به صورت " عدم تطابق نوع پارامتر " یا به صورت انگلیسی" 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 یا بسته مدیریتی

مدیریت بسته های تجزیه شده

سرور متد Packet دریافتی یک DLL را که از قبل تجزیه شده، با پارامترهای مشخص فراخوانی می کند. در DLL به بسته مربوط استناد کرده و پارامترهایی که از قبل تجزیه شدند را دریافت می کند. متد فراخوانی شده یک شیئ برمی گرداند که مقدار برگشتی این متد را نگه می دارد که نیاز دارد به نوع MethodResponse تجزیه شود.

در نهایت با Packet ها حلقه ای را تشکلیل می دهند که شامل MethodResponse بوده و یک پیام را به کلاینت یا کلاینت ها می فرستند.

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

این DLL ای است که توسط آن می توانید سرور خود را به صورتی که نیاز دارید سفارشی کنید. در اینجا می توانید متدهای سفارشی خود را بنویسید.

اگر می خواهید سرور بارگذاری نشود، می توانید از یک Flag خصوصی در این متدها استفاده کنید.

PacketHandler باید شامل هردو متد OnClientConnect و OnClientDisconnect باشد

هر متد عمومی باید یک نوع بازگشتی از نوع 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)
    {

    }
} 

کلاینت

این یک کلاینت از برنامه چت ساده است. شما می توانید به سرور متصل شده و Login را انجام دهید. سپس قادر خواهید بود که یک پیام به تمام کاربران متصل بفرستید.

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;
            
            ...
    }
} 

وب سرویس

ما همچنین یک سرویس وب منعطف را پیاده سازی کرده تا یک نوع از پنل وب سرور مرتب سازی شده ایجاد کنیم.

یک پنل وب سرور ساده برای چت

webServiceConnector.php

این اسکریپت یک ورود به وب سرویس را انجام می دهد و متدهای GetUserCount و GetUserList را فراخوانی می کند.

پاسخ در قالب 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

این تابع یک درخواست ajax را انجام داده و webServiceConnector.php را برای در یافت پاسخ Json فراخوانی می کند.

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

داخل DLL

متدهایی که از یک اسکریپت فراخوانی می شود.

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();
}

کد سرور برای شروع کار با وب سرویس

/// <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();               
    }
}

چگونگی استفاده از MysqlConnector  (اختیاری)

یک رفرنس به MysqlConnector.dll و MySql.Data.dll در TestDLL اضافه کنید.

اضافه کردن

using System.Data;
using MysqlConnector;

مثال: مقدار دهی اولیه اتصال mysql 

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 با استفاده از 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
} 

TestDLL را کامپایل کنید.

به TestDLL.dll, MysqlConnector.dll, MySql.Data.dll در دایرکتوری سرور بروید.

چگونه پروژه را تست کنیم؟

FlexibleServer.exe واقع شده در  /Flexible Server/bin/Debug را به عنوان مدیر(Admin) باز کنید.

یک یا بیشتر ChatClient.exe قرار گرفته در /Chat Client/bin/Debug را نیز باز کنید.

یک نام کاربری وارد کنید.

کلمه عبور به صورت "password" است.

برای ارسال یک whisper عبارت " whisper target message" را بنویسد (تارگت را با یک نام کاربر که می خواهید به آن پیام ارسال کنید جایگزین کنید).

ایجاد DLL

یک پروژه DLL جدید در Visual Studio ایجاد کنید.

رفرنس را به System.ServiceModel اضافه کنید

برای نوشتن روی کنسول با استفاده از LogLevel ، رفرنس Logging.dll را اضافه کنید.

(اختیاری) اگر می خواهید پشتیبانی Mysql را استفاده کنیدرفرنس MysqlConnector.dll و MySql.Data.dll را اضافه کنید. 

کد پایه را بنویسید.

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

    }
}

DLL را کامپایل کنید.

به DLL رفته و آن را به دایرکتوری سرور رفرنس دهید.

مقدار packetHandlerDLL را داخل flexibleserver-config.conf با نام DLL خود تغییر دهید.

نکات مورد توجه

فایل پیکربندی(Configuration file)

این فایل شامل تمام تنظیمات سرور می باشد.

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

MethodResponse

این کلاس به سرور و DLL اجازه می دهد که با یکدیگر ارتباط برقرا ر کنند.

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);
    }
}

آموزش سی شارپ

فایل های ضمیمه

برنامه نویسان

نویسنده 3355 مقاله در برنامه نویسان
  • C#.net
  • 2k بازدید
  • 1 تشکر

کاربرانی که از نویسنده این مقاله تشکر کرده اند

در صورتی که در رابطه با این مقاله سوالی دارید، در تاپیک های انجمن مطرح کنید