From dbea3660a7020d4666d2d55f1aa424970d5bcbc6 Mon Sep 17 00:00:00 2001 From: syneffort Date: Thu, 21 Mar 2024 17:38:30 +0900 Subject: [PATCH] ClientSocket --- PLibs/ConsoleApp1/ConsoleApp1.csproj | 54 ++++ PLibs/ConsoleApp1/Program.cs | 42 +++ PLibs/ConsoleApp1/Properties/AssemblyInfo.cs | 36 +++ PLibs/HeadTailSocketFW/ByteQueue.cs | 88 +++++++ PLibs/HeadTailSocketFW/ClientSocket.cs | 243 ++++++++++++++++++ PLibs/HeadTailSocketFW/HeadTailSocket.cs | 64 +++++ .../HeadTailSocketFW/HeadTailSocketFW.csproj | 46 ++++ .../Properties/AssemblyInfo.cs | 36 +++ PLibs/PLibs.sln | 14 +- 9 files changed, 622 insertions(+), 1 deletion(-) create mode 100644 PLibs/ConsoleApp1/ConsoleApp1.csproj create mode 100644 PLibs/ConsoleApp1/Program.cs create mode 100644 PLibs/ConsoleApp1/Properties/AssemblyInfo.cs create mode 100644 PLibs/HeadTailSocketFW/ByteQueue.cs create mode 100644 PLibs/HeadTailSocketFW/ClientSocket.cs create mode 100644 PLibs/HeadTailSocketFW/HeadTailSocket.cs create mode 100644 PLibs/HeadTailSocketFW/HeadTailSocketFW.csproj create mode 100644 PLibs/HeadTailSocketFW/Properties/AssemblyInfo.cs 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