使用DLL发布Web Service的智能TCP/IP服务端

标签: dll WebService C#
发布时间: 2013/7/24 9:46:38

介绍  

我想让这个项目完成用一个动态库实现数据包功能而不用每次重构服务端。试想一下可以在不到一分钟内实现从聊天服务端到鼠标远程控制服务端或其他的转换,是不是很酷?只需要替换DLL或者在服务端配置中修改新DLL的文件名即可。

项目包含:

  • Flexible Server – 服务端 

  • Chat Client – 简单的聊天客户端用于测试服务端 

  • 客户端工具库 – 包含连接到服务端的类库 

  • Logging – 使用日志级别的可读性更佳的控制台类库 

  • MethodResponse – 服务端中的DLL中的类,用于连接

  • TestDLL – 简单的DLL,用于处理传入的聊天客户端数据包

  • (可选)Mysql连接器 – 包含连接mysql服务和执行查询的类

逻辑图 

服务端截图

利弊

有利之处 

  • 更简单

  • 你只需要写特定函数的DLL就行

  • 你只需要通过替换DLL就能很快改变服务端功能  

弊端  

  • 效率较慢,请阅读更多关于性能的章节 

目的是为何?  

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

服务端 

载入DLL  

服务端载入特定DLL到程序集中(这里以TestDLL.dll为例)该DLL必须包含PacketHandler类,PacketHandler类必须包含OnClientConnect和OnClientDisconnect方法,我们将在客户端连接和断开时用到他们。

服务端只载入MethodResponse返回类型的公有函数。MethodResponse是服务和dll用于通信的对象。

最后服务端将函数信息存入列表中并在后面需要时进行调用。

//获取用户创建的DLL
string handlerDLL = GetConfig().data["packetHandlerDLL"];
    
Assembly packetHandlerDllAssembly = null;
//检查如果用户DLL不存在则关闭服务端
if (File.Exists(handlerDLL))
{
    //载入用户DLL程序集
    packetHandlerDllAssembly = 
      Assembly.LoadFrom(AppDomain.CurrentDomain.BaseDirectory + handlerDLL); 
    Logging.WriteLine("Loading Packet Handler DLL", LogLevel.Information);
    
    //获取PacketHandler类
    Type Class = packetHandlerDllAssembly.GetType("PacketHandler");
    try
    {
        //创建一个PacketHandler类的实例
        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;
    //创建函数列表        
    RegisteredMethods = new List<Method>();                 
    
    bool OnClientConnectMethodFound = false;
    bool OnClientDisconnectMethodFound = false;
    //获取用户函数
    foreach (MethodInfo MethodInfo in Class.GetMethods(BindingFlags.DeclaredOnly | 
             BindingFlags.Public | BindingFlags.Instance)) 
    {
        //检查OnClientConnect和OnClientDisconnect函数是否存在
        if (MethodInfo.Name == "OnClientConnect")
        {
            OnClientConnectMethodFound = true;
            continue;
        }
            
        if (MethodInfo.Name == "OnClientDisconnect")
        {
            OnClientDisconnectMethodFound = true;
            continue;
        }
    
        //只载入拥有MethodResponse类型返回值的函数
        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 = "";
        //创建一个新类,为了未来Dll函数的调用,MethodInfo是必填项
        Method Method = new Method(MethodInfo.Name, MethodInfo);
        //函数必须有int类型的connID参数
        bool connIDParameterFound = false;
        //获取函数参数列表
        foreach (ParameterInfo pParameter in MethodInfo.GetParameters())
        {
            //增加参数
            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 ";
    
        //增加函数到已注册函数列表中
        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);
    }
    
    //如果已有任何注册过的函数,则关闭服务
    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)
{
    //从回调对象callback中获取连接
    Connection conn = (Connection)result.AsyncState;
    
    try
    {
        //取得缓冲区并读取缓冲区中已收到数据的长度
        int bytesRead = conn.socket.EndReceive(result);
    
        if (bytesRead > 0)
        {
            HandlePacket(ParseMessage(Encoding.ASCII.GetString(conn.buffer, 0, bytesRead), conn), conn);
              
            //压入接收队列
            conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, 
              SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
        }
        else //客户端断开时
        {
            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类.

首先取得包头或名称 

然后如果参数为int,将转为string,服务端将会调用DLL并根据string解析数据包负载类型,否则将在获得参数类型不匹配的异常 

/// <summary>
/// Parse message string to Packet class</summary>
/// <param name="message">Packet string</param>   
/// <param name="conn">Client connection</param>
/// </summary>
private Packet ParseMessage(string Message, Connection conn)
{
    string PacketHeader = Message.Split(Delimiter)[0];
    
    Packet Packet = new Packet(PacketHeader);
    
    Message = Message.Substring(Message.IndexOf(Delimiter) + 1); //Only Packet Body
    
    //解析传入数据包负载的类型
    foreach (string Parameter in Message.Split(Delimiter))
    {
        //这里可以进行更复杂的解析
        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);
        }
    }
    
    //总是加入连接ID到数据包中用于获取客户端ID到用户创建DLL中
    Packet.AddInt32(conn.connID);
    
    return Packet;
}

处理数据包

处理解析过的数据包

服务端回调各自DLL中的Packet函数,并给与解析过的参数

服务端解析回调响应值到MethodResponse

最后循环发送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);
    //使用包头/名称获取相关的数据包函数
    Method Method = Core.GetMethodByName(Packet.Header.ToLower());
    if (Method != null)
    {
        //数据包负载长度必须符合函数参数中的长度
        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
            {
                //尝试调用相关的函数并将传入的数据包负载作为参数
                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);
    
                //调用成功!现在读取在MethodResponse中包含的数据包并发送到特定的客户端  
                foreach (Packet PacketToSend in result.Packets)
                {
                    string PacketString = PacketToSend.GetPacketString();
                    if (PacketToSend.sendToAll) //发送到所有客户端
                    {
                        sendToAll(StrToByteArray(PacketString));
                        Core.GetLogging().WriteLine("Sent response: " + 
                          PacketString + " to all clients", LogLevel.Debug);
                    }
                    else if (PacketToSend.sendTo != null) //仅发送到指定列表的客户端
                    {
                        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(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 

使用者只需要重写这个来确保服务运行。

使用函数的私有标志来避免服务端载入这些函数 

PacketHandler必须包含OnClientConnect和OnClientDisconnect函数 

所有公有函数必须返回MethodResponse类型


//PacketHandler类必须为公开属性来让服务获取它的函数
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>
    
    //使用函数的私有标志来避免服务端载入这些函数
    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>
     
    //公有函数必须返回MethodResponse类型
    public MethodResponse Login(string username, string password, int connID)
    {
        //创建一个新的MethodResponse
        MethodResponse MethodResponse = new MethodResponse();
    
        bool loginFailed = true;
    
        if (password == "password")
            loginFailed = false;
    
        if (loginFailed)
        {
            //创建一个新的LOGIN_RESPONSE数据包并发送到发送器
            Packet LoginResponse = new Packet("LOGIN_RESPONSE");
            //将一个boolean值添加到数据包中,它表示登录失败
            LoginResponse.AddBoolean(false);
            //将包加入MethodResponse中
            MethodResponse.AddPacket(LoginResponse);
        }
        else
        {
            Packet LoginResponse = new Packet("LOGIN_RESPONSE");
            LoginResponse.AddBoolean(true);//It means successful login
            //增加一个int值到包里,它将提供给客户端连接ID
            LoginResponse.AddInt32(connID);
    
            //通知所有客户端加入了一个新用户
            //如果你希望将数据包发往所有客户端,将sendToAll参数设为true(默认false)
            Packet UserJoin = new Packet("USER_JOIN", true);
            //添加新聊天用户的名字
            UserJoin.AddString(username);
    
            //将包发送到MethodResponse
            MethodResponse.AddPacket(LoginResponse);
            MethodResponse.AddPacket(UserJoin);
    
            Users.Add(new User(connID, username)); //Add the Chat User to a List
    
            //打印消息到服务端控制台
            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();
    //通过本地8888端口连接到服务器
    conn.Connect(IPAddress.Parse("127.0.0.1"), 8888);
    //处理数据包
    conn.onPacketReceive += new ServerConnection.onPacketReceiveHandler(HandlePacket);
        
        
    ...
} 
/// <summary>
/// Ask Login
/// </summary>
static void Login()
{
    Console.WriteLine("Write username");
    username = Console.ReadLine(); //读取用户输入
    Console.WriteLine("Write password");
    string password = Console.ReadLine(); //读取用户输入
    
    Packet Login = new Packet("LOGIN"); //创建一个LOGIN数据包
    Login.AddString(username); //将用户名加入到数据包中
    Login.AddString(password); //将密码加入到数据包中
    
    conn.Send(Login); //发送数据包到服务器
}  
/// <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": //收到LOGIN_RESPONSE数据包
            {
                bool loginResponse = Convert.ToBoolean(Packet.bodyValues[0]);
                //从包负载中得到登录响应
    
                if (!loginResponse)
                {
                    Console.WriteLine("Login failed");
                    Login(); //请求登录直到登录成功
                }
                else
                {
                    id = int.Parse(Packet.bodyValues[1].ToString());
                    //从包负载中获取连接ID
                    Console.WriteLine("Login Successful");
                    Logged = true; //已经登录
                }
            }
            break;
                
            ...
    }
}

截屏

Web Service 

我同时创建了智能Web服务面板

简单的Web服务聊天面板

webServiceConnector.php 

使用Web服务实现登录并调用GetUserCount和GetUserList函数. 

使用JSON返回结果

<?php
if (isset($_GET['readData']))
{
    //连接到web服务
    $client = new SoapClient("http://localhost:8000/FlexibleServer/?wsdl",array(
    'login' => "admin", 'password' => "password"));
    
    try
    {         
    //获取用户数量
        $response = $client->__soapCall("GetUserCount",array());
        $arr=objectToArray($response);
    //获取用户列表
        $response2 = $client->__soapCall("GetUserList",array());
        $arr2=objectToArray($response2);
    //组合结果
        $result = array_merge($arr,$arr2);
    //组成JSON报文
        echo json_encode($result);
    } 
    catch (SoapFault $exception)
    {
        trigger_error("SOAP Fault: (faultcode: {$exception->faultcode}, faultstring:
        {$exception->faultstring})");
    
        var_dump($exception);
    }
}
?>

Javascript函数

实现ajax请求用于获取结果并显示  

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)
      {        
    //从源中解析JSON
        var obj = jQuery.parseJSON(xmlhttp.responseText);    
    //刷新用户数量值
        g1.refresh(obj["GetUserCountResult"]);
    //获取输入框
        var txtarea = document.getElementById("txtarea");
        if (obj["GetUserListResult"]["string"] != null)
        {
            var length = obj["GetUserListResult"]["string"].length;
                
            var s = "";
        //增加用户名称
            for (var i = 0; i < length; i++) {
              s += obj["GetUserListResult"]["string"][i];
        }    
        //显示名称
        txtarea.innerHTML = s;
        txtarea.scrollTop = txtarea.scrollHeight;
        }
        else
        {
            txtarea.innerHTML = "";
            txtarea.scrollTop = txtarea.scrollHeight;
        }
            
    //每秒刷新
        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();
}


Server code to start Web Service 

/// <summary>
/// Start webService
/// </summary>     
public void Start()
{
    Uri baseAddress = new Uri("http://" + IP.ToString() + ":" + Port + "/FlexibleServer/");
    
    //从用户创建DLL中获取WebserviceHandler函数
    Type Webservice = packetHandlerDllAssembly.GetType("WebserviceHandler");
    //从用户DLL 中获取Webservice接口
    Type Interface = packetHandlerDllAssembly.GetType("IWebservice");
    //获取用户创建的weService函数
    foreach (MethodInfo m in Interface.GetMethods(BindingFlags.DeclaredOnly | 
             BindingFlags.Public | BindingFlags.Instance)) 
    {
        string param = "";
    
        //获取参数
        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);
    }
    
    // 创建ServiceHost,绑定 http://ip:port/FlexibleServer/
    ServiceHost selfHost = new ServiceHost(Webservice, baseAddress);
    
    //绑定到配置好的endpoint中
    BasicHttpBinding http = new BasicHttpBinding();
    
    //设置基本的用户名密码权限
    http.Security.Mode = BasicHttpSecurityMode.TransportCredentialOnly;
    
    http.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;
    
    try
    {
         //增加endpoint到service host
        ServiceEndpoint endpoint = selfHost.AddServiceEndpoint(
          Interface, http, "RemoteControlService");
        //增加客户webservice behavior到endpoint中
        endpoint.Behaviors.Add(new webServiceEvent());
    
        //设置自定义用户名密码校验
        selfHost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = 
          UserNamePasswordValidationMode.Custom;
        selfHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = 
          new LoginValidator();
    
        //激活元数据发布
        ServiceMetadataBehavior smb = 
          selfHost.Description.Behaviors.Find<ServiceMetadataBehavior>();
        if (smb == null)
        {
            smb = new ServiceMetadataBehavior();
            smb.HttpGetEnabled = true;
            selfHost.Description.Behaviors.Add(smb);
        }
    
        try
        {
            //启动服务
            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();               
    }
}

截屏

如何连接mysql数据库 

在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>
    
//公有函数必须返回MethodResponse类型的对象 
public MethodResponse Login(object username, object password, int connID)
{
    //创建一个新的MethodResponse
    MethodResponse MethodResponse = new MethodResponse();
    
    //检查mysql中是否有已存在的用户
    DataRow Row = MysqlConn.ReadDataRow("SELECT * FROM users where username = '" + username + "' AND password = '" + password + "'");
    
    bool loginFailed = true;
    
    if (Row != null)
    {
        loginFailed = false;
    }
    
    if (loginFailed)
    {
        //创建一个新的LOGIN_RESPONSE数据包并发送到发送器
        Packet LoginResponse = new Packet("LOGIN_RESPONSE");
        //增加一个bool值,意味着登录失败
        LoginResponse.AddBoolean(false);
        //增加Packet到MethodResponse
        MethodResponse.AddPacket(LoginResponse);
    }
    else
    {
        Packet LoginResponse = new Packet("LOGIN_RESPONSE");
        LoginResponse.AddBoolean(true);//增加int值表示连接ID提供给客户端
        LoginResponse.AddInt32(connID);
    
        //通知所有客户端加入了一个新用户
        //如果你希望将数据包发往所有客户端,将sendToAll参数设为true(默认false)
        Packet UserJoin = new Packet("USER_JOIN", true);
        //添加新聊天用户的名字
        UserJoin.AddString(username.ToString());
    
        //将包发送到MethodRespons
        MethodResponse.AddPacket(LoginResponse);
        MethodResponse.AddPacket(UserJoin);
    
        Users.Add(new User(connID, username.ToString())); //Add the Chat User to a List
    
        //打印消息到服务端控制台
        Logging.WriteLine("User: " + username + " has joined the chat", LogLevel.Information);
    }
    
    return MethodResponse; //返回服务端MethodResponse对象
}
  • 编译TestDLL 

  • 将TestDll.dll,MysqlConnector.dll,Mysql.data.dll放置到服务端目录下 

如何测试工程 

  • 使用管理员身份打开FlexibleServer.exe 

  • 打开一个或多个ChatClient.exe

  • 输入用户名 

  • 输入密码 

  • 发送一条消息

创建你自己的DLL

  • 使用Visual Studio创建一个新的DLL工程

  • 添加System.ServiceModel引用

  • 添加Logging.dll引用

  • (可选)如果需要支持Mysql数据库,则需要添加MysqlConnector.dll和Mysql.data.dll引用

  • 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

日志 

更好可读性控制台打印消息的类,并使用级别控制

Debug 
Information 
Warning 
Error 
Success 4  

WriteLine Code

/// <summary>
/// Write Line to Console with specified Log Level
/// <param name="Line">Line text</param>
/// <param name="Level">LogLevel</param>
/// </summary>
public void WriteLine(string Line, LogLevel Level)
{
    //如果LogLevel比最小的级别还小就不需要打印
    if (Level >= MinimumLogLevel)
    {
        DateTime _DTN = DateTime.Now;
        StackFrame _SF = new StackTrace().GetFrame(1); 
        Console.Write("[");
        Console.ForegroundColor = ConsoleColor.Green;
        //打印当前的类函数
        Console.Write(_SF.GetMethod().ReflectedType.Name + "." + _SF.GetMethod().Name);
        Console.ForegroundColor = ConsoleColor.Gray;
        Console.Write("] » ");
    
        //根据日志级别改变颜色
        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;
    }
}

响应函数 

这个用于服务端与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?
        
    //服务端将要发送包的连接列表
    public List<int> sendTo;
        
    //使用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)
        {
            //增加负载中的分隔符
            PacketString += Delimiter.ToString() + o.ToString();
        }
        return PacketString;
    }
}
    
public class MethodResponse
{    
    public List<Packet> 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毫秒

结论,智能服务端需要更多的流程,因此更慢些,不适合大型项目。

结论

感谢阅读这篇文章,希望你喜欢

如果你找到bug或有更好的改进建议请通知我,我很高兴进行改良。 


源码下载



赞助商