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






4.98/5 (19投票s)
灵活的服务器,使用用户创建的 DLL 来处理进出的客户端数据包。
目录
引言
我创建这个项目的目的是为了不必每次都重新编写服务器基础,而只需编写我需要的 DLL 中的数据包方法。
想象一下,从聊天服务器切换到远程鼠标控制或其他任何东西,不到一分钟就能完成。是不是很酷?只需替换 DLL 或在服务器配置中将 DLL 名称更改为新的即可!
项目包含
- 灵活服务器 - 服务器
- 聊天客户端 - 简单的聊天客户端,用于测试服务器是否正常工作
- 客户端工具 - 包含帮助您连接到服务器的类
- 日志记录- 用于通过日志级别提高控制台可读性的类
- MethodResponse- 服务器和 DLL 之间用于通信的类
- TestDLL - 用于处理进出的聊天客户端数据包的简单 DLL
- (可选)MysqlConnector - 包含帮助您连接到 mysql 服务器并执行查询的类
逻辑图

服务器截图

优点和缺点
优点
- 更容易
- 您只需编写包含所需方法的 DLL
- 通过更改 DLL,您可以在不到一分钟的时间内更改服务器的目的
缺点
- 速度稍慢。请阅读 关于性能章节了解更多信息
目标是什么?
我们将创建一个小型的聊天服务器/客户端来测试服务器的工作方式
服务器
加载 DLL
服务器加载指定 DLL 的程序集(在本例中为 TestDLL.dll)。
DLL 必须包含 PacketHandler 类。PacketHandler 必须包含 OnClientConnect 和 OnClientDisconnect 方法,如果我们想在客户端连接或断开连接时执行某些操作,我们将使用它们。  
服务器仅加载返回类型为 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 必须同时包含 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)
    {
    }
} 
客户端
这是简单的聊天程序的客户端。
您可以连接到服务器并登录。然后,您将能够向所有连接的用户发送消息或进行耳语。
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 服务的登录,并调用 GetUserCount 和 GetUserList 方法。 
响应以 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 
日志记录
用于通过日志级别提高控制台可读性的类
| 调试 | 0 | 
| 信息 | 1 | 
| 警告 | 2 | 
| 错误 | 3 | 
| 成功 | 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: 添加了目录、优缺点、关于性能的讨论、结论



 
 