diff --git a/Projects/DLL/SystemX.Core.dll b/Projects/DLL/SystemX.Core.dll
index 135cf62..7515f20 100644
Binary files a/Projects/DLL/SystemX.Core.dll and b/Projects/DLL/SystemX.Core.dll differ
diff --git a/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac b/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac
index 380c56b..1307ee6 100644
Binary files a/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac and b/Projects/HubX/DBPatch/sqlScripts/dacpac/HubX.DB.dacpac differ
diff --git a/Projects/HubX/HubX.Library/HubX.Library.csproj b/Projects/HubX/HubX.Library/HubX.Library.csproj
index fa71b7a..766fa05 100644
--- a/Projects/HubX/HubX.Library/HubX.Library.csproj
+++ b/Projects/HubX/HubX.Library/HubX.Library.csproj
@@ -6,4 +6,14 @@
enable
+
+
+
+
+
+
+ ..\..\DLL\SystemX.Core.dll
+
+
+
diff --git a/Projects/HubX/HubX.Library/Socket/Object/Client.cs b/Projects/HubX/HubX.Library/Socket/Object/Client.cs
new file mode 100644
index 0000000..f2f957a
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Object/Client.cs
@@ -0,0 +1,29 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using HubX.Library.Socket.Session;
+
+namespace HubX.Library.Socket.Object
+{
+ public class Client
+ {
+
+ public EnumObjectType ObjectType = EnumObjectType.Client;
+
+ private int ClientId;
+ public int Id
+ {
+ get { return ClientId; }
+ set { ClientId = value; }
+ }
+
+ public ClientSession Session { get; set; }
+
+ public Client()
+ {
+
+ }
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Object/EnumObjectType.cs b/Projects/HubX/HubX.Library/Socket/Object/EnumObjectType.cs
new file mode 100644
index 0000000..84888f3
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Object/EnumObjectType.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HubX.Library.Socket.Object
+{
+ public enum EnumObjectType
+ {
+ NONE = 0,
+ Client = 1,
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Object/ObjectManager.cs b/Projects/HubX/HubX.Library/Socket/Object/ObjectManager.cs
new file mode 100644
index 0000000..8812d84
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Object/ObjectManager.cs
@@ -0,0 +1,80 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HubX.Library.Socket.Object
+{
+ public class ObjectManager
+ {
+ public static ObjectManager Instance { get; } = new ObjectManager();
+
+ object _lock = new object();
+ Dictionary _players = new Dictionary();
+
+ // [UNUSED(1)][TYPE(7)][ID(24)]
+ int _counter = 0;
+
+ public T Add() where T : Client, new()
+ {
+ T gameObject = new T();
+
+ lock (_lock)
+ {
+ gameObject.Id = GenerateId(gameObject.ObjectType);
+
+ if (gameObject.ObjectType == EnumObjectType.Client)
+ {
+ _players.Add(gameObject.Id, gameObject as Client);
+ }
+ }
+
+ return gameObject;
+ }
+
+ int GenerateId(EnumObjectType type)
+ {
+ lock (_lock)
+ {
+ return ((int)type << 24) | (_counter++);
+ }
+ }
+
+ public static EnumObjectType GetObjectTypeById(int id)
+ {
+ int type = (id >> 24) & 0x7F;
+ return (EnumObjectType)type;
+ }
+
+ public bool Remove(int objectId)
+ {
+ EnumObjectType objectType = GetObjectTypeById(objectId);
+
+ lock (_lock)
+ {
+ if (objectType == EnumObjectType.Client)
+ return _players.Remove(objectId);
+ }
+
+ return false;
+ }
+
+ public Client Find(int objectId)
+ {
+ EnumObjectType objectType = GetObjectTypeById(objectId);
+
+ lock (_lock)
+ {
+ if (objectType == EnumObjectType.Client)
+ {
+ Client player = null;
+ if (_players.TryGetValue(objectId, out player))
+ return player;
+ }
+ }
+
+ return null;
+ }
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Packet/EnumMessageId.cs b/Projects/HubX/HubX.Library/Socket/Packet/EnumMessageId.cs
new file mode 100644
index 0000000..20c5e92
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Packet/EnumMessageId.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HubX.Library.Socket.Packet
+{
+ public enum EnumMessageId
+ {
+ //C2S : Client to Server
+ //S2C : Server to Client
+
+ //unique key CRUD
+ C2S_INSERT_UniqueKey = 10,
+ S2C_INSERT_UniqueKey = 11,
+ C2S_SELECT_UniqueKey = 12,
+ S2C_SELECT_UniqueKey = 13,
+ C2S_UPDATE_UniqueKey = 14,
+ S2C_UPDATE_UniqueKey = 15,
+ C2S_DELETE_UniqueKey = 16,
+ S2C_DELETE_UniqueKey = 17,
+ }
+
+ public enum EnumMessageResult
+ {
+ None = 0,
+
+ Success = 10,
+ Failed = 10,
+ Error = 11,
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Packet/PacketHandler.cs b/Projects/HubX/HubX.Library/Socket/Packet/PacketHandler.cs
new file mode 100644
index 0000000..245a75d
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Packet/PacketHandler.cs
@@ -0,0 +1,43 @@
+using HubX.Library.Socket.Object;
+using HubX.Library.Socket.Session;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+using SystemX.Core.Communication;
+
+namespace HubX.Library.Socket.Packet
+{
+
+ public class PacketHandler
+ {
+ public static void C2S_INSERT_UniqueKeyHandler(PacketSession session, IMessage packet)
+ {
+ C2S_INSERT_UniqueKey movePacket = packet as C2S_INSERT_UniqueKey;
+ ClientSession clientSession = session as ClientSession;
+
+ Client client = clientSession.Client;
+ if (client == null)
+ return;
+ }
+
+ public static void C_SkillHandler(PacketSession session, IMessage packet)
+ {
+ //C_Skill skillPacket = packet as C_Skill;
+ //ClientSession clientSession = session as ClientSession;
+
+ //Player player = clientSession.MyPlayer;
+ //if (player == null)
+ // return;
+
+ //GameRoom room = player.Room;
+ //if (room == null)
+ // return;
+
+ //room.Push(room.HandleSkill, player, skillPacket);
+ }
+ }
+
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Packet/Protocol.cs b/Projects/HubX/HubX.Library/Socket/Packet/Protocol.cs
new file mode 100644
index 0000000..3909a35
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Packet/Protocol.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace HubX.Library.Socket.Packet
+{
+ public interface IMessage
+ {
+ }
+
+ public class C2S : IMessage
+ {
+ public EnumMessageId MessageId { get; set; }
+ }
+
+ public class S2C : IMessage
+ {
+ public EnumMessageId MessageId { get; set; }
+
+ public EnumMessageResult Result { get; set; }
+ }
+
+ public sealed class C2S_INSERT_UniqueKey : C2S
+ {
+ public string Identity { get; set; }
+
+ public string Data1 { get; set; }
+ public string Data2 { get; set; }
+ public string Data3 { get; set; }
+ public string Data4 { get; set; }
+ public string Data5 { get; set; }
+ }
+
+ public sealed class S2C_INSERT_UniqueKey : S2C
+ {
+ public string Identity { get; set; }
+
+ public string Data1 { get; set; }
+ public string Data2 { get; set; }
+ public string Data3 { get; set; }
+ public string Data4 { get; set; }
+ public string Data5 { get; set; }
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Packet/ServerPacketManager.cs b/Projects/HubX/HubX.Library/Socket/Packet/ServerPacketManager.cs
new file mode 100644
index 0000000..62e5426
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Packet/ServerPacketManager.cs
@@ -0,0 +1,72 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SystemX.Core.Communication;
+
+namespace HubX.Library.Socket.Packet
+{
+ public class PacketManager
+ {
+ #region Singleton
+ static PacketManager _instance = new PacketManager();
+ public static PacketManager Instance { get { return _instance; } }
+ #endregion
+
+ PacketManager()
+ {
+ Register();
+ }
+
+ Dictionary, ushort>> _onRecv = new Dictionary, ushort>>();
+ Dictionary> _handler = new Dictionary>();
+
+ public Action CustomHandler { get; set; }
+
+ public void Register()
+ {
+ _onRecv.Add((ushort)EnumMessageId.C2S_INSERT_UniqueKey, MakePacket);
+ _handler.Add((ushort)EnumMessageId.C2S_INSERT_UniqueKey, PacketHandler.C2S_INSERT_UniqueKeyHandler);
+ }
+
+ public void OnRecvPacket(PacketSession session, ArraySegment buffer)
+ {
+ ushort count = 0;
+
+ ushort size = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
+ count += 2;
+ ushort id = BitConverter.ToUInt16(buffer.Array, buffer.Offset + count);
+ count += 2;
+
+ Action, ushort> action = null;
+ if (_onRecv.TryGetValue(id, out action))
+ action.Invoke(session, buffer, id);
+ }
+
+ void MakePacket(PacketSession session, ArraySegment buffer, ushort id) where T : IMessage, new()
+ {
+ T pkt = new T();
+ //pkt.MergeFrom(buffer.Array, buffer.Offset + 4, buffer.Count - 4);
+
+ if (CustomHandler != null)
+ {
+ CustomHandler.Invoke(session, pkt, id);
+ }
+ else
+ {
+ Action action = null;
+ if (_handler.TryGetValue(id, out action))
+ action.Invoke(session, pkt);
+ }
+ }
+
+ public Action GetPacketHandler(ushort id)
+ {
+ Action action = null;
+ if (_handler.TryGetValue(id, out action))
+ return action;
+ return null;
+ }
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Session/ClientSession.cs b/Projects/HubX/HubX.Library/Socket/Session/ClientSession.cs
new file mode 100644
index 0000000..e11dad6
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Session/ClientSession.cs
@@ -0,0 +1,56 @@
+using HubX.Library.Socket.Object;
+using HubX.Library.Socket.Packet;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net;
+using System.Numerics;
+using System.Text;
+using System.Threading.Tasks;
+using SystemX.Core.Communication;
+
+namespace HubX.Library.Socket.Session
+{
+ public class ClientSession : PacketSession
+ {
+ public Client Client { get; set; }
+ public int SessionId { get; set; }
+
+ public void Send(IMessage packet)
+ {
+ //string msgName = packet.Descriptor.Name.Replace("_", string.Empty);
+ //EnumMessageId msgId = (EnumMessageId)Enum.Parse(typeof(EnumMessageId), msgName);
+ //ushort size = (ushort)packet.CalculateSize();
+ //byte[] sendBuffer = new byte[size + 4];
+ //Array.Copy(BitConverter.GetBytes((ushort)(size + 4)), 0, sendBuffer, 0, sizeof(ushort));
+ //Array.Copy(BitConverter.GetBytes((ushort)msgId), 0, sendBuffer, 2, sizeof(ushort));
+ //Array.Copy(packet.ToByteArray(), 0, sendBuffer, 4, size);
+ //Send(new ArraySegment(sendBuffer));
+ }
+
+ public override void OnConnected(EndPoint endPoint)
+ {
+ Log4net.WriteLine($"OnConnected : {endPoint}", LogType.SOCKET);
+ Client = ObjectManager.Instance.Add();
+ {
+ Client.Session = this;
+ }
+ }
+
+ public override void OnRecvPacket(ArraySegment buffer)
+ {
+ Log4net.WriteLine($"OnRecvPacket : {Encoding.UTF8.GetString(buffer)}", LogType.SOCKET);
+ PacketManager.Instance.OnRecvPacket(this, buffer);
+ }
+
+ public override void OnDisconnected(EndPoint endPoint)
+ {
+ Log4net.WriteLine($"OnDisconnected : {endPoint}", LogType.SOCKET);
+ }
+
+ public override void OnSend(int numOfBytes)
+ {
+ //Console.WriteLine($"Transferred bytes: {numOfBytes}");
+ }
+ }
+}
diff --git a/Projects/HubX/HubX.Library/Socket/Session/SessionManager.cs b/Projects/HubX/HubX.Library/Socket/Session/SessionManager.cs
new file mode 100644
index 0000000..f409659
--- /dev/null
+++ b/Projects/HubX/HubX.Library/Socket/Session/SessionManager.cs
@@ -0,0 +1,54 @@
+using HubX.Library.Socket.Packet;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using SystemX.Core.Communication;
+
+namespace HubX.Library.Socket.Session
+{
+ public class SessionManager
+ {
+ static SessionManager _session = new SessionManager();
+ public static SessionManager Instance { get { return _session; } }
+
+ int _sessionId = 0;
+ Dictionary _sessions = new Dictionary();
+ object _lock = new object();
+
+ public ClientSession Generate()
+ {
+ lock (_lock)
+ {
+ int sessionId = ++_sessionId;
+
+ ClientSession session = new ClientSession();
+ session.SessionId = sessionId;
+ _sessions.Add(sessionId, session);
+
+ Log4net.WriteLine($"Connected : {sessionId}", LogType.SOCKET);
+
+ return session;
+ }
+ }
+
+ public ClientSession Find(int id)
+ {
+ lock (_lock)
+ {
+ ClientSession session = null;
+ _sessions.TryGetValue(id, out session);
+ return session;
+ }
+ }
+
+ public void Remove(ClientSession session)
+ {
+ lock (_lock)
+ {
+ _sessions.Remove(session.SessionId);
+ }
+ }
+ }
+}
diff --git a/Projects/HubX/HubX.Server/HubX.Server.csproj b/Projects/HubX/HubX.Server/HubX.Server.csproj
index d3bb312..2cc9165 100644
--- a/Projects/HubX/HubX.Server/HubX.Server.csproj
+++ b/Projects/HubX/HubX.Server/HubX.Server.csproj
@@ -6,10 +6,22 @@
enable
+
+ 9999
+
+
+
+ 9999
+
+
+
+
+
+
..\..\DLL\SystemX.Core.dll
diff --git a/Projects/HubX/HubX.Server/Program.cs b/Projects/HubX/HubX.Server/Program.cs
index c56fe2d..797a699 100644
--- a/Projects/HubX/HubX.Server/Program.cs
+++ b/Projects/HubX/HubX.Server/Program.cs
@@ -1,4 +1,7 @@
-using SystemX.Core.Log4net;
+using HubX.Library.Socket.Session;
+using HubX.Server;
+using System.Net;
+using SystemX.Core.Communication;
var builder = WebApplication.CreateBuilder(args);
@@ -21,6 +24,30 @@ if (app.Environment.IsDevelopment())
Log4net.WriteLine("Run");
Log4net.WriteLine("Custom LogLevel",LogType.DB);
+WeatherForecast weatherForecast = new WeatherForecast();
+weatherForecast.Summary = "so hot";
+var strJson = weatherForecast.ToJson();
+
+var deep = weatherForecast.DeepCopy();
+deep.Summary = "so cool";
+
+var rr = strJson.ToObject();
+
+Task.Run(async() =>
+{
+ await Task.Delay(2000);
+ Listener _listener = new Listener();
+
+ // string host = Dns.GetHostName();
+ IPHostEntry ipHost = Dns.GetHostEntry("127.0.0.1");
+ IPAddress ipAddr = ipHost.AddressList[0];
+ IPEndPoint endPoint = new IPEndPoint(ipAddr, 7777);
+
+ _listener.Init(endPoint, () => { return SessionManager.Instance.Generate(); });
+
+ Console.WriteLine("Listening...");
+});
+
app.UseHttpsRedirection();
app.UseAuthorization();
diff --git a/Projects/HubX/HubX.Server/Properties/launchSettings.json b/Projects/HubX/HubX.Server/Properties/launchSettings.json
index ceae9d3..aee3210 100644
--- a/Projects/HubX/HubX.Server/Properties/launchSettings.json
+++ b/Projects/HubX/HubX.Server/Properties/launchSettings.json
@@ -1,4 +1,31 @@
-{
+{
+ "profiles": {
+ "http": {
+ "commandName": "Project",
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "http://localhost:5103"
+ },
+ "https": {
+ "commandName": "Project",
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "dotnetRunMessages": true,
+ "applicationUrl": "https://localhost:7163;http://localhost:5103"
+ },
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchUrl": "swagger",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ },
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
@@ -7,35 +34,5 @@
"applicationUrl": "http://localhost:33125",
"sslPort": 44383
}
- },
- "profiles": {
- "http": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "http://localhost:5103",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "https": {
- "commandName": "Project",
- "dotnetRunMessages": true,
- "launchBrowser": true,
- "launchUrl": "swagger",
- "applicationUrl": "https://localhost:7163;http://localhost:5103",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
- "IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "launchUrl": "swagger",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- }
}
-}
+}
\ No newline at end of file
diff --git a/Projects/SystemX.Core/SystemX.Core/Communication/Http.cs b/Projects/SystemX.Core/SystemX.Core/Communication/Http.cs
new file mode 100644
index 0000000..33cbbf2
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Communication/Http.cs
@@ -0,0 +1,112 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http.Json;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Communication
+{
+ public class Http
+ {
+ ///
+ /// PostJsonAsync
+ ///
+ /// https://127.0.0.1:443
+ /// Range 5~30 secconds
+ public virtual async Task PostJsonAsync(string url, REQUEST request, short timeOutSeconds = 5) where REQUEST : class where RESPONSE : class
+ {
+ RESPONSE? response = default(RESPONSE);
+ Guid guid = Guid.NewGuid();
+
+ using (HttpClient httpClient = new HttpClient(GetClientHandler()))
+ {
+ int retry = 0;
+ while (true)
+ {
+ await Task.Delay(1);
+ try
+ {
+ var timeOutSec = SetTimeout(timeOutSeconds);
+ httpClient.Timeout = new TimeSpan(0, 0, timeOutSec);
+ httpClient.BaseAddress = new Uri($"{url}");
+
+ Log4net.WriteLine($"[POST] Request({guid})::{url}{Environment.NewLine}{request?.ToJson()}", LogType.HTTP);
+
+ DateTime requestTime = DateTime.Now;
+ var res = await httpClient.PostAsJsonAsync(url, request);
+ response = await res.Content.ReadFromJsonAsync();
+
+ Log4net.WriteLine($"[POST] Rseponse({guid}) ({(DateTime.Now - requestTime).TotalSeconds} sec)::{url}{Environment.NewLine}{response?.ToJson()}", LogType.HTTP);
+ break;
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine(e);
+ retry += 1;
+ }
+
+ if (retry > 1)
+ break;
+ }
+ }
+
+ return response;
+ }
+
+ ///
+ /// GetJsonAsnyc
+ ///
+ /// https://127.0.0.1:443
+ /// Range 5~30 secconds
+ public virtual async Task GetJsonAsync(string url, short timeOutSeconds = 10) where RESPONSE : class
+ {
+ RESPONSE? response = default(RESPONSE);
+ Guid guid = Guid.NewGuid();
+
+ using (HttpClient httpClient = new HttpClient(GetClientHandler()))
+ {
+ try
+ {
+ var timeOutSec = SetTimeout(timeOutSeconds);
+ httpClient.Timeout = new TimeSpan(0, 0, timeOutSec);
+ httpClient.BaseAddress = new Uri($"{url}");
+
+ Log4net.WriteLine($"[GET] Request({guid})::{url}", LogType.HTTP);
+
+ DateTime requestTime = DateTime.Now;
+ response = await httpClient.GetFromJsonAsync(url);
+
+ Log4net.WriteLine($"[GET] Rseponse({guid}) ({(DateTime.Now - requestTime).TotalSeconds} sec)::{url}", LogType.HTTP);
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine(e);
+ }
+ }
+
+ return response;
+ }
+
+ protected HttpClientHandler GetClientHandler()
+ {
+ HttpClientHandler clientHandler = new HttpClientHandler();
+ clientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, sslPolicyErrors) =>
+ {
+ return true;
+ };
+
+ return clientHandler;
+ }
+
+ protected short SetTimeout(short timeOutSeconds)
+ {
+ short timeoutMin = 5;
+ short timeoutMax = 30;
+
+ timeOutSeconds = Math.Clamp(timeOutSeconds, timeoutMin, timeoutMax);
+
+ return timeOutSeconds;
+ }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Communication/Socket/Listener.cs b/Projects/SystemX.Core/SystemX.Core/Communication/Socket/Listener.cs
new file mode 100644
index 0000000..ed7d124
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Communication/Socket/Listener.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+using static System.Collections.Specialized.BitVector32;
+
+namespace SystemX.Core.Communication
+{
+ public class Listener
+ {
+ Socket _listenSocket;
+ Func _sessionFactory;
+
+ public void Init(IPEndPoint endPoint, Func sessionFactory, int register = 10, int backlog = 100)
+ {
+ _listenSocket = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
+ _sessionFactory += sessionFactory;
+
+ _listenSocket.Bind(endPoint);
+
+ // backlog : 최대 대기수
+ _listenSocket.Listen(backlog);
+
+ for (int i = 0; i < register; i++)
+ {
+ SocketAsyncEventArgs args = new SocketAsyncEventArgs();
+ args.Completed += new EventHandler(OnAcceptCompleted);
+ RegisterAccept(args);
+ }
+ }
+
+ void RegisterAccept(SocketAsyncEventArgs args)
+ {
+ args.AcceptSocket = null;
+
+ bool pending = _listenSocket.AcceptAsync(args);
+ if (pending == false)
+ OnAcceptCompleted(null, args);
+ }
+
+ void OnAcceptCompleted(object sender, SocketAsyncEventArgs args)
+ {
+ if (args.SocketError == SocketError.Success)
+ {
+ Session session = _sessionFactory.Invoke();
+ session.Start(args.AcceptSocket);
+ session.OnConnected(args.AcceptSocket.RemoteEndPoint);
+ }
+ else
+ {
+ Log4net.WriteLine(args.SocketError.ToString(), LogType.Error);
+ }
+
+ RegisterAccept(args);
+ }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Communication/Socket/RecvBuffer.cs b/Projects/SystemX.Core/SystemX.Core/Communication/Socket/RecvBuffer.cs
new file mode 100644
index 0000000..29f67bb
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Communication/Socket/RecvBuffer.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Communication
+{
+ public class RecvBuffer
+ {
+ // [r][][w][][][][][][][]
+ ArraySegment _buffer;
+ int _readPos;
+ int _writePos;
+
+ public RecvBuffer(int bufferSize)
+ {
+ _buffer = new ArraySegment(new byte[bufferSize], 0, bufferSize);
+ }
+
+ public int DataSize { get { return _writePos - _readPos; } }
+ public int FreeSize { get { return _buffer.Count - _writePos; } }
+
+ public ArraySegment ReadSegment
+ {
+ get { return new ArraySegment(_buffer.Array, _buffer.Offset + _readPos, DataSize); }
+ }
+
+ public ArraySegment WriteSegment
+ {
+ get { return new ArraySegment(_buffer.Array, _buffer.Offset + _writePos, FreeSize); }
+ }
+
+ public void Clean()
+ {
+ int dataSize = DataSize;
+ if (dataSize == 0)
+ {
+ // 남은 데이터가 없으면 복사하지 않고 커서 위치만 리셋
+ _readPos = _writePos = 0;
+ }
+ else
+ {
+ // 남은 찌끄레기가 있으면 시작 위치로 복사
+ Array.Copy(_buffer.Array, _buffer.Offset + _readPos, _buffer.Array, _buffer.Offset, dataSize);
+ _readPos = 0;
+ _writePos = dataSize;
+ }
+ }
+
+ public bool OnRead(int numOfBytes)
+ {
+ if (numOfBytes > DataSize)
+ return false;
+
+ _readPos += numOfBytes;
+ return true;
+ }
+
+ public bool OnWrite(int numOfBytes)
+ {
+ if (numOfBytes > FreeSize)
+ return false;
+
+ _writePos += numOfBytes;
+ return true;
+ }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Communication/Socket/Session.cs b/Projects/SystemX.Core/SystemX.Core/Communication/Socket/Session.cs
new file mode 100644
index 0000000..d4edf48
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Communication/Socket/Session.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Net;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.Communication
+{
+ public abstract class PacketSession : Session
+ {
+ public static readonly int HeaderSize = 2;
+
+ // [size(2)][packetId(2)][ ... ][size(2)][packetId(2)][ ... ]
+ public sealed override int OnRecv(ArraySegment buffer)
+ {
+ int processLen = 0;
+ int packetCount = 0;
+
+ while (true)
+ {
+ // 최소한 헤더는 파싱할 수 있는지 확인
+ if (buffer.Count < HeaderSize)
+ break;
+
+ // 패킷이 완전체로 도착했는지 확인
+ ushort dataSize = BitConverter.ToUInt16(buffer.Array, buffer.Offset);
+ if (buffer.Count < dataSize)
+ break;
+
+ // 여기까지 왔으면 패킷 조립 가능
+ OnRecvPacket(new ArraySegment(buffer.Array, buffer.Offset, dataSize));
+ packetCount++;
+
+ processLen += dataSize;
+ buffer = new ArraySegment(buffer.Array, buffer.Offset + dataSize, buffer.Count - dataSize);
+ }
+
+ return processLen;
+ }
+
+ public abstract void OnRecvPacket(ArraySegment buffer);
+ }
+
+ public abstract class Session
+ {
+ Socket _socket;
+ int _disconnected = 0;
+
+ RecvBuffer _recvBuffer = new RecvBuffer(65535);
+
+ object _lock = new object();
+ Queue> _sendQueue = new Queue>();
+ List> _pendingList = new List>();
+ SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs();
+ SocketAsyncEventArgs _recvArgs = new SocketAsyncEventArgs();
+
+ public abstract void OnConnected(EndPoint endPoint);
+ public abstract int OnRecv(ArraySegment buffer);
+ public abstract void OnSend(int numOfBytes);
+ public abstract void OnDisconnected(EndPoint endPoint);
+
+ void Clear()
+ {
+ lock (_lock)
+ {
+ _sendQueue.Clear();
+ _pendingList.Clear();
+ }
+ }
+
+ public void Start(Socket socket)
+ {
+ Log4net.WriteLine("Session Start");
+
+ _socket = socket;
+
+ _recvArgs.Completed += new EventHandler(OnRecvCompleted);
+ _sendArgs.Completed += new EventHandler(OnSendCompleted);
+
+ RegisterRecv();
+ }
+
+ public void Send(List> sendBuffList)
+ {
+ if (sendBuffList.Count == 0)
+ return;
+
+ lock (_lock)
+ {
+ foreach (ArraySegment sendBuff in sendBuffList)
+ _sendQueue.Enqueue(sendBuff);
+
+ if (_pendingList.Count == 0)
+ RegisterSend();
+ }
+ }
+
+ public void Send(ArraySegment sendBuff)
+ {
+ lock (_lock)
+ {
+ _sendQueue.Enqueue(sendBuff);
+ if (_pendingList.Count == 0)
+ RegisterSend();
+ }
+ }
+
+ public void Disconnect()
+ {
+ if (Interlocked.Exchange(ref _disconnected, 1) == 1)
+ return;
+
+ OnDisconnected(_socket.RemoteEndPoint);
+ _socket.Shutdown(SocketShutdown.Both);
+ _socket.Close();
+ Clear();
+ }
+
+ #region 네트워크 통신
+
+ void RegisterSend()
+ {
+ if (_disconnected == 1)
+ return;
+
+ while (_sendQueue.Count > 0)
+ {
+ ArraySegment buff = _sendQueue.Dequeue();
+ _pendingList.Add(buff);
+ }
+ _sendArgs.BufferList = _pendingList;
+
+ try
+ {
+ bool pending = _socket.SendAsync(_sendArgs);
+ if (pending == false)
+ OnSendCompleted(null, _sendArgs);
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine($"RegisterSend Failed {e}", LogType.Error);
+ }
+ }
+
+ void OnSendCompleted(object sender, SocketAsyncEventArgs args)
+ {
+ lock (_lock)
+ {
+ if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
+ {
+ try
+ {
+ _sendArgs.BufferList = null;
+ _pendingList.Clear();
+
+ OnSend(_sendArgs.BytesTransferred);
+
+ if (_sendQueue.Count > 0)
+ RegisterSend();
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine($"OnSendCompleted Failed {e}", LogType.Error);
+ }
+ }
+ else
+ {
+ Disconnect();
+ }
+ }
+ }
+
+ void RegisterRecv()
+ {
+ if (_disconnected == 1)
+ return;
+
+ _recvBuffer.Clean();
+ ArraySegment segment = _recvBuffer.WriteSegment;
+ _recvArgs.SetBuffer(segment.Array, segment.Offset, segment.Count);
+
+ try
+ {
+ bool pending = _socket.ReceiveAsync(_recvArgs);
+ if (pending == false)
+ OnRecvCompleted(null, _recvArgs);
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine($"RegisterRecv Failed {e}", LogType.Error);
+ }
+ }
+
+ void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
+ {
+ if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
+ {
+ try
+ {
+ // Write 커서 이동
+ if (_recvBuffer.OnWrite(args.BytesTransferred) == false)
+ {
+ Disconnect();
+ return;
+ }
+
+ // 컨텐츠 쪽으로 데이터를 넘겨주고 얼마나 처리했는지 받는다
+ int processLen = OnRecv(_recvBuffer.ReadSegment);
+ if (processLen < 0 || _recvBuffer.DataSize < processLen)
+ {
+ Disconnect();
+ return;
+ }
+
+ // Read 커서 이동
+ if (_recvBuffer.OnRead(processLen) == false)
+ {
+ Disconnect();
+ return;
+ }
+
+ RegisterRecv();
+ }
+ catch (Exception e)
+ {
+ Log4net.WriteLine($"OnRecvCompleted Failed {e}", LogType.Error);
+ }
+ }
+ else
+ {
+ Disconnect();
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/DB/DataBase.cs b/Projects/SystemX.Core/SystemX.Core/DB/DataBase.cs
new file mode 100644
index 0000000..8080bea
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/DB/DataBase.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.Json.Serialization;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.DB
+{
+ public class DataBase
+ {
+ [JsonPropertyName("IP")]
+ public string? IP { get; set; }
+
+ [JsonPropertyName("Port")]
+ public int Port { get; set; }
+
+ [JsonPropertyName("DBName")]
+ public string? DBName { get; set; }
+
+ [JsonPropertyName("DBID")]
+ public int DBID { get; set; }
+
+ [JsonPropertyName("DBContext")]
+ public string? DBContext { get; set; }
+
+ [JsonPropertyName("UserID")]
+ public string? UserID { get; set; }
+
+ [JsonPropertyName("Password")]
+ public string? Password { get; set; }
+ }
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/DB/EFCore.cs b/Projects/SystemX.Core/SystemX.Core/DB/EFCore.cs
new file mode 100644
index 0000000..bb3a997
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/DB/EFCore.cs
@@ -0,0 +1,106 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Storage;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace SystemX.Core.DB
+{
+ public static class EFCore
+ {
+ ///
+ /// Get SqlServer Connection String
+ ///
+ public static string ConvertToConnectionString(this DataBase dbConfig)
+ {
+ return $"server={dbConfig.IP},{dbConfig.Port}; user id={dbConfig.UserID}; password={dbConfig.Password}; database={dbConfig.DBName}; TrustServerCertificate=true;";
+ }
+
+ public static IEnumerable? GetEntity(this DbContext dbContext) where TEntity : class
+ {
+ IEnumerable? entity = default;
+
+ var type = dbContext.GetType();
+ try
+ {
+ foreach (var prop in type.GetProperties())
+ {
+ string propertyFullName = $"{prop.PropertyType.FullName}";
+ if (propertyFullName.ToLower().Contains(typeof(TEntity).Name.ToLower()))
+ {
+ entity = prop.GetValue(dbContext) as IEnumerable;
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Log4net.WriteLine(ex);
+ }
+
+ return entity;
+ }
+
+ #region Transaction
+ public static IDbContextTransaction CreateTransaction(this DbContext dbContext)
+ {
+ return dbContext.Database.BeginTransaction();
+ }
+
+ public static bool CloseTransaction(this DbContext dbContext, IDbContextTransaction transaction)
+ {
+ bool result = false;
+ try
+ {
+ dbContext.SaveChanges();
+ transaction.Commit();
+ result = true;
+
+ Log4net.WriteLine("Transaction Commit", LogType.Debug);
+ }
+ catch (Exception ex)
+ {
+ transaction.Rollback();
+ Log4net.WriteLine("Transaction Rollback", LogType.Error);
+ Log4net.WriteLine(ex);
+ }
+ transaction.Dispose();
+
+ return result;
+ }
+ #endregion
+
+ #region Transaction Async
+ public static async Task CreateTransactionAsync(this DbContext dbContext)
+ {
+ return await dbContext.Database.BeginTransactionAsync();
+ }
+
+ public static async Task CloseTransactionAsync(this DbContext dbContext, IDbContextTransaction transaction)
+ {
+ bool result = false;
+ try
+ {
+ await dbContext.SaveChangesAsync();
+ await transaction.CommitAsync();
+ result = true;
+
+ Log4net.WriteLine("Transaction Commit", LogType.Debug);
+ }
+ catch (Exception ex)
+ {
+ await transaction.RollbackAsync();
+ Log4net.WriteLine("Transaction Rollback", LogType.Error);
+ Log4net.WriteLine(ex);
+ }
+ await transaction.DisposeAsync();
+
+ return result;
+ }
+ #endregion
+ }
+
+
+}
diff --git a/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs b/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs
index d7ea1a8..8d10913 100644
--- a/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs
+++ b/Projects/SystemX.Core/SystemX.Core/Log4net/Log4net.cs
@@ -7,240 +7,239 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
-namespace SystemX.Core.Log4net
+
+#region LogType
+public enum LogType
{
- #region LogType
- public enum LogType
+ //default level
+ Info = 0,
+ Warn = 1,
+ Error = 2,
+ Debug = 3,
+ Fatal = 4,
+
+ //custom level
+ DB = 10,
+
+ HTTP = 20,
+ SOCKET = 21,
+}
+#endregion
+
+#region CustomLevel
+public static class Log4netCustomLevel
+{
+ public static readonly log4net.Core.Level DB = new log4net.Core.Level(10010, LogType.DB.ToString());
+ public static readonly log4net.Core.Level HTTP = new log4net.Core.Level(10020, LogType.HTTP.ToString());
+ public static readonly log4net.Core.Level SOCKET = new log4net.Core.Level(10021, LogType.SOCKET.ToString());
+
+ public static void SetCustomLevel(ILoggerRepository repo)
{
- //default level
- Info = 0,
- Warn = 1,
- Error = 2,
- Debug = 3,
- Fatal = 4,
-
- //custom level
- DB = 10,
-
- HTTP = 20,
- SOCKET = 21,
- }
- #endregion
-
- #region CustomLevel
- public static class Log4netCustomLevel
- {
- public static readonly log4net.Core.Level DB = new log4net.Core.Level(10010, LogType.DB.ToString());
- public static readonly log4net.Core.Level HTTP = new log4net.Core.Level(10020, LogType.HTTP.ToString());
- public static readonly log4net.Core.Level SOCKET = new log4net.Core.Level(10021, LogType.SOCKET.ToString());
-
- public static void SetCustomLevel(ILoggerRepository repo)
- {
- repo.LevelMap.Add(DB);
- repo.LevelMap.Add(HTTP);
- repo.LevelMap.Add(SOCKET);
- }
- }
- #endregion
-
- public static class Log4net
- {
- private static ILog? Manager;
-
- private static ILoggerRepository repo = LogManager.GetRepository();
-
- public static bool IsConfigLoad { get; set; } = false;
-
-
-
- static Log4net()
- {
- Console.WriteLine("log4net constructor");
- if (File.Exists("./log4net.config") == false)
- {
- File.WriteAllText("log4net.config", Config);
- }
-
- IsConfigLoad = OpenConfig(@"./log4net.config");
- }
-
- private static bool OpenConfig(string path)
- {
- bool result = true;
-
- try
- {
- Log4netCustomLevel.SetCustomLevel(repo);
-
- if (File.Exists(path) == false)
- result = false;
-
- log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
- Manager = LogManager.GetLogger("");
- }
- catch (Exception e)
- {
- Console.WriteLine("Log4net Init Error");
- Console.WriteLine(e.Message);
- result = false;
- }
-
- return result;
- }
-
- public static void WriteLine(T log, LogType logType = LogType.Info)
- {
- if (IsConfigLoad == false)
- {
- Console.ForegroundColor = ConsoleColor.Red;
- Console.WriteLine($"[Log4net Initialize Error] {log}");
- return;
- }
-
- switch (logType)
- {
- case LogType.Info:
- {
- Manager?.Info(log);
- break;
- }
- case LogType.Warn:
- {
- Manager?.Warn(log);
- break;
- }
- case LogType.Error:
- {
- Manager?.Error(log);
- break;
- }
- case LogType.Debug:
- {
- Manager?.Debug(log);
- break;
- }
- case LogType.Fatal:
- {
- Manager?.Fatal(log);
- break;
- }
- case LogType.DB:
- {
- Type? t = MethodBase.GetCurrentMethod()?.DeclaringType;
- if (t != null)
- {
- Manager?.Logger.Log(t, Log4netCustomLevel.DB, log, null);
- }
- break;
- }
- case LogType.HTTP:
- {
- Type? t = MethodBase.GetCurrentMethod()?.DeclaringType;
- if (t != null)
- {
- Manager?.Logger.Log(t, Log4netCustomLevel.HTTP, log, null);
- }
- break;
- }
- case LogType.SOCKET:
- {
- Type? t = MethodBase.GetCurrentMethod()?.DeclaringType;
- if (t != null)
- {
- Manager?.Logger.Log(t, Log4netCustomLevel.SOCKET, log, null);
- }
- break;
- }
- }
- }
-
- public static void WriteLine(Exception? ex)
- {
- WriteLine(ex?.Message, LogType.Error);
- }
-
-
- #region Config
- static string Config = @$"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-";
- #endregion
+ repo.LevelMap.Add(DB);
+ repo.LevelMap.Add(HTTP);
+ repo.LevelMap.Add(SOCKET);
}
}
+#endregion
+
+public static class Log4net
+{
+ private static ILog? Manager;
+
+ private static ILoggerRepository repo = LogManager.GetRepository();
+
+ public static bool IsConfigLoad { get; set; } = false;
+
+
+
+ static Log4net()
+ {
+ Console.WriteLine("log4net constructor");
+ if (File.Exists("./log4net.config") == false)
+ {
+ File.WriteAllText("log4net.config", Config);
+ }
+
+ IsConfigLoad = OpenConfig(@"./log4net.config");
+ }
+
+ private static bool OpenConfig(string path)
+ {
+ bool result = true;
+
+ try
+ {
+ Log4netCustomLevel.SetCustomLevel(repo);
+
+ if (File.Exists(path) == false)
+ result = false;
+
+ log4net.Config.XmlConfigurator.Configure(new FileInfo(path));
+ Manager = LogManager.GetLogger("");
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Log4net Init Error");
+ Console.WriteLine(e.Message);
+ result = false;
+ }
+
+ return result;
+ }
+
+ public static void WriteLine(T log, LogType logType = LogType.Info)
+ {
+ if (IsConfigLoad == false)
+ {
+ Console.ForegroundColor = ConsoleColor.Red;
+ Console.WriteLine($"[Log4net Initialize Error] {log}");
+ return;
+ }
+
+ switch (logType)
+ {
+ case LogType.Info:
+ {
+ Manager?.Info(log);
+ break;
+ }
+ case LogType.Warn:
+ {
+ Manager?.Warn(log);
+ break;
+ }
+ case LogType.Error:
+ {
+ Manager?.Error(log);
+ break;
+ }
+ case LogType.Debug:
+ {
+ Manager?.Debug(log);
+ break;
+ }
+ case LogType.Fatal:
+ {
+ Manager?.Fatal(log);
+ break;
+ }
+ case LogType.DB:
+ {
+ Type? t = MethodBase.GetCurrentMethod()?.DeclaringType;
+ if (t != null)
+ {
+ Manager?.Logger.Log(t, Log4netCustomLevel.DB, log, null);
+ }
+ break;
+ }
+ case LogType.HTTP:
+ {
+ Type? t = MethodBase.GetCurrentMethod()?.DeclaringType;
+ if (t != null)
+ {
+ Manager?.Logger.Log(t, Log4netCustomLevel.HTTP, log, null);
+ }
+ break;
+ }
+ case LogType.SOCKET:
+ {
+ Type? t = MethodBase.GetCurrentMethod()?.DeclaringType;
+ if (t != null)
+ {
+ Manager?.Logger.Log(t, Log4netCustomLevel.SOCKET, log, null);
+ }
+ break;
+ }
+ }
+ }
+
+ public static void WriteLine(Exception? ex)
+ {
+ WriteLine(ex?.Message, LogType.Error);
+ }
+
+
+ #region Config
+ static string Config = @$"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+";
+ #endregion
+}
+
/*
log4net 지정 가능 색상
diff --git a/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj b/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj
index 1d7f086..c96a53d 100644
--- a/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj
+++ b/Projects/SystemX.Core/SystemX.Core/SystemX.Core.csproj
@@ -17,6 +17,7 @@
+
diff --git a/Projects/SystemX.Core/SystemX.Core/Utils/DateTimeUtils.cs b/Projects/SystemX.Core/SystemX.Core/Utils/DateTimeUtils.cs
new file mode 100644
index 0000000..212a657
--- /dev/null
+++ b/Projects/SystemX.Core/SystemX.Core/Utils/DateTimeUtils.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+public static class DateTimeUtils
+{
+ public static long ToUnixTime(this DateTime dateTime)
+ {
+ DateTimeOffset dto = new DateTimeOffset(dateTime.ToUniversalTime());
+ return dto.ToUnixTimeSeconds();
+ }
+}
+
diff --git a/Projects/SystemX.Core/SystemX.Core/Json/JsonUtils.cs b/Projects/SystemX.Core/SystemX.Core/Utils/JsonUtils.cs
similarity index 97%
rename from Projects/SystemX.Core/SystemX.Core/Json/JsonUtils.cs
rename to Projects/SystemX.Core/SystemX.Core/Utils/JsonUtils.cs
index fab37aa..4cda787 100644
--- a/Projects/SystemX.Core/SystemX.Core/Json/JsonUtils.cs
+++ b/Projects/SystemX.Core/SystemX.Core/Utils/JsonUtils.cs
@@ -4,7 +4,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
-using SystemX.Core.Log4net;
public static class JsonUtils
{