├── .travis.yml ├── LICENSE ├── README.md └── Source ├── Managed ├── NanoSockets.cs └── NanoSockets.csproj └── Native ├── CMakeLists.txt ├── nanosockets.c └── nanosockets.h /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - osx 3 | 4 | language: c 5 | 6 | compiler: clang 7 | 8 | script: 9 | - cmake ./Source/Native/ -DCMAKE_BUILD_TYPE=Release -DNANOSOCKETS_SHARED=1 10 | - cmake --build . 11 | 12 | deploy: 13 | provider: surge 14 | domain: nxrighthere.surge.sh 15 | skip_cleanup: true 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Stanislav Denisov (nxrighthere@gmail.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | alt logo 3 |

4 | 5 | [![GitHub release](https://img.shields.io/github/release/nxrighthere/NanoSockets.svg?style=flat-square)](https://github.com/nxrighthere/NanoSockets/releases) 6 | 7 | This is a highly portable, lightweight and straightforward, zero-cost abstraction of UDP sockets with dual-stack IPv4/IPv6 support for rapid implementation of message-oriented protocols. The library is designed for cross-language compatibility with C, C++, C#, and other languages. For .NET environment, functions support blittable pointers as an alternative to managed types for usage with unmanaged memory allocator. 8 | 9 | Building 10 | -------- 11 | The native library can be built using [CMake](https://cmake.org/download/) with GNU Make or Visual Studio. 12 | 13 | A managed assembly can be built using any available compiling platform that supports C# 4.0 or higher. 14 | 15 | Compiled libraries 16 | -------- 17 | You can grab compiled libraries from the [release](https://github.com/nxrighthere/NanoSockets/releases) section. 18 | 19 | Binaries are provided only for traditional platforms: Windows, Linux, and macOS (x64). 20 | 21 | Supported OS versions: 22 | - Windows 7 or higher 23 | - Linux 4.4 or higher 24 | - macOS 10.12 or higher 25 | 26 | Usage 27 | -------- 28 | Before starting to work, the library should be initialized using `NanoSockets.UDP.Initialize();` function. 29 | 30 | After the work is done, deinitialize the library using `NanoSockets.UDP.Deinitialize();` function. 31 | 32 | ### .NET environment 33 | ##### Start a new server 34 | ```c# 35 | Socket server = UDP.Create(256 * 1024, 256 * 1024); 36 | Address listenAddress = new Address(); 37 | 38 | listenAddress.port = port; 39 | 40 | if (UDP.SetIP(ref listenAddress, "::0") == Status.OK) 41 | Console.WriteLine("Address set!"); 42 | 43 | if (UDP.Bind(server, ref listenAddress) == 0) 44 | Console.WriteLine("Socket bound!"); 45 | 46 | if (UDP.SetDontFragment(server) != Status.OK) 47 | Console.WriteLine("Don't fragment option error!"); 48 | 49 | if (UDP.SetNonBlocking(server) != Status.OK) 50 | Console.WriteLine("Non-blocking option error!"); 51 | 52 | StringBuilder ip = new StringBuilder(UDP.hostNameSize); 53 | Address address = new Address(); 54 | byte[] buffer = new byte[1024]; 55 | 56 | while (!Console.KeyAvailable) { 57 | if (UDP.Poll(server, 15) > 0) { 58 | int dataLength = 0; 59 | 60 | while ((dataLength = UDP.Receive(server, ref address, buffer, buffer.Length)) > 0) { 61 | UDP.GetIP(ref address, ip, ip.Capacity); 62 | 63 | Console.WriteLine("Message received from - IP: " + ip + ", Data length: " + dataLength); 64 | 65 | UDP.Send(server, ref address, buffer, buffer.Length); 66 | } 67 | } 68 | } 69 | 70 | UDP.Destroy(ref server); 71 | ``` 72 | 73 | ##### Start a new client 74 | ```c# 75 | Socket client = UDP.Create(256 * 1024, 256 * 1024); 76 | Address connectionAddress = new Address(); 77 | 78 | connectionAddress.port = port; 79 | 80 | if (UDP.SetIP(ref connectionAddress, "::1") == Status.OK) 81 | Console.WriteLine("Address set!"); 82 | 83 | if (UDP.Connect(client, ref connectionAddress) == 0) 84 | Console.WriteLine("Socket connected!"); 85 | 86 | if (UDP.SetDontFragment(client) != Status.OK) 87 | Console.WriteLine("Don't fragment option error!"); 88 | 89 | if (UDP.SetNonBlocking(client) != Status.OK) 90 | Console.WriteLine("Non-blocking option error!"); 91 | 92 | byte[] buffer = new byte[1024]; 93 | 94 | UDP.Send(client, IntPtr.Zero, buffer, buffer.Length); 95 | 96 | while (!Console.KeyAvailable) { 97 | if (UDP.Poll(client, 15) > 0) { 98 | int dataLength = 0; 99 | 100 | while ((dataLength = UDP.Receive(client, IntPtr.Zero, buffer, buffer.Length)) > 0) { 101 | Console.WriteLine("Message received from server - Data length: " + dataLength); 102 | 103 | UDP.Send(client, IntPtr.Zero, buffer, buffer.Length); 104 | } 105 | } 106 | } 107 | 108 | UDP.Destroy(ref client); 109 | ``` 110 | 111 | ### Unity 112 | Usage is almost the same as in the .NET environment, except that the console functions must be replaced with functions provided by Unity. If the `UDP.Poll()` will be called in a game loop, then make sure that the timeout parameter set to 0 which means non-blocking. Also, keep Unity run in background by enabling the appropriate option in the player settings. 113 | 114 | API reference 115 | -------- 116 | ### Enumerations 117 | #### Status 118 | Definitions of status types for functions: 119 | 120 | `OK` 121 | 122 | `Error` 123 | 124 | ### Structures 125 | #### Socket 126 | Contains a blittable structure with socket handle. 127 | 128 | `Socket.handle` a socket handle. 129 | 130 | `Socket.IsCreated` checks if a socket is created. 131 | 132 | #### Address 133 | Contains a blittable structure with anonymous host data and port number. 134 | 135 | `Address.port` a port number. 136 | 137 | ### Classes 138 | #### UDP 139 | `UDP.Initialize()` initializes the native library. Should be called before starting the work. Returns status with a result. 140 | 141 | `UDP.Deinitialize()` deinitializes the native library. Should be called after the work is done. 142 | 143 | `UDP.Create(int sendBufferSize, int receiveBufferSize)` creates a new socket with a specified size of buffers for sending and receiving. Returns the `Socket` structure with the handle. 144 | 145 | `UDP.Destroy(ref Socket socket)` destroys a socket and reset the handle. 146 | 147 | `UDP.Bind(Socket socket, ref Address address)` assigns an address to a socket. The address parameter can be set to `IntPtr.Zero` to let the operating system assign any address. Returns 0 on success or != 0 on failure. 148 | 149 | `UDP.Connect(Socket socket, ref Address address)` connects a socket to an address. Returns 0 on success or != 0 on failure. 150 | 151 | `UDP.SetOption(Socket socket, int level, int optionName, ref int optionValue, int optionLength)` sets the current value for a socket option associated with a socket. This function can be used to set platform-specific options that were not specified at socket creation by default. Returns status with a result. 152 | 153 | `UDP.GetOption(Socket socket, int level, int optionName, ref int optionValue, ref int optionLength)` gets the current value for a socket option associated with a socket. A length of an option value should be initially set to an appropriate size. Returns status with a result. 154 | 155 | `UDP.SetNonBlocking(Socket socket, bool shouldBlock)` sets a non-blocking I/O mode for a socket. Returns status with a result. 156 | 157 | `UDP.SetDontFragment(Socket socket)` sets a don't fragment mode for a socket. Returns status with a result. 158 | 159 | `UDP.Poll(Socket socket, long timeout)` determines the status of a socket and waiting if necessary. This function can be used for readiness-oriented receiving. The timeout parameter may be specified in milliseconds to control polling duration. If a timeout of 0 is specified, this function will return immediately. If the time limit expired it will return 0. If a socket is ready for receiving it will return 1. Otherwise, it will return < 0 if an error occurred. 160 | 161 | `UDP.Send(Socket socket, ref Address address, byte[] buffer, int bufferLength)` sends a message to the specified address of a receiver. The address parameter can be set to `IntPtr.Zero` if a socket is connected to an address. A pointer `IntPtr` to a native buffer can be used instead of a reference to a byte array. Returns the total number of bytes sent, which can be less than the number indicated by the buffer length. Otherwise, it will return < 0 if an error occurred. 162 | 163 | `UDP.Receive(Socket socket, ref Address address, byte[] buffer, int bufferLength)` receives a message and obtains the address of a sender. The address parameter can be set to `IntPtr.Zero` to skip the address obtainment. A pointer `IntPtr` to a native buffer can be used instead of a reference to a byte array. Returns the total number of bytes received. Otherwise, it will return < 0 if an error occurred. 164 | 165 | `UDP.GetAddress(Socket socket, ref Address address)` gets an address from a bound or connected socket. This function is especially useful to determine the local association that has been set by the operating system. Returns status with a result. 166 | 167 | `UDP.IsEqual(ref Address left, ref Address right)` compares two addresses for equality. Returns status with a result. 168 | 169 | `UDP.SetIP(ref Address address, string ip)` sets an IP address. A pointer `IntPtr` can be used instead of the immutable string. Returns status with a result. 170 | 171 | `UDP.GetIP(ref Address address, StringBuilder ip, int ipLength)` gets an IP address. The capacity of the mutable string should be equal to `UDP.hostNameSize` constant field. A pointer `IntPtr` can be used instead of the mutable string. Returns status with a result. 172 | 173 | `UDP.SetHostName(ref Address address, string name)` sets host name or an IP address. A pointer `IntPtr` can be used instead of the immutable string. Returns status with a result. 174 | 175 | `UDP.GetHostName(ref Address address, StringBuilder name, int nameLength)` attempts to do a reverse lookup from the address. A pointer `IntPtr` can be used instead of the mutable string. Returns status with a result. 176 | -------------------------------------------------------------------------------- /Source/Managed/NanoSockets.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Lightweight UDP sockets abstraction for rapid implementation of message-oriented protocols 3 | * Copyright (c) 2019 Stanislav Denisov 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | using System; 25 | using System.Runtime.InteropServices; 26 | using System.Security; 27 | using System.Text; 28 | 29 | namespace NanoSockets { 30 | public enum Status { 31 | OK = 0, 32 | Error = -1 33 | } 34 | 35 | [StructLayout(LayoutKind.Explicit, Size = 8)] 36 | public struct Socket { 37 | [FieldOffset(0)] 38 | private long handle; 39 | 40 | public bool IsCreated { 41 | get { 42 | return handle > 0; 43 | } 44 | } 45 | } 46 | 47 | [StructLayout(LayoutKind.Explicit, Size = 18)] 48 | public struct Address : IEquatable
{ 49 | [FieldOffset(0)] 50 | private ulong address0; 51 | [FieldOffset(8)] 52 | private ulong address1; 53 | [FieldOffset(16)] 54 | public ushort port; 55 | [FieldOffset(16)] 56 | public ushort Port; 57 | 58 | public bool Equals(Address other) { 59 | return address0 == other.address0 && address1 == other.address1 && port == other.port; 60 | } 61 | 62 | public override bool Equals(object obj) { 63 | if (obj is Address) 64 | return Equals((Address)obj); 65 | 66 | return false; 67 | } 68 | 69 | public override int GetHashCode() { 70 | int hash = 17; 71 | 72 | hash = hash * 31 + address0.GetHashCode(); 73 | hash = hash * 31 + address1.GetHashCode(); 74 | hash = hash * 31 + port.GetHashCode(); 75 | 76 | return hash; 77 | } 78 | 79 | public override string ToString() { 80 | StringBuilder ip = new StringBuilder(64); 81 | 82 | NanoSockets.UDP.GetIP(ref this, ip, 64); 83 | 84 | return String.Format("IP:{0} Port:{1}", ip, this.port); 85 | } 86 | 87 | public static Address CreateFromIpPort(string ip, ushort port) { 88 | Address address = default(Address); 89 | 90 | NanoSockets.UDP.SetIP(ref address, ip); 91 | address.port = port; 92 | 93 | return address; 94 | } 95 | } 96 | 97 | [SuppressUnmanagedCodeSecurity] 98 | public static class UDP { 99 | #if __IOS__ || UNITY_IOS && !UNITY_EDITOR 100 | private const string nativeLibrary = "__Internal"; 101 | #else 102 | private const string nativeLibrary = "nanosockets"; 103 | #endif 104 | 105 | public const int hostNameSize = 1025; 106 | 107 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_initialize", CallingConvention = CallingConvention.Cdecl)] 108 | public static extern Status Initialize(); 109 | 110 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_deinitialize", CallingConvention = CallingConvention.Cdecl)] 111 | public static extern void Deinitialize(); 112 | 113 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_create", CallingConvention = CallingConvention.Cdecl)] 114 | public static extern Socket Create(int sendBufferSize, int receiveBufferSize); 115 | 116 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_destroy", CallingConvention = CallingConvention.Cdecl)] 117 | public static extern void Destroy(ref Socket socket); 118 | 119 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_bind", CallingConvention = CallingConvention.Cdecl)] 120 | public static extern int Bind(Socket socket, IntPtr address); 121 | 122 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_bind", CallingConvention = CallingConvention.Cdecl)] 123 | public static extern int Bind(Socket socket, ref Address address); 124 | 125 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_connect", CallingConvention = CallingConvention.Cdecl)] 126 | public static extern int Connect(Socket socket, ref Address address); 127 | 128 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_set_option", CallingConvention = CallingConvention.Cdecl)] 129 | public static extern Status SetOption(Socket socket, int level, int optionName, ref int optionValue, int optionLength); 130 | 131 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_get_option", CallingConvention = CallingConvention.Cdecl)] 132 | public static extern Status GetOption(Socket socket, int level, int optionName, ref int optionValue, ref int optionLength); 133 | 134 | public static Status SetNonBlocking(Socket socket, bool shouldBlock = false) => SetNonBlocking(socket, shouldBlock ? 0 : 1); 135 | 136 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_set_nonblocking", CallingConvention = CallingConvention.Cdecl)] 137 | private static extern Status SetNonBlocking(Socket socket, byte state); 138 | 139 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_set_dontfragment", CallingConvention = CallingConvention.Cdecl)] 140 | public static extern Status SetDontFragment(Socket socket); 141 | 142 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_poll", CallingConvention = CallingConvention.Cdecl)] 143 | public static extern int Poll(Socket socket, long timeout); 144 | 145 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send", CallingConvention = CallingConvention.Cdecl)] 146 | public static extern int Send(Socket socket, IntPtr address, IntPtr buffer, int bufferLength); 147 | 148 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send", CallingConvention = CallingConvention.Cdecl)] 149 | public static extern int Send(Socket socket, IntPtr address, byte[] buffer, int bufferLength); 150 | 151 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send", CallingConvention = CallingConvention.Cdecl)] 152 | public static extern int Send(Socket socket, ref Address address, IntPtr buffer, int bufferLength); 153 | 154 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send", CallingConvention = CallingConvention.Cdecl)] 155 | public static extern int Send(Socket socket, ref Address address, byte[] buffer, int bufferLength); 156 | 157 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send_offset", CallingConvention = CallingConvention.Cdecl)] 158 | public static extern int Send(Socket socket, IntPtr address, byte[] buffer, int offset, int bufferLength); 159 | 160 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send_offset", CallingConvention = CallingConvention.Cdecl)] 161 | public static extern int Send(Socket socket, ref Address address, byte[] buffer, int offset, int bufferLength); 162 | 163 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive", CallingConvention = CallingConvention.Cdecl)] 164 | public static extern int Receive(Socket socket, IntPtr address, IntPtr buffer, int bufferLength); 165 | 166 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive", CallingConvention = CallingConvention.Cdecl)] 167 | public static extern int Receive(Socket socket, IntPtr address, byte[] buffer, int bufferLength); 168 | 169 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive", CallingConvention = CallingConvention.Cdecl)] 170 | public static extern int Receive(Socket socket, ref Address address, IntPtr buffer, int bufferLength); 171 | 172 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive", CallingConvention = CallingConvention.Cdecl)] 173 | public static extern int Receive(Socket socket, ref Address address, byte[] buffer, int bufferLength); 174 | 175 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive_offset", CallingConvention = CallingConvention.Cdecl)] 176 | public static extern int Receive(Socket socket, IntPtr address, byte[] buffer, int offset, int bufferLength); 177 | 178 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive_offset", CallingConvention = CallingConvention.Cdecl)] 179 | public static extern int Receive(Socket socket, ref Address address, byte[] buffer, int offset, int bufferLength); 180 | 181 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_get", CallingConvention = CallingConvention.Cdecl)] 182 | public static extern Status GetAddress(Socket socket, ref Address address); 183 | 184 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_is_equal", CallingConvention = CallingConvention.Cdecl)] 185 | public static extern Status IsEqual(ref Address left, ref Address right); 186 | 187 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_set_ip", CallingConvention = CallingConvention.Cdecl)] 188 | public static extern Status SetIP(ref Address address, IntPtr ip); 189 | 190 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_set_ip", CallingConvention = CallingConvention.Cdecl)] 191 | public static extern Status SetIP(ref Address address, string ip); 192 | 193 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_get_ip", CallingConvention = CallingConvention.Cdecl)] 194 | public static extern Status GetIP(ref Address address, IntPtr ip, int ipLength); 195 | 196 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_get_ip", CallingConvention = CallingConvention.Cdecl)] 197 | public static extern Status GetIP(ref Address address, StringBuilder ip, int ipLength); 198 | 199 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_set_hostname", CallingConvention = CallingConvention.Cdecl)] 200 | public static extern Status SetHostName(ref Address address, IntPtr name); 201 | 202 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_set_hostname", CallingConvention = CallingConvention.Cdecl)] 203 | public static extern Status SetHostName(ref Address address, string name); 204 | 205 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_get_hostname", CallingConvention = CallingConvention.Cdecl)] 206 | public static extern Status GetHostName(ref Address address, IntPtr name, int nameLength); 207 | 208 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_address_get_hostname", CallingConvention = CallingConvention.Cdecl)] 209 | public static extern Status GetHostName(ref Address address, StringBuilder name, int nameLength); 210 | 211 | #if NANOSOCKETS_UNSAFE_API 212 | public static unsafe class Unsafe { 213 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_receive", CallingConvention = CallingConvention.Cdecl)] 214 | public static extern int Receive(Socket socket, Address* address, byte* buffer, int bufferLength); 215 | 216 | [DllImport(nativeLibrary, EntryPoint = "nanosockets_send", CallingConvention = CallingConvention.Cdecl)] 217 | public static extern int Send(Socket socket, Address* address, byte* buffer, int bufferLength); 218 | } 219 | #endif 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /Source/Managed/NanoSockets.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Library 5 | netstandard2.0;netcoreapp2.0 6 | x64 7 | NanoSockets 8 | 3 9 | 10 | 11 | 12 | false 13 | True 14 | 15 | 16 | 17 | true 18 | False 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Source/Native/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.6) 2 | project(nanosockets C) 3 | 4 | set(NANOSOCKETS_STATIC "0" CACHE BOOL "Create a static library") 5 | set(NANOSOCKETS_SHARED "0" CACHE BOOL "Create a shared library") 6 | 7 | if (MSYS OR MINGW) 8 | set(CMAKE_C_FLAGS "-static") 9 | 10 | add_definitions(-DWINVER=0x0601) 11 | add_definitions(-D_WIN32_WINNT=0x0601) 12 | endif() 13 | 14 | if (NANOSOCKETS_STATIC) 15 | add_library(nanosockets_static STATIC nanosockets.c ${SOURCES}) 16 | 17 | if (NOT UNIX) 18 | target_link_libraries(nanosockets_static ws2_32) 19 | SET_TARGET_PROPERTIES(nanosockets_static PROPERTIES PREFIX "") 20 | endif() 21 | endif() 22 | 23 | if (NANOSOCKETS_SHARED) 24 | add_definitions(-DNANOSOCKETS_DLL) 25 | add_library(nanosockets SHARED nanosockets.c ${SOURCES}) 26 | 27 | if (NOT UNIX) 28 | target_link_libraries(nanosockets ws2_32) 29 | SET_TARGET_PROPERTIES(nanosockets PROPERTIES PREFIX "") 30 | endif() 31 | endif() 32 | -------------------------------------------------------------------------------- /Source/Native/nanosockets.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Lightweight UDP sockets abstraction for rapid implementation of message-oriented protocols 3 | * Copyright (c) 2019 Stanislav Denisov 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | #define NANOSOCKETS_IMPLEMENTATION 25 | #include "nanosockets.h" 26 | -------------------------------------------------------------------------------- /Source/Native/nanosockets.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Lightweight UDP sockets abstraction for rapid implementation of message-oriented protocols 3 | * Copyright (c) 2019 Stanislav Denisov 4 | * 5 | * Permission is hereby granted, free of charge, to any person obtaining a copy 6 | * of this software and associated documentation files (the "Software"), to deal 7 | * in the Software without restriction, including without limitation the rights 8 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | * copies of the Software, and to permit persons to whom the Software is 10 | * furnished to do so, subject to the following conditions: 11 | * 12 | * The above copyright notice and this permission notice shall be included in all 13 | * copies or substantial portions of the Software. 14 | * 15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | * SOFTWARE. 22 | */ 23 | 24 | #ifndef NANOSOCKETS_H 25 | #define NANOSOCKETS_H 26 | 27 | #include 28 | 29 | #define NANOSOCKETS_VERSION_MAJOR 1 30 | #define NANOSOCKETS_VERSION_MINOR 0 31 | #define NANOSOCKETS_VERSION_PATCH 6 32 | 33 | #ifdef _WIN32 34 | #define NANOSOCKETS_WINDOWS 1 35 | #elif defined __unix__ 36 | #define NANOSOCKETS_UNIX 2 37 | #elif defined __APPLE__ 38 | #define NANOSOCKETS_MAC 3 39 | #endif 40 | 41 | #if defined(NANOSOCKETS_WINDOWS) && defined(NANOSOCKETS_DLL) 42 | #ifdef NANOSOCKETS_IMPLEMENTATION 43 | #define NANOSOCKETS_API __declspec(dllexport) 44 | #else 45 | #define NANOSOCKETS_API __declspec(dllimport) 46 | #endif 47 | #else 48 | #define NANOSOCKETS_API extern 49 | #endif 50 | 51 | #ifdef NANOSOCKETS_WINDOWS 52 | #include 53 | #else 54 | #ifdef NANOSOCKETS_MAC 55 | #define __APPLE_USE_RFC_3542 56 | #endif 57 | 58 | #include 59 | #endif 60 | 61 | #define NANOSOCKETS_HOSTNAME_SIZE 1025 62 | 63 | // API 64 | 65 | #ifdef __cplusplus 66 | extern "C" { 67 | #endif 68 | 69 | typedef int64_t NanoSocket; 70 | 71 | typedef enum _NanoStatus { 72 | NANOSOCKETS_STATUS_OK = 0, 73 | NANOSOCKETS_STATUS_ERROR = -1 74 | } NanoStatus; 75 | 76 | typedef struct _NanoAddress { 77 | union { 78 | struct in6_addr ipv6; 79 | struct { 80 | uint8_t zeros[10]; 81 | uint16_t ffff; 82 | struct in_addr ip; 83 | } ipv4; 84 | }; 85 | uint16_t port; 86 | } NanoAddress; 87 | 88 | NANOSOCKETS_API NanoStatus nanosockets_initialize(void); 89 | 90 | NANOSOCKETS_API void nanosockets_deinitialize(void); 91 | 92 | NANOSOCKETS_API NanoSocket nanosockets_create(int, int); 93 | 94 | NANOSOCKETS_API void nanosockets_destroy(NanoSocket*); 95 | 96 | NANOSOCKETS_API int nanosockets_bind(NanoSocket, const NanoAddress*); 97 | 98 | NANOSOCKETS_API int nanosockets_connect(NanoSocket, const NanoAddress*); 99 | 100 | NANOSOCKETS_API NanoStatus nanosockets_set_option(NanoSocket, int, int, const int*, int); 101 | 102 | NANOSOCKETS_API NanoStatus nanosockets_get_option(NanoSocket, int, int, int*, int*); 103 | 104 | NANOSOCKETS_API NanoStatus nanosockets_set_nonblocking(NanoSocket, uint8_t); 105 | 106 | NANOSOCKETS_API NanoStatus nanosockets_set_dontfragment(NanoSocket); 107 | 108 | NANOSOCKETS_API int nanosockets_poll(NanoSocket, long); 109 | 110 | NANOSOCKETS_API int nanosockets_send(NanoSocket, const NanoAddress*, const uint8_t*, int); 111 | 112 | NANOSOCKETS_API int nanosockets_send_offset(NanoSocket, const NanoAddress*, const uint8_t*, int, int); 113 | 114 | NANOSOCKETS_API int nanosockets_receive(NanoSocket, NanoAddress*, uint8_t*, int); 115 | 116 | NANOSOCKETS_API int nanosockets_receive_offset(NanoSocket, NanoAddress*, uint8_t*, int, int); 117 | 118 | NANOSOCKETS_API NanoStatus nanosockets_address_get(NanoSocket, NanoAddress*); 119 | 120 | NANOSOCKETS_API NanoStatus nanosockets_address_is_equal(const NanoAddress*, const NanoAddress*); 121 | 122 | NANOSOCKETS_API NanoStatus nanosockets_address_set_ip(NanoAddress*, const char*); 123 | 124 | NANOSOCKETS_API NanoStatus nanosockets_address_get_ip(const NanoAddress*, char*, int); 125 | 126 | NANOSOCKETS_API NanoStatus nanosockets_address_set_hostname(NanoAddress*, const char*); 127 | 128 | NANOSOCKETS_API NanoStatus nanosockets_address_get_hostname(const NanoAddress*, char*, int); 129 | 130 | #ifdef __cplusplus 131 | } 132 | #endif 133 | 134 | #if defined(NANOSOCKETS_IMPLEMENTATION) && !defined(NANOSOCKETS_IMPLEMENTATION_DONE) 135 | #define NANOSOCKETS_IMPLEMENTATION_DONE 1 136 | 137 | #include 138 | 139 | #ifndef NANOSOCKETS_WINDOWS 140 | #include 141 | #include 142 | #include 143 | #include 144 | #include 145 | #endif 146 | 147 | // Macros 148 | 149 | #define NANOSOCKETS_HOST_TO_NET_16(value) (htons(value)) 150 | #define NANOSOCKETS_HOST_TO_NET_32(value) (htonl(value)) 151 | #define NANOSOCKETS_NET_TO_HOST_16(value) (ntohs(value)) 152 | #define NANOSOCKETS_NET_TO_HOST_32(value) (ntohl(value)) 153 | 154 | // Functions 155 | 156 | inline static int nanosockets_array_is_zeroed(const uint8_t* array, int length) { 157 | for (size_t i = 0; i < length; i++) { 158 | if (array[i] != 0) 159 | return -1; 160 | } 161 | 162 | return 0; 163 | } 164 | 165 | inline static void nanosockets_address_extract(NanoAddress* address, const struct sockaddr_storage* source) { 166 | if (source->ss_family == AF_INET) { 167 | struct sockaddr_in* socketAddress = (struct sockaddr_in*)source; 168 | 169 | memset(address, 0, sizeof(address->ipv4.zeros)); 170 | 171 | address->ipv4.ffff = 0xFFFF; 172 | address->ipv4.ip = socketAddress->sin_addr; 173 | address->port = NANOSOCKETS_NET_TO_HOST_16(socketAddress->sin_port); 174 | } else if (source->ss_family == AF_INET6) { 175 | struct sockaddr_in6* socketAddress = (struct sockaddr_in6*)source; 176 | 177 | address->ipv6 = socketAddress->sin6_addr; 178 | address->port = NANOSOCKETS_NET_TO_HOST_16(socketAddress->sin6_port); 179 | } 180 | } 181 | 182 | NanoStatus nanosockets_initialize(void) { 183 | #ifdef NANOSOCKETS_WINDOWS 184 | WSADATA wsaData = { 0 }; 185 | 186 | if (WSAStartup(MAKEWORD(2, 2), &wsaData)) 187 | return NANOSOCKETS_STATUS_ERROR; 188 | 189 | if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { 190 | WSACleanup(); 191 | 192 | return NANOSOCKETS_STATUS_ERROR; 193 | } 194 | #endif 195 | 196 | return NANOSOCKETS_STATUS_OK; 197 | } 198 | 199 | void nanosockets_deinitialize(void) { 200 | #ifdef NANOSOCKETS_WINDOWS 201 | WSACleanup(); 202 | #endif 203 | } 204 | 205 | NanoSocket nanosockets_create(int sendBufferSize, int receiveBufferSize) { 206 | int socketType = SOCK_DGRAM; 207 | 208 | #ifdef SOCK_CLOEXEC 209 | socketType |= SOCK_CLOEXEC; 210 | #endif 211 | 212 | NanoSocket socketHandle = socket(PF_INET6, socketType, 0); 213 | 214 | if (socketHandle > -1) { 215 | int onlyIPv6 = 0; 216 | 217 | if (setsockopt(socketHandle, IPPROTO_IPV6, IPV6_V6ONLY, (const char*)&onlyIPv6, sizeof(onlyIPv6)) != 0) 218 | goto destroy; 219 | 220 | if (setsockopt(socketHandle, SOL_SOCKET, SO_SNDBUF, (const char*)&sendBufferSize, sizeof(sendBufferSize)) != 0) 221 | goto destroy; 222 | 223 | if (setsockopt(socketHandle, SOL_SOCKET, SO_RCVBUF, (const char*)&receiveBufferSize, sizeof(receiveBufferSize)) != 0) 224 | goto destroy; 225 | 226 | goto create; 227 | 228 | destroy: 229 | 230 | nanosockets_destroy(&socketHandle); 231 | 232 | return -1; 233 | } 234 | 235 | create: 236 | 237 | return socketHandle; 238 | } 239 | 240 | void nanosockets_destroy(NanoSocket* socket) { 241 | if (*socket > 0) { 242 | #if NANOSOCKETS_WINDOWS 243 | closesocket(*socket); 244 | #else 245 | close(*socket); 246 | #endif 247 | 248 | *socket = 0; 249 | } 250 | } 251 | 252 | int nanosockets_bind(NanoSocket socket, const NanoAddress* address) { 253 | struct sockaddr_in6 socketAddress = { 0 }; 254 | 255 | socketAddress.sin6_family = AF_INET6; 256 | 257 | if (address == NULL) { 258 | socketAddress.sin6_addr = in6addr_any; 259 | socketAddress.sin6_port = 0; 260 | } else { 261 | socketAddress.sin6_addr = address->ipv6; 262 | socketAddress.sin6_port = NANOSOCKETS_HOST_TO_NET_16(address->port); 263 | } 264 | 265 | return bind(socket, (struct sockaddr*)&socketAddress, sizeof(socketAddress)); 266 | } 267 | 268 | int nanosockets_connect(NanoSocket socket, const NanoAddress* address) { 269 | struct sockaddr_in6 socketAddress = { 0 }; 270 | 271 | socketAddress.sin6_family = AF_INET6; 272 | socketAddress.sin6_addr = address->ipv6; 273 | socketAddress.sin6_port = NANOSOCKETS_HOST_TO_NET_16(address->port); 274 | 275 | return connect(socket, (struct sockaddr*)&socketAddress, sizeof(socketAddress)); 276 | } 277 | 278 | NanoStatus nanosockets_set_option(NanoSocket socket, int level, int optionName, const int* optionValue, int optionLength) { 279 | if (setsockopt(socket, level, optionName, (const char*)optionValue, optionLength) == 0) 280 | return NANOSOCKETS_STATUS_OK; 281 | else 282 | return NANOSOCKETS_STATUS_ERROR; 283 | } 284 | 285 | NanoStatus nanosockets_get_option(NanoSocket socket, int level, int optionName, int* optionValue, int* optionLength) { 286 | if (getsockopt(socket, level, optionName, (char*)optionValue, (socklen_t*)optionLength) == 0) 287 | return NANOSOCKETS_STATUS_OK; 288 | else 289 | return NANOSOCKETS_STATUS_ERROR; 290 | } 291 | 292 | NanoStatus nanosockets_set_nonblocking(NanoSocket socket, uint8_t state) { 293 | #ifdef NANOSOCKETS_WINDOWS 294 | DWORD nonBlocking = state; 295 | 296 | if (ioctlsocket(socket, FIONBIO, &nonBlocking) != 0) 297 | return NANOSOCKETS_STATUS_ERROR; 298 | #else 299 | int nonBlocking = state; 300 | 301 | if (fcntl(socket, F_SETFL, O_NONBLOCK, nonBlocking) == -1) 302 | return NANOSOCKETS_STATUS_ERROR; 303 | #endif 304 | 305 | return NANOSOCKETS_STATUS_OK; 306 | } 307 | 308 | NanoStatus nanosockets_set_dontfragment(NanoSocket socket) { 309 | #ifdef IP_DONTFRAG 310 | int dontFragment = 1; 311 | 312 | if (setsockopt(socket, IPPROTO_IPV6, IP_DONTFRAG, (const char*)&dontFragment, sizeof(dontFragment)) != 0) 313 | return NANOSOCKETS_STATUS_ERROR; 314 | #elif defined IP_DONTFRAGMENT 315 | DWORD dontFragment = 1; 316 | 317 | if (setsockopt(socket, IPPROTO_IPV6, IP_DONTFRAGMENT, (const char*)&dontFragment, sizeof(dontFragment)) != 0) 318 | return NANOSOCKETS_STATUS_ERROR; 319 | #elif defined IPV6_DONTFRAG 320 | int dontFragment = 1; 321 | 322 | if (setsockopt(socket, IPPROTO_IPV6, IPV6_DONTFRAG, (const char*)&dontFragment, sizeof(dontFragment)) != 0) 323 | return NANOSOCKETS_STATUS_ERROR; 324 | #else 325 | #error "Don't fragment socket option is not implemented for this platform" 326 | #endif 327 | 328 | return NANOSOCKETS_STATUS_OK; 329 | } 330 | 331 | int nanosockets_poll(NanoSocket socket, long timeout) { 332 | fd_set set = { 0 }; 333 | struct timeval time = { 0 }; 334 | 335 | FD_ZERO(&set); 336 | FD_SET(socket, &set); 337 | 338 | time.tv_sec = timeout / 1000; 339 | time.tv_usec = (timeout % 1000) * 1000; 340 | 341 | #pragma warning(suppress: 4244) 342 | return select(socket + 1, &set, NULL, NULL, &time); 343 | } 344 | 345 | int nanosockets_send(NanoSocket socket, const NanoAddress* address, const uint8_t* buffer, int bufferLength) { 346 | struct sockaddr_in6 socketAddress = { 0 }; 347 | 348 | if (address != NULL) { 349 | socketAddress.sin6_family = AF_INET6; 350 | socketAddress.sin6_addr = address->ipv6; 351 | socketAddress.sin6_port = NANOSOCKETS_HOST_TO_NET_16(address->port); 352 | } 353 | 354 | return sendto(socket, (const char*)buffer, bufferLength, 0, (address != NULL ? (struct sockaddr*)&socketAddress : NULL), sizeof(socketAddress)); 355 | } 356 | 357 | int nanosockets_send_offset(NanoSocket socket, const NanoAddress* address, const uint8_t* buffer, int offset, int bufferLength) { 358 | return nanosockets_send(socket, address, buffer + offset, bufferLength); 359 | } 360 | 361 | int nanosockets_receive(NanoSocket socket, NanoAddress* address, uint8_t* buffer, int bufferLength) { 362 | struct sockaddr_storage addressStorage = { 0 }; 363 | socklen_t addressLength = sizeof(addressStorage); 364 | 365 | int socketBytes = recvfrom(socket, (char*)buffer, bufferLength, 0, (struct sockaddr*)&addressStorage, &addressLength); 366 | 367 | if (address != NULL) 368 | nanosockets_address_extract(address, &addressStorage); 369 | 370 | return socketBytes; 371 | } 372 | 373 | int nanosockets_receive_offset(NanoSocket socket, NanoAddress* address, uint8_t* buffer, int offset, int bufferLength) { 374 | return nanosockets_receive(socket, address, buffer + offset, bufferLength); 375 | } 376 | 377 | NanoStatus nanosockets_address_get(NanoSocket socket, NanoAddress* address) { 378 | struct sockaddr_storage addressStorage = { 0 }; 379 | socklen_t addressLength = sizeof(addressStorage); 380 | 381 | if (getsockname(socket, (struct sockaddr*)&addressStorage, &addressLength) == -1) 382 | return NANOSOCKETS_STATUS_ERROR; 383 | 384 | nanosockets_address_extract(address, &addressStorage); 385 | 386 | return NANOSOCKETS_STATUS_OK; 387 | } 388 | 389 | NanoStatus nanosockets_address_is_equal(const NanoAddress* left, const NanoAddress* right) { 390 | if (memcmp(left, right, sizeof(struct in6_addr)) == 0 && left->port == right->port) 391 | return NANOSOCKETS_STATUS_OK; 392 | else 393 | return NANOSOCKETS_STATUS_ERROR; 394 | } 395 | 396 | NanoStatus nanosockets_address_set_ip(NanoAddress* address, const char* ip) { 397 | int type = AF_INET6; 398 | void* destination = &address->ipv6; 399 | 400 | if (strchr(ip, ':') == NULL) { 401 | type = AF_INET; 402 | 403 | memset(address, 0, sizeof(address->ipv4.zeros)); 404 | 405 | address->ipv4.ffff = 0xFFFF; 406 | destination = &address->ipv4.ip; 407 | } 408 | 409 | if (!inet_pton(type, ip, destination)) 410 | return NANOSOCKETS_STATUS_ERROR; 411 | 412 | return NANOSOCKETS_STATUS_OK; 413 | } 414 | 415 | NanoStatus nanosockets_address_get_ip(const NanoAddress* address, char* ip, int ipLength) { 416 | if (address->ipv4.ffff == 0xFFFF && nanosockets_array_is_zeroed(address->ipv4.zeros, sizeof(address->ipv4.zeros)) == 0) { 417 | if (inet_ntop(AF_INET, &address->ipv4.ip, ip, ipLength) == NULL) 418 | return NANOSOCKETS_STATUS_ERROR; 419 | } else if (inet_ntop(AF_INET6, &address->ipv6, ip, ipLength) == NULL) { 420 | return NANOSOCKETS_STATUS_ERROR; 421 | } 422 | 423 | return NANOSOCKETS_STATUS_OK; 424 | } 425 | 426 | NanoStatus nanosockets_address_set_hostname(NanoAddress* address, const char* name) { 427 | struct addrinfo addressInfo = { 0 }, *result = NULL, *resultList = NULL; 428 | 429 | addressInfo.ai_family = AF_UNSPEC; 430 | 431 | if (getaddrinfo(name, NULL, &addressInfo, &resultList) != 0) 432 | return NANOSOCKETS_STATUS_ERROR; 433 | 434 | for (result = resultList; result != NULL; result = result->ai_next) { 435 | if (result->ai_addr != NULL && result->ai_addrlen >= sizeof(struct sockaddr_in)) { 436 | if (result->ai_family == AF_INET) { 437 | struct sockaddr_in* socketAddress = (struct sockaddr_in*)result->ai_addr; 438 | 439 | memset(address, 0, sizeof(address->ipv4.zeros)); 440 | 441 | address->ipv4.ffff = 0xFFFF; 442 | address->ipv4.ip.s_addr = socketAddress->sin_addr.s_addr; 443 | 444 | freeaddrinfo(resultList); 445 | 446 | return NANOSOCKETS_STATUS_OK; 447 | } else if (result->ai_family == AF_INET6) { 448 | struct sockaddr_in6* socketAddress = (struct sockaddr_in6*)result->ai_addr; 449 | 450 | address->ipv6 = socketAddress->sin6_addr; 451 | 452 | freeaddrinfo(resultList); 453 | 454 | return NANOSOCKETS_STATUS_OK; 455 | } 456 | } 457 | } 458 | 459 | if (resultList != NULL) 460 | freeaddrinfo(resultList); 461 | 462 | return nanosockets_address_set_ip(address, name); 463 | } 464 | 465 | NanoStatus nanosockets_address_get_hostname(const NanoAddress* address, char* name, int nameLength) { 466 | struct sockaddr_in6 socketAddress = { 0 }; 467 | 468 | socketAddress.sin6_family = AF_INET6; 469 | socketAddress.sin6_addr = address->ipv6; 470 | socketAddress.sin6_port = NANOSOCKETS_HOST_TO_NET_16(address->port); 471 | 472 | int error = getnameinfo((struct sockaddr*)&socketAddress, sizeof(socketAddress), name, nameLength, NULL, 0, NI_NAMEREQD); 473 | 474 | if (!error) { 475 | if (name != NULL && nameLength > 0 && !memchr(name, '\0', nameLength)) 476 | return NANOSOCKETS_STATUS_ERROR; 477 | 478 | return NANOSOCKETS_STATUS_OK; 479 | } 480 | 481 | if (error != EAI_NONAME) 482 | return NANOSOCKETS_STATUS_ERROR; 483 | 484 | return nanosockets_address_get_ip(address, name, nameLength); 485 | } 486 | 487 | #endif // NANOSOCKETS_IMPLEMENTATION 488 | 489 | #endif // NANOSOCKETS_H 490 | --------------------------------------------------------------------------------