65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 DLL 的灵活 TCP/IP 服务器及 Web 服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.98/5 (19投票s)

2013年7月19日

CPOL

5分钟阅读

viewsIcon

56559

downloadIcon

2862

灵活的服务器,使用用户创建的 DLL 来处理进出的客户端数据包。

目录

 

 

引言  

我创建这个项目的目的是为了不必每次都重新编写服务器基础,而只需编写我需要的 DLL 中的数据包方法。 

想象一下,从聊天服务器切换到远程鼠标控制或其他任何东西,不到一分钟就能完成。是不是很酷?只需替换 DLL 或在服务器配置中将 DLL 名称更改为新的即可! 

项目包含

  • 灵活服务器 - 服务器
  • 聊天客户端 - 简单的聊天客户端,用于测试服务器是否正常工作
  • 客户端工具 - 包含帮助您连接到服务器的类
  • 日志记录 - 用于通过日志级别提高控制台可读性的类 
  • MethodResponse - 服务器和 DLL 之间用于通信的类
  • TestDLL - 用于处理进出的聊天客户端数据包的简单 DLL
  • (可选)MysqlConnector - 包含帮助您连接到 mysql 服务器并执行查询的类 

逻辑图

服务器截图

优点和缺点 

优点 

 

  • 更容易
  • 您只需编写包含所需方法的 DLL
  • 通过更改 DLL,您可以在不到一分钟的时间内更改服务器的目的  

 

缺点  

 

  • 速度稍慢。请阅读 关于性能章节了解更多信息 

 

目标是什么?  

我们将创建一个小型的聊天服务器/客户端来测试服务器的工作方式  

服务器 

加载 DLL  

服务器加载指定 DLL 的程序集(在本例中为 TestDLL.dll)。 

DLL 必须包含 PacketHandler 类。PacketHandler 必须包含 OnClientConnectOnClientDisconnect 方法,如果我们想在客户端连接或断开连接时执行某些操作,我们将使用它们。  

服务器仅加载返回类型为 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 类,然后将 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 的 Header/名称 

然后,我们需要将数据包体值的类型从字符串解析为正确的类型(int、float 等)。 这是必需的,否则我们会收到“参数类型不匹配”的异常。 

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

处理数据包

处理已解析的 Packet

服务器调用 DLL 中相应的 Packet 方法,传入之前已解析的参数。 

invoke 方法返回一个对象,其中包含需要解析为 MethodResponse 类型的方法的返回值。 

最后,它循环 MethodResponse 中包含的 Packet,并将消息发送回客户端。 

 

/// <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 是您可以根据需要自定义服务器的地方。您可以在此处编写自定义方法。 

您可以对不想让服务器加载的方法使用 private 关键字。 

PacketHandler 必须同时包含 OnClientConnectOnClientDisconnect 方法。 

每个公共方法都必须返回 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)
    {

    }
} 

客户端

这是简单的聊天程序的客户端。 

您可以连接到服务器并登录。然后,您将能够向所有连接的用户发送消息或进行耳语。 

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

屏幕截图

 

Web服务 

我还实现了一个灵活的 Web 服务,用于创建一个类似 Web 服务器面板的功能。 

聊天用的简单 Web 服务器面板

webServiceConnector.php 

此脚本执行 Web 服务的登录,并调用 GetUserCountGetUserList 方法。 

响应以 JSON 格式表示。 

<?php
if (isset($_GET['readData']))
{
    //Connect to WebService
    $client = new SoapClient("https://: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 中

将从 php 脚本调用的方法 

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

用于启动 Web 服务的服务器代码

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

在 TestDLL 中添加对 MysqlConnector.dll 和 MySql.Data.dll 的引用。 

添加 

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

使用 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 移动到服务器目录。 

 

 

如何测试项目 

 

  • 以管理员身份打开位于 /Flexible Server/bin/Debug 的 FlexibleServer.exe。 
  • 打开位于 /Chat Client/bin/Debug 的一个或多个 ChatClient.exe。 
  • 输入用户名。 
  • 密码是“password”。 
  • 要发送耳语,请键入“whisper target message”(将 target 替换为您想发送消息的用户姓名)。 

创建您自己的 DLL

 

  • 在 Visual Studio 中创建一个新的 DLL 项目。 
  • 添加对 System.ServiceModel 的引用。 
  • 添加对 Logging.dll 的引用,以使用 LogLevel 写入控制台。 
  • (可选)添加对 MysqlConnector.dll 和 MySql.Data.dll 的引用,如果您想添加 Mysql 支持。 
  • 编写 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 及其引用移动到服务器目录。 
  • 在 flexibleserver-config.conf 中更改 packetHandlerDLL 的值为您的 DLL 名称。 

 

关注点 

配置文件

包含所有服务器设置。 

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

 

日志记录 

用于通过日志级别提高控制台可读性的类

 

调试 
信息  
警告 
错误 
成功  4  

WriteLine 代码

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

此类允许服务器和 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);
    }
}

关于性能

普通服务器

 

 

解析数据包并发送响应大约需要 12 毫秒。 

 

灵活服务器 

解析数据包、调用 DLL 中的方法并将 MethodResponse 中包含的数据包发送回去大约需要 27 毫秒。 

结论:灵活服务器需要执行更多操作。当然,它会更慢。不推荐用于大型项目。 

 

结论

感谢阅读本文,希望您喜欢。 

如果您发现错误或有改进建议,请告诉我,我很乐意修复/实现它们。  

历史       

  • 版本 1.0 - 2013/07/19:初始发布  
  • 2013/07/20: (可选)包含 MysqlConnector 示例、如何创建您自己的 DLL、关注点、为 php/javascript 脚本添加了代码注释 
  • 2013/07/23:  添加了目录、优缺点、关于性能的讨论、结论 
© . All rights reserved.