IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发工具 -> C#---HylicServer 轻量级服务器 (更新中) -> 正文阅读

[开发工具]C#---HylicServer 轻量级服务器 (更新中)

概念

想将并发量做的更大一点,思路是维护一个请求队列,一个线程用于登记并收集客户端请求,缓存到队列中。另一个线程用于处理队列中的请求。目前在考虑是否换一种数据结构,因为我想让此服务器支持长连接(可能要设置服务器查询客户端存活情况,或者在客户端主动断开时能够选择到该客户端并删除套接字)

设计过程

单例基类

线程安全,目前没有使用反射创建实例,后期更新中修改。

namespace HylicServer
{
    /// <summary>
    /// 用于制作单例的抽象类,继承后访问静态的Instance属性
    /// </summary>
    /// <typeparam name="T"></typeparam>
    abstract class SingleTonBase<T> where T : new()
    {
        private static T instance;
        private static Object Olock = new Object();
        public static T Instance
        {
            get
            {
                if(null == instance)
                {
                    lock (Olock)
                    {
                        if (null == instance)
                        {
                            instance = new T();
                        }
                    }
                }
                return instance;
            }
        }
    }
}

为日志类,配置文件类使用

配置操作类

配置文件读取:
本地存储一个json格式的配置文件,用于存储部署服务器的IP与端口。
ConfigurationBuilder类用于生成基于键/值的配置设置,以供在应用程序中使用。
AddJsonFile将 JSON 配置源添加到 builder。
建立配置类后可以通过键以获取对应的值

提供消息入队方法MesEnqueue,记录每一个日志消息实例。MesWriting每三秒将日志消息读进磁盘一次
这里做的不好,可以使用信号量改进写磁盘的时机。

更新中可能会修改配置文件,需要添加Set方法。

    /// <summary>
    /// 配置文件读取类
    /// </summary>
    class Configuration : SingleTonBase<Configuration>
    {
        private ConfigurationBuilder configBuilder;
        private IConfigurationRoot config;
        public Configuration()
        {
            configBuilder = new ConfigurationBuilder();
            string path= System.AppDomain.CurrentDomain.BaseDirectory;
            path += "\\config\\setting.json";
            configBuilder.AddJsonFile(path, false);
            config = configBuilder.Build();
        }
        public string Get(string key)
        {
            return config[key];
        }
    }

日志类

LogLevel用于枚举日志等级
日志类需要继承单例模式类,全局唯一实例,通过Instance属性以获取日志实例。

   //日志消息的等级
    public enum LogLevel
    {
        Debug,
        Info,
        Error,
        Warn,
        Fatal,
        Defult
    }

    //日志消息结构体
    public struct LogMessage
    {
        public string Message { get; set; }
        public LogLevel Level { get; set; }
        
        public Exception Exception { get; set; }

        public LogMessage(string mes,LogLevel level,Exception ex)
        {
            this.Message = mes;
            this.Level = level;
            this.Exception = ex;
        }
    }

    /// <summary>
    /// 用于操作日志的类
    /// </summary>
    class LogProvider:SingleTonBase<LogProvider>
    {
       
        //输入消息做处理后的标准消息格式
        private string outputStr;
        //保存日志消息的队列  线程安全的阻塞集合
        private BlockingCollection<LogMessage> logMessagesQue;
        public int MaxLength = 100;
        public LogProvider()
        {
            outputStr = string.Empty;
            logMessagesQue = new BlockingCollection<LogMessage>(100);
        }

        /// <summary>
        /// 格式化日志消息后输出
        /// </summary>
        /// <param name="log"></param>
        public void LogPrint(LogMessage log)
        {
            string NowTime = DateTime.Now.ToString();
            outputStr = $"==========================\n" +
                $"Time:{NowTime}\n" +
                $"Message:{log.Message}\n" +
                $"Level:{log.Level}\n" +
                $"Execption:{log.Exception}\n";
            Console.WriteLine(outputStr);
        }

        //将日志消息存入消息队列
        public void MesEnqueue(string Mes,LogLevel level,Exception ex)
        {
            LogMessage lm = new LogMessage(Mes, level, ex);
            if(logMessagesQue.Count<MaxLength)
            {
                logMessagesQue.Add(lm);
            }
        }
        //日志当日志消息入队时需要不断地写入到磁盘中
        public void MesWritting()
        {
            Task.Run(
               () =>
               {
                   while(true)
                   {
                       LogMessage lm = new LogMessage("", LogLevel.Defult, null);
                       while (!logMessagesQue.IsCompleted)      //还未完成添加 并且 还不为空
                       {
                           try                                                                  
                           {
                               lm = logMessagesQue.Take();      //尝试取出一个日志消息
                           }
                           catch (InvalidOperationException ex)    //Take时IsCompleted被设置为true就会抛出这个异常
                           {
                               ;
                           }
                           if (lm.Level != LogLevel.Defult)     //获取到的日志信息不是默认的,则将日志信息写入磁盘
                           {
                               //写入磁盘
                               //或写入数据库
                           }
                       }
                       //生产线程不再继续往集合中添加元素了 ==> IsCompleted==True
                       //每三秒会执行一次消费动作,让出CPU时间以提高效率
                       Thread.Sleep(3000);
                   }
               }
               );
        }
    }

为连接上的客户端的信息记录创建类

一个连接上的客户端都有其单独的信息缓存,需要保存Socket并记录是否有关闭的需求,更新后会在内存中存储一张记录需要销毁的连接的表,在请求处理器中找到他们并做销毁。

namespace HylicServer
{
    /// <summary>
    /// 客户端结构体信息
    /// </summary>
    public class ClientInfo
    {
        //缓存区大小
        public int BufferSize = 100;
        //客户端序号
        public int ClientIndex { get; set; }
        //客户端连接句柄
        public TcpClient tc { get; set; }
        //是否关闭
        public bool isClose;
        //接收缓存区
        public Byte[] ReceiveBuffer;
        //Ctor
        public ClientInfo(int index , TcpClient tc)
        {
            ClientIndex = index;
            this.tc = tc;
            ReceiveBuffer = new Byte[BufferSize];
            isClose = false;
        }

        public async Task<string> ClientRead()
        {
            string receiveStringBuffer;
            using (NetworkStream stream = this.tc.GetStream())
            {
               int bytes = await stream.ReadAsync(ReceiveBuffer, 0, BufferSize);
               receiveStringBuffer = Encoding.UTF8.GetString(ReceiveBuffer, 0, bytes);
            }
            return receiveStringBuffer;
        }

        public async Task ClientWriteAsync(string msg)
        {
            Byte[] SendBytes = Encoding.UTF8.GetBytes(msg);

            using (NetworkStream stream = this.tc.GetStream())
            {
               Console.WriteLine(SendBytes);
               await stream.WriteAsync(SendBytes, 0, BufferSize);
            }
        }
    }
}

HylicServer核心

主要设置一个线程作为请求收集器,另一个线程作为请求处理器,维护一个BlockingCollection线程安全的队列。

/// <summary>
/// HylicServer采用消息队列循环机制
/// 客户端连接上来时将其加入到服务队列中,结束时关闭连接/销毁资源
/// 一个线程监听连接,有连接则入队
/// 一个线程处理请求,扫描队列中的客户端请求做出相应的处理,客户端有断开需求则关闭连接销毁资源,客户端句柄出队
/// </summary>
namespace HylicServer
{
    /// <summary>
    /// Hylic服务器类
    /// </summary>
    class Hylic
    {
        private const int ConnectMaxNumber = 1000;              //最大可连接数
        private string bindaddress;                             //服务端绑定的IP地址
        private string bindport;                                //服务端绑定的Port
        private IPEndPoint ipe;                                 //IP终节点
        public TcpListener server;                              //服务端监听器
        private BlockingCollection<ClientInfo> ClientList;      //请求队列
        private int ConnectCount;                               //记录连接数
        public Task Listenner;                                  //监听任务句柄
        public Task Handler;                                    //任务处理器句柄

        /// <summary>
        /// 构造函数从配置文件里读取参数来初始化服务器
        /// </summary>
        public Hylic()
        {
            ConnectCount = 0;
            ClientList = new BlockingCollection<ClientInfo>(boundedCapacity:255);
            bindaddress = Configuration.Instance.Get("BindAddress");
            bindport = Configuration.Instance.Get("BindPort");
            ipe = new IPEndPoint(IPAddress.Parse(bindaddress), int.Parse(bindport));
        }

        /// <summary>
        /// Hylic服务器启动
        /// </summary>
        public void HylicRun()
        {
            try
            {
                server = new TcpListener(ipe);
                server.Start();
                Console.WriteLine($"Server Is Running At:[{this.ipe}]\n");
            }
            catch(SocketException SE)
            {
                Console.WriteLine(SE.SocketErrorCode); 
            }
        }

        /// <summary>
        /// 异步监听,将连接上来的客户端存储到队列中
        /// 这里只做连接信息登记 并收集
        /// </summary>
        public void HylicListen()
        {
            Listenner =  Task.Run(
                async () =>
                {
                    while(true)
                    {
                        this.ConnectCount = this.ConnectCount + 1;
                        TcpClient tc =await server.AcceptTcpClientAsync();
                        ClientInfo clientInfo = new ClientInfo(this.ConnectCount, tc);
                        ClientList.Add(clientInfo);
                    }
                }
                );
        }

        /// <summary>
        /// 消息处理器
        /// </summary>
        public void HylicRequestHandler()
        {
            Handler = Task.Run(
                async () =>
                {
                    while(true)
                    {
                        while(!ClientList.IsCompleted)
                        {
                            ClientInfo ci = null;
                            bool IsTakeSucceed = false;
                            try
                            {
                                IsTakeSucceed = ClientList.TryTake(out ci);
                            }
                            catch(InvalidOperationException ex)
                            {
                                Console.WriteLine($"Take Request From Queue Error:{ex.Message}");
                            }
                            if(ci != null && IsTakeSucceed==true)
                            {
                                string ReadRes = await ci.ClientRead();
                                Console.WriteLine($"[Message From Client {ci.ClientIndex}]\n" +
                                    $"Message:\n{ReadRes}\n");

                                var firstLine = ReadRes.Split(" ").First();

                                Console.WriteLine("Handle String :", firstLine);

                                ci.tc.Close();
                            }
                        }
                    }
                }
                );
        }
    }
}

程序入口使用Hylic示例

namespace HylicServer
{
    class Program
    {
        static void Main(string[] args)
        {
            Hylic hylicServer = new Hylic();
            hylicServer.HylicRun();
            hylicServer.HylicListen();
            hylicServer.HylicRequestHandler();
            while(true)
            {
                ;
            }
        }
    }

总结更新需求

1.扩展请求处理器的线程数
2.实现Server端长连接,设置Alive时长,或根据isClose标志位来决定是否出队。
3.部分配置需要放置到setting文件中
4.考虑解耦性

  开发工具 最新文章
Postman接口测试之Mock快速入门
ASCII码空格替换查表_最全ASCII码对照表0-2
如何使用 ssh 建立 socks 代理
Typora配合PicGo阿里云图床配置
SoapUI、Jmeter、Postman三种接口测试工具的
github用相对路径显示图片_GitHub 中 readm
Windows编译g2o及其g2o viewer
解决jupyter notebook无法连接/ jupyter连接
Git恢复到之前版本
VScode常用快捷键
上一篇文章      下一篇文章      查看所有文章
加:2022-04-04 12:31:11  更:2022-04-04 12:35:03 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:22:39-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码