概念
想将并发量做的更大一点,思路是维护一个请求队列,一个线程用于登记并收集客户端请求,缓存到队列中。另一个线程用于处理队列中的请求。目前在考虑是否换一种数据结构,因为我想让此服务器支持长连接(可能要设置服务器查询客户端存活情况,或者在客户端主动断开时能够选择到该客户端并删除套接字)
设计过程
单例基类
线程安全,目前没有使用反射创建实例,后期更新中修改。
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.考虑解耦性