diff --git a/PLibs/ConsoleApp1/ConsoleApp1.csproj b/PLibs/ConsoleApp1/ConsoleApp1.csproj
new file mode 100644
index 0000000..122d47d
--- /dev/null
+++ b/PLibs/ConsoleApp1/ConsoleApp1.csproj
@@ -0,0 +1,54 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {634E2E96-AFDD-4C4B-8C1B-E068889DA360}
+ Exe
+ ConsoleApp1
+ ConsoleApp1
+ v4.0
+ 512
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {08d0b4d1-328f-4bfd-8af3-f8fe66b5981d}
+ HeadTailSocketFW
+
+
+
+
\ No newline at end of file
diff --git a/PLibs/ConsoleApp1/Program.cs b/PLibs/ConsoleApp1/Program.cs
new file mode 100644
index 0000000..7b34925
--- /dev/null
+++ b/PLibs/ConsoleApp1/Program.cs
@@ -0,0 +1,42 @@
+using HeadTailSocketFW;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+
+namespace ConsoleApp1
+{
+ internal class Program
+ {
+ static void Main(string[] args)
+ {
+ string ip = "127.0.0.1";
+ int port = 5300;
+
+ HeadTailSocket socket = new HeadTailSocket();
+ socket.Head = new byte[] { 0x02 }; //STX
+ socket.Tail = new byte[] { 0x03 }; //ETX
+ socket.OnReceived -= Socket_OnReceived;
+ socket.OnReceived += Socket_OnReceived;
+
+ while (true)
+ {
+ if (socket == null)
+ continue;
+
+ if (!socket.IsConnected)
+ socket.Connect(ip, port);
+
+ Thread.Sleep(1000);
+ }
+ }
+
+ private static void Socket_OnReceived(object sender, byte[] buffer)
+ {
+ string rawPacket = Encoding.ASCII.GetString(buffer);
+ Console.WriteLine(rawPacket);
+ }
+ }
+}
diff --git a/PLibs/ConsoleApp1/Properties/AssemblyInfo.cs b/PLibs/ConsoleApp1/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..81c0a4a
--- /dev/null
+++ b/PLibs/ConsoleApp1/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
+// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
+// 이러한 특성 값을 변경하세요.
+[assembly: AssemblyTitle("ConsoleApp1")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("ConsoleApp1")]
+[assembly: AssemblyCopyright("Copyright © 2024")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
+// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
+// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
+[assembly: ComVisible(false)]
+
+// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
+[assembly: Guid("634e2e96-afdd-4c4b-8c1b-e068889da360")]
+
+// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
+//
+// 주 버전
+// 부 버전
+// 빌드 번호
+// 수정 버전
+//
+// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
+// 기본값으로 할 수 있습니다.
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PLibs/HeadTailSocketFW/ByteQueue.cs b/PLibs/HeadTailSocketFW/ByteQueue.cs
new file mode 100644
index 0000000..3f82600
--- /dev/null
+++ b/PLibs/HeadTailSocketFW/ByteQueue.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace HeadTailSocketFW
+{
+ public class ByteQueue
+ {
+ private object _lock = new object();
+
+ private Queue _queue;
+
+ public int Count { get { return _queue.Count; } }
+
+ public ByteQueue(int capacity)
+ {
+ lock (_lock)
+ {
+ _queue = new Queue(capacity);
+ }
+ }
+
+ public void Enqueue(byte item)
+ {
+ lock (_lock)
+ {
+ _queue.Enqueue(item);
+ }
+ }
+
+ public void Enqueue(byte[] array)
+ {
+ lock (_lock)
+ {
+ for (int i = 0; i < array.Length; i++)
+ {
+ _queue.Enqueue(array[i]);
+ }
+ }
+ }
+
+ public byte Dequeue()
+ {
+ lock (_lock)
+ {
+ return _queue.Dequeue();
+ }
+ }
+
+ public byte[] Dequeue(int size)
+ {
+ lock (_lock)
+ {
+ byte[] array = new byte[size];
+ for (int i = 0; i < array.Length; i++)
+ {
+ array[i] = _queue.Dequeue();
+ }
+
+ return array;
+ }
+ }
+
+ public int FindIndex(byte[] item, int startIndex = 0)
+ {
+ if (item == null || item.Length < 1)
+ return -1;
+ byte[] queueBytes = new byte[_queue.Count];
+ _queue.CopyTo(queueBytes, 0);
+ for (int i = startIndex; i < queueBytes.Length - item.Length + 1; i++)
+ {
+ if (queueBytes.Skip(i).Take(item.Length).SequenceEqual(item))
+ return i;
+ }
+
+ return -1;
+ }
+
+ public void Clear()
+ {
+ lock (_lock)
+ {
+ _queue.Clear();
+ }
+ }
+ }
+}
diff --git a/PLibs/HeadTailSocketFW/ClientSocket.cs b/PLibs/HeadTailSocketFW/ClientSocket.cs
new file mode 100644
index 0000000..0fb109b
--- /dev/null
+++ b/PLibs/HeadTailSocketFW/ClientSocket.cs
@@ -0,0 +1,243 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Net;
+using System.Net.Sockets;
+using System.Text;
+using System.Threading;
+using System.Timers;
+
+namespace HeadTailSocketFW
+{
+ public delegate void BufferReceivedHandler(object sender, byte[] buffer);
+ public delegate void ConnectionStatusHandler(object sender);
+
+
+ public class ClientSocket
+ {
+ protected readonly int MAX_BUFFER_SIZE = 8 * 1024;
+
+ protected readonly int SOCKET_BUFFER_SIZE = 2 * 1024; // 2KB
+ protected readonly int SEND_SOCKET_BUFFER_SIZE = 8 * 1024; // 8KB
+
+ protected Socket _socket;
+ protected byte[] _receiveSocketBuffer;
+ protected byte[] _sendSocketBuffer;
+ protected ByteQueue _buffer;
+
+ private ManualResetEvent _sendEvent = new ManualResetEvent(true);
+ private IPEndPoint _endPoint;
+ private bool _disposed;
+
+ public bool IsConnected => _socket != null && _socket.Connected;
+
+ public event BufferReceivedHandler OnReceived;
+ public event ConnectionStatusHandler OnConnected;
+ public event ConnectionStatusHandler OnDisconnected;
+
+ public bool EnableDebug { get; set; } = false;
+
+ public ClientSocket(int bufferSize = 2, int sendBufferSize = 8)
+ {
+ SOCKET_BUFFER_SIZE = bufferSize * 1024 > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : bufferSize * 1024;
+ SEND_SOCKET_BUFFER_SIZE = sendBufferSize * 1024 > MAX_BUFFER_SIZE ? MAX_BUFFER_SIZE : sendBufferSize * 1024;
+
+ _receiveSocketBuffer = new byte[SOCKET_BUFFER_SIZE];
+ _sendSocketBuffer = new byte[SEND_SOCKET_BUFFER_SIZE];
+ _buffer = new ByteQueue(MAX_BUFFER_SIZE);
+ }
+
+ public bool Connect(string address, int port)
+ {
+ Disconnect();
+ try
+ {
+ _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ IPAddress endPointAddress = IPAddress.Parse(address.Trim());
+ _endPoint = new IPEndPoint(endPointAddress, port);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.Connect] {ex.Message}");
+ return false;
+ }
+
+ return Reconnect();
+ }
+
+ public bool Connect(string address, int port, int timeout)
+ {
+ Disconnect();
+ try
+ {
+ _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
+ IPAddress endPointAddress = IPAddress.Parse(address.Trim());
+ _endPoint = new IPEndPoint(endPointAddress, port);
+ _socket.Connect(_endPoint);
+ _socket.Blocking = true;
+ }
+ catch (SocketException sockEx)
+ {
+ Debug.WriteLine($"[ClientSocket.Connect.Socket] {sockEx.Message}");
+ if (sockEx.SocketErrorCode != SocketError.WouldBlock)
+ {
+ _socket.Close();
+ return false;
+ }
+
+ if (!_socket.Poll(timeout * 1000, SelectMode.SelectWrite))
+ {
+ _socket.Close();
+ Debug.WriteLine($"[ClientSocket.Connect.Socket] Failed to connect.");
+ return false;
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.Connect] {ex.Message}");
+ return false;
+ }
+
+ return true;
+ }
+
+ public bool Reconnect()
+ {
+ if (_endPoint == null)
+ throw new Exception("EndPoint is not initialized.");
+
+ if (_socket.Connected)
+ _socket.Disconnect(true);
+
+ bool isSuccess = false;
+ try
+ {
+ _socket.Connect(_endPoint);
+ _socket.BeginReceive(_receiveSocketBuffer, 0, SOCKET_BUFFER_SIZE, SocketFlags.None, ReceiveCallback, this);
+ isSuccess = true;
+ }
+ catch (SocketException sockEx)
+ {
+ Debug.WriteLine($"[ClientSocket.Reconnect.Socket] {sockEx.ToString()}");
+
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.Reconnect] {ex.ToString()}");
+
+ throw;
+ }
+
+ if (isSuccess && OnConnected != null)
+ OnConnected(this);
+
+ return isSuccess;
+ }
+
+ public void Disconnect()
+ {
+ if (_socket == null)
+ return;
+
+ try
+ {
+ _socket.Close();
+ _socket = null;
+ if (OnDisconnected != null)
+ OnDisconnected(this);
+ }
+ catch (Exception)
+ {
+
+ throw;
+ }
+ }
+
+ protected void ReceiveCallback(IAsyncResult ar)
+ {
+ if (_socket == null)
+ return;
+
+ try
+ {
+ int receiveCount = _socket.EndReceive(ar);
+ if (receiveCount == 0)
+ {
+ Disconnect();
+ return;
+ }
+
+ try
+ {
+ _buffer.Enqueue(_receiveSocketBuffer);
+ if (EnableDebug)
+ {
+ if (EnableDebug)
+ Debug.WriteLine($"SocketDebug[{_endPoint.ToString()}] {Encoding.ASCII.GetString(_receiveSocketBuffer)}");
+ }
+
+ Interpret();
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.ReceiveCallback.Interpret] {ex.Message}");
+ _buffer.Clear();
+ }
+
+ if (_socket != null)
+ _socket.BeginReceive(_receiveSocketBuffer, 0, SOCKET_BUFFER_SIZE, SocketFlags.None, ReceiveCallback, this);
+
+ }
+ catch (SocketException sockEx)
+ {
+ Debug.WriteLine($"[ClientSocket.ReceiveCallback.Socket] {sockEx.Message}");
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.ReceiveCallback] {ex.Message}");
+ }
+ }
+
+ protected virtual void Interpret()
+ {
+ RaiseReceivedEvent(this, _buffer.Dequeue(_buffer.Count));
+ }
+
+ protected void RaiseReceivedEvent(object sender, byte[] buffer)
+ {
+ if (OnReceived != null)
+ OnReceived(sender, buffer);
+ }
+
+ public virtual void Send(byte[] data)
+ {
+ byte[] array = new byte[data.Length];
+ Array.Copy(data, array, data.Length);
+ try
+ {
+ _sendEvent.WaitOne();
+ _sendEvent.Reset();
+ _socket.BeginSend(array, 0, array.Length, SocketFlags.None, SendCallback, this);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.Send] -Message: {Encoding.ASCII.GetString(array)} -Exception: {ex.Message}");
+ }
+ }
+
+ protected void SendCallback(IAsyncResult ar)
+ {
+ _sendEvent.Set();
+ try
+ {
+ int sendCount = _socket.EndSend(ar);
+ }
+ catch (Exception ex)
+ {
+ Debug.WriteLine($"[ClientSocket.SendCallback] {ex.Message}");
+ }
+ }
+ }
+}
diff --git a/PLibs/HeadTailSocketFW/HeadTailSocket.cs b/PLibs/HeadTailSocketFW/HeadTailSocket.cs
new file mode 100644
index 0000000..6e40235
--- /dev/null
+++ b/PLibs/HeadTailSocketFW/HeadTailSocket.cs
@@ -0,0 +1,64 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace HeadTailSocketFW
+{
+ public class HeadTailSocket : ClientSocket
+ {
+ public byte[] Head { get; set; }
+ public byte[] Tail { get; set; }
+
+ public bool IsIncludeHeadTail { get; set; }
+
+ protected override void Interpret()
+ {
+ // Both head and tail are not exist.
+ if ((Head == null || Head.Length < 1) && (Tail == null || Tail.Length < 1))
+ {
+ RaiseReceivedEvent(this, _buffer.Dequeue(_buffer.Count));
+ return;
+ }
+
+ byte[] head;
+ byte[] tail;
+
+ // Head only
+ if ((Head != null || Head.Length > 0) && (Tail == null || Tail.Length < 1))
+ {
+ head = Head;
+ tail = Head;
+ }
+
+
+ // Tail only
+ if ((Head != null || Head.Length > 0) && (Tail == null || Tail.Length < 1))
+ {
+ head = Tail;
+ tail = Tail;
+ }
+
+ // Both
+ {
+ head = Head;
+ tail = Tail;
+ }
+
+ int startIndex = _buffer.FindIndex(head);
+ if (startIndex < 0)
+ return;
+
+ int endIndex = _buffer.FindIndex(tail, startIndex + 1);
+ if (endIndex < 0)
+ return;
+
+ if (startIndex >= endIndex)
+ return;
+
+ byte[] rawPacket = _buffer.Dequeue(endIndex + 1);
+ byte[] packet = new byte[endIndex - startIndex - 1];
+ Array.Copy(rawPacket, startIndex + 1, packet, 0, packet.Length);
+ RaiseReceivedEvent(this, packet);
+ }
+ }
+}
diff --git a/PLibs/HeadTailSocketFW/HeadTailSocketFW.csproj b/PLibs/HeadTailSocketFW/HeadTailSocketFW.csproj
new file mode 100644
index 0000000..9f60fae
--- /dev/null
+++ b/PLibs/HeadTailSocketFW/HeadTailSocketFW.csproj
@@ -0,0 +1,46 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {08D0B4D1-328F-4BFD-8AF3-F8FE66B5981D}
+ Library
+ Properties
+ HeadTailSocketFW
+ HeadTailSocketFW
+ v4.0
+ 512
+ true
+
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/PLibs/HeadTailSocketFW/Properties/AssemblyInfo.cs b/PLibs/HeadTailSocketFW/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..e9e7182
--- /dev/null
+++ b/PLibs/HeadTailSocketFW/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 어셈블리에 대한 일반 정보는 다음 특성 집합을 통해
+// 제어됩니다. 어셈블리와 관련된 정보를 수정하려면
+// 이러한 특성 값을 변경하세요.
+[assembly: AssemblyTitle("HeadTailSocketFW")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("HeadTailSocketFW")]
+[assembly: AssemblyCopyright("Copyright © 2024")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// ComVisible을 false로 설정하면 이 어셈블리의 형식이 COM 구성 요소에
+// 표시되지 않습니다. COM에서 이 어셈블리의 형식에 액세스하려면
+// 해당 형식에 대해 ComVisible 특성을 true로 설정하세요.
+[assembly: ComVisible(false)]
+
+// 이 프로젝트가 COM에 노출되는 경우 다음 GUID는 typelib의 ID를 나타냅니다.
+[assembly: Guid("08d0b4d1-328f-4bfd-8af3-f8fe66b5981d")]
+
+// 어셈블리의 버전 정보는 다음 네 가지 값으로 구성됩니다.
+//
+// 주 버전
+// 부 버전
+// 빌드 번호
+// 수정 버전
+//
+// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
+// 기본값으로 할 수 있습니다.
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/PLibs/PLibs.sln b/PLibs/PLibs.sln
index 71bf3d3..bece1c0 100644
--- a/PLibs/PLibs.sln
+++ b/PLibs/PLibs.sln
@@ -3,12 +3,16 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34511.84
MinimumVisualStudioVersion = 10.0.40219.1
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelHelperCore", "ExcelHelperCore\ExcelHelperCore.csproj", "{0DC75403-4084-462B-936B-FA86EA90811B}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ExcelHelperCore", "ExcelHelperCore\ExcelHelperCore.csproj", "{0DC75403-4084-462B-936B-FA86EA90811B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ExcelHelperFW", "ExcelHelperFW\ExcelHelperFW.csproj", "{FFEDB9F8-4285-493B-A30E-900C53D0CB55}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfHelperFW", "PdfHelperFW\PdfHelperFW.csproj", "{4F62138C-7B92-4FF8-8CB3-F37980AE99F2}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HeadTailSocketFW", "HeadTailSocketFW\HeadTailSocketFW.csproj", "{08D0B4D1-328F-4BFD-8AF3-F8FE66B5981D}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConsoleApp1", "ConsoleApp1\ConsoleApp1.csproj", "{634E2E96-AFDD-4C4B-8C1B-E068889DA360}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -27,6 +31,14 @@ Global
{4F62138C-7B92-4FF8-8CB3-F37980AE99F2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4F62138C-7B92-4FF8-8CB3-F37980AE99F2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4F62138C-7B92-4FF8-8CB3-F37980AE99F2}.Release|Any CPU.Build.0 = Release|Any CPU
+ {08D0B4D1-328F-4BFD-8AF3-F8FE66B5981D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {08D0B4D1-328F-4BFD-8AF3-F8FE66B5981D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {08D0B4D1-328F-4BFD-8AF3-F8FE66B5981D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {08D0B4D1-328F-4BFD-8AF3-F8FE66B5981D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {634E2E96-AFDD-4C4B-8C1B-E068889DA360}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {634E2E96-AFDD-4C4B-8C1B-E068889DA360}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {634E2E96-AFDD-4C4B-8C1B-E068889DA360}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {634E2E96-AFDD-4C4B-8C1B-E068889DA360}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE