├── .gitattributes ├── .gitignore ├── Assets └── Scripts │ └── UdpSocket_BitStream_Utilities │ ├── BitStreamsUsageExample.cs │ ├── BitwiseMemoryInputStream.cs │ ├── BitwiseMemoryOutputStream.cs │ ├── UdpSocketManager.cs │ └── UdpSocketManagerUsageExample.cs ├── LICENSE ├── Packages ├── UdpSocket_BitStream_Utilities_v1.0.unitypackage └── UdpSocket_BitStream_Utilities_v1.1.unitypackage └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store -------------------------------------------------------------------------------- /Assets/Scripts/UdpSocket_BitStream_Utilities/BitStreamsUsageExample.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | public class BitStreamsUsageExample : MonoBehaviour { 6 | 7 | private UdpSocketManager _udpSocketManager; 8 | private bool _isListenPortLogged = false; 9 | 10 | void Start() { 11 | _udpSocketManager = new UdpSocketManager("127.0.0.1", 55056); 12 | StartCoroutine(_udpSocketManager.initSocket()); 13 | StartCoroutine(sendAndReceiveStream()); 14 | 15 | } 16 | 17 | IEnumerator sendAndReceiveStream() { 18 | while (!_udpSocketManager.isInitializationCompleted()) { 19 | yield return null; 20 | } 21 | 22 | if (UdpSocketManagerUsageExample.isActive) { 23 | Debug.LogWarning("UdpSocketManagerUsageExample and BitStreamsUsageExample scripts couldn't be used concurrently!"); 24 | yield break; 25 | } 26 | 27 | if (!_isListenPortLogged) { 28 | Debug.Log("UdpSocketManager, listen port: " + _udpSocketManager.getListenPort()); 29 | _isListenPortLogged = true; 30 | } 31 | 32 | BitwiseMemoryOutputStream outStream = new BitwiseMemoryOutputStream(); 33 | outStream.writeBool(true); 34 | outStream.writeByte(0xfa); 35 | outStream.writeDouble(1.2); 36 | outStream.writeFloat(81.12f); 37 | outStream.writeInt(7, 3); 38 | outStream.writeLong(8, 4); 39 | outStream.writeSigned(-7, 3); 40 | outStream.writeSignedLong(-8, 4); 41 | outStream.writeString("Hello World!"); 42 | Debug.Log("UdpSocketManager, stream have sent!"); 43 | 44 | _udpSocketManager.send(outStream.getBuffer()); 45 | 46 | IList recPackets = _udpSocketManager.receive(); 47 | 48 | while (recPackets.Count < 1) { 49 | yield return null; 50 | recPackets = _udpSocketManager.receive(); 51 | } 52 | 53 | byte[] echoPacket = recPackets[0]; 54 | 55 | BitwiseMemoryInputStream inStream = new BitwiseMemoryInputStream(echoPacket); 56 | Debug.Assert(inStream.readBool() == true); 57 | Debug.Assert(inStream.readByte() == 0xfa); 58 | Debug.Assert(inStream.readDouble() == 1.2); 59 | Debug.Assert(inStream.readFloat() == 81.12f); 60 | Debug.Assert(inStream.readInt(3) == 7); 61 | Debug.Assert(inStream.readLong(4) == 8); 62 | Debug.Assert(inStream.readSignedInt(3) == -7); 63 | Debug.Assert(inStream.readSignedLong(4) == -8); 64 | Debug.Assert(inStream.readString() == "Hello World!"); 65 | Debug.Log("UdpSocketManager, stream have received!"); 66 | } 67 | 68 | private void OnDestroy() { 69 | if(_udpSocketManager != null) { 70 | _udpSocketManager.closeSocketThreads(); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Assets/Scripts/UdpSocket_BitStream_Utilities/BitwiseMemoryInputStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | 5 | /** 6 | * This class is a bit-wise memory output stream implementation 7 | */ 8 | public class BitwiseMemoryInputStream { 9 | 10 | readonly private int _capacity; 11 | private int _head; 12 | readonly private byte[] _buffer; 13 | 14 | public BitwiseMemoryInputStream(byte[] buffer) { 15 | 16 | _buffer = buffer; 17 | _capacity = buffer.Length; 18 | _head = 0; 19 | } 20 | 21 | /** 22 | * reads an int value from buffer by 32 bits 23 | */ 24 | public int readInt() { 25 | return readInt(32); 26 | } 27 | 28 | /** 29 | * reads an int value from buffer by specified bits count 30 | */ 31 | public int readInt(int bitCount) { 32 | 33 | int byteCount = bitCount / 8; 34 | if (bitCount % 8 > 0) { 35 | byteCount++; 36 | } 37 | byte[] resBytes = new byte[byteCount]; 38 | 39 | int bitsToRead = (bitCount % 8 == 0) ? 8 : bitCount % 8; 40 | resBytes[0] = readBits(bitsToRead); 41 | 42 | int i = 1; 43 | //write all the bytes 44 | while (i < byteCount) { 45 | resBytes[i] = readBits(8); 46 | ++i; 47 | } 48 | return bytesToInt(resBytes); 49 | } 50 | 51 | /** 52 | * reads a float value from buffer by 32 bits 53 | */ 54 | public float readFloat() { 55 | return readFloat(32); 56 | } 57 | 58 | /** 59 | * reads a float value from buffer by specified bits count 60 | */ 61 | private float readFloat(int bitCount) { 62 | 63 | int byteCount = bitCount / 8; 64 | if (bitCount % 8 > 0) { 65 | byteCount++; 66 | } 67 | byte[] resBytes = new byte[byteCount]; 68 | 69 | int bitsToRead = (bitCount % 8 == 0) ? 8 : bitCount % 8; 70 | resBytes[0] = readBits(bitsToRead); 71 | 72 | int i = 1; 73 | //write all the bytes 74 | while (i < byteCount) { 75 | resBytes[i] = readBits(8); 76 | ++i; 77 | } 78 | int intRepresentation = BitConverter.ToInt32(resBytes, 0); 79 | return BitConverter.ToSingle(intToBytes(intRepresentation, resBytes.Length), 0); 80 | } 81 | 82 | /** 83 | * reads a long value from buffer by 64 bits 84 | */ 85 | public long readLong() { 86 | return readLong(64); 87 | } 88 | 89 | /** 90 | * reads a long value from buffer by specified bits count 91 | */ 92 | public long readLong(int bitCount) { 93 | 94 | int byteCount = bitCount / 8; 95 | if (bitCount % 8 > 0) { 96 | byteCount++; 97 | } 98 | byte[] resBytes = new byte[byteCount]; 99 | 100 | int bitsToRead = (bitCount % 8 == 0) ? 8 : bitCount % 8; 101 | resBytes[0] = readBits(bitsToRead); 102 | 103 | int i = 1; 104 | //write all the bytes 105 | while (i < byteCount) { 106 | resBytes[i] = readBits(8); 107 | ++i; 108 | } 109 | return bytesToLong(resBytes); 110 | } 111 | 112 | /** 113 | * reads a double value from buffer by 64 bits 114 | */ 115 | public double readDouble() { 116 | return readDouble(64); 117 | } 118 | 119 | /** 120 | * reads a double value from buffer by specified bits count 121 | */ 122 | private double readDouble(int bitCount) { 123 | 124 | int byteCount = bitCount / 8; 125 | if (bitCount % 8 > 0) { 126 | byteCount++; 127 | } 128 | byte[] resBytes = new byte[byteCount]; 129 | 130 | int bitsToRead = (bitCount % 8 == 0) ? 8 : bitCount % 8; 131 | resBytes[0] = readBits(bitsToRead); 132 | 133 | int i = 1; 134 | //write all the bytes 135 | while (i < byteCount) { 136 | resBytes[i] = readBits(8); 137 | ++i; 138 | } 139 | long longRepresentation = BitConverter.ToInt64(resBytes, 0); 140 | return BitConverter.ToDouble(longToBytes(longRepresentation, resBytes.Length), 0); 141 | } 142 | 143 | /** 144 | * reads a signed int value from buffer by 32 bits 145 | */ 146 | public int readSignedInt() { 147 | return readSignedInt(32); 148 | } 149 | 150 | /** 151 | * reads a signed int value from buffer by specified bits count 152 | * it first reads number's sign as a bool and then reads number's absolute value as a long 153 | */ 154 | public int readSignedInt(int bitCount) { 155 | bool isNegative = readBool(); 156 | int res = readInt(bitCount); 157 | if (isNegative) { 158 | res *= -1; 159 | } 160 | return res; 161 | } 162 | 163 | /** 164 | * reads a signed long value from buffer by 64 bits 165 | */ 166 | public long readSignedLong() { 167 | return readSignedLong(64); 168 | } 169 | 170 | /** 171 | * reads a signed long value from buffer by specified bits count 172 | * it first reads number's sign as a bool and then reads number's absolute value as a long 173 | */ 174 | public long readSignedLong(int bitCount) { 175 | bool isNegative = readBool(); 176 | long res = readLong(bitCount); 177 | if (isNegative) { 178 | res *= -1; 179 | } 180 | return res; 181 | } 182 | 183 | /** 184 | * reads a bool value from buffer by 1 bit 185 | */ 186 | public bool readBool() { 187 | return (readBits(1) == 1); 188 | } 189 | 190 | /** 191 | * reads a byte value from buffer by 8 bits 192 | */ 193 | public byte readByte() { 194 | return readBits(8); 195 | } 196 | 197 | /** 198 | * reads a string value from buffer based on string's length 199 | * it first reads string's length as an int value, then reads string's bytes from buffer 200 | */ 201 | public string readString() { 202 | int bytesCount = readInt(32); 203 | byte[] resArr = new byte[bytesCount]; 204 | for (int i = 0; i < bytesCount; i++) { 205 | resArr[i] = readBits(8); 206 | } 207 | return Encoding.UTF8.GetString(resArr); 208 | } 209 | 210 | private byte readBits(int bitCount) { 211 | 212 | //// example for _head=9, bitCount=3, currentByte{01110101}: 213 | // _head{00001001} >> 3 = {00000001} = 1 214 | int byteOffset = _head >> 3; // or _head/8 215 | 216 | //// example 217 | // _head{00001001} & {00000111} = {00000001} = 1 218 | int bitOffsetInCurrentByte = _head & 0x7; 219 | 220 | //// example 221 | // currentByte{01110101} << 1 = resultByte{11101010} 222 | byte resultByte = (byte)(_buffer[byteOffset] << bitOffsetInCurrentByte); 223 | 224 | //// example 225 | // 8 - 1 = 7 226 | int numberOfBitsAreReadableInCurrentByte = 8 - bitOffsetInCurrentByte; 227 | 228 | //// example 229 | // if condition fails 230 | if (numberOfBitsAreReadableInCurrentByte < bitCount) { 231 | //need to read from next byte 232 | int readablePortionOfNextByte = (_buffer[byteOffset + 1] >> numberOfBitsAreReadableInCurrentByte); 233 | resultByte = (byte)(resultByte | readablePortionOfNextByte); 234 | } 235 | 236 | //// example 237 | // resultByte{11101010} >> 5 = {000111} 238 | resultByte = (byte)(resultByte >> (8 - bitCount)); 239 | //// example 240 | // _head = 12 241 | _head += bitCount; 242 | //// example 243 | // return {000111} 244 | return resultByte; 245 | } 246 | 247 | private byte[] intToBytes(int num, int byteCount) { 248 | 249 | byte[] result = new byte[byteCount]; 250 | for (int i = 0; byteCount > 0; i++, byteCount--) { 251 | result[i] = (byte)(num >> ((byteCount - 1) * 8)); 252 | } 253 | return result; 254 | } 255 | 256 | private int bytesToInt(byte[] num) { 257 | 258 | int result = 0; 259 | for (int i = 0; i < num.Length; i++) { 260 | result |= (num[i] << ((num.Length - i - 1) * 8)); 261 | } 262 | return result; 263 | } 264 | 265 | private long bytesToLong(byte[] num) { 266 | 267 | long result = 0; 268 | for (int i = 0; i < num.Length; i++) { 269 | result |= (((long)num[i]) << ((num.Length - i - 1) * 8)); 270 | } 271 | return result; 272 | } 273 | 274 | private byte[] longToBytes(long num, int byteCount) { 275 | 276 | byte[] result = new byte[byteCount]; 277 | 278 | for (int i = 0; byteCount > 0; i++, byteCount--) { 279 | result[i] = (byte)(num >> ((byteCount - 1) * 8)); 280 | } 281 | return result; 282 | } 283 | 284 | /** 285 | * returns buffer as a byte array 286 | */ 287 | public byte[] getBuffer() { 288 | return _buffer; 289 | } 290 | 291 | /** 292 | * returns buffer length in bits 293 | */ 294 | public int getRemainingBytes() { 295 | return _capacity - (_head/8) + 1; 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /Assets/Scripts/UdpSocket_BitStream_Utilities/BitwiseMemoryOutputStream.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Text; 3 | 4 | /** 5 | * This class is a bit-wise memory output stream implementation 6 | */ 7 | public class BitwiseMemoryOutputStream { 8 | 9 | private int _capacity; 10 | private int _head; 11 | private byte[] _buffer; 12 | 13 | private static readonly int INITIAL_BUFFER_SIZE_IN_BYTE = 600; 14 | 15 | public BitwiseMemoryOutputStream() { 16 | _head = 0; 17 | reallocateBuffer(INITIAL_BUFFER_SIZE_IN_BYTE * 8); 18 | } 19 | 20 | /** 21 | * writes an int value to buffer by 32 bits 22 | */ 23 | public void writeInt(int data) { 24 | writeInt(data, 32); 25 | } 26 | 27 | /** 28 | * writes an int value to buffer by specified bits count 29 | * example: writeInt(7, 3); 30 | */ 31 | public void writeInt(int data, int bitCount) { 32 | 33 | if (bitCount < 1) { 34 | return; 35 | } 36 | if (bitCount > 32) { 37 | bitCount = 32; 38 | } 39 | int byteCount = bitCount / 8; 40 | if (bitCount % 8 > 0) { 41 | byteCount++; 42 | } 43 | byte[] srcByte = intToBytes(data, byteCount); 44 | int bitsToWrite = (bitCount % 8 == 0) ? 8 : bitCount % 8; 45 | writeBits(srcByte[0], bitsToWrite); 46 | 47 | int i = 1; 48 | //write all the bytes 49 | while (i < byteCount) { 50 | writeBits(srcByte[i], 8); 51 | ++i; 52 | } 53 | } 54 | 55 | /** 56 | * writes a long value to buffer by 64 bits 57 | */ 58 | public void writeLong(long data) { 59 | writeLong(data, 64); 60 | } 61 | 62 | /** 63 | * writes a long value to buffer by specified bits count 64 | * example: writeLong(7L, 3); 65 | */ 66 | public void writeLong(long data, int bitCount) { 67 | 68 | if (bitCount < 1) { 69 | return; 70 | } 71 | if (bitCount > 64) { 72 | bitCount = 64; 73 | } 74 | int byteCount = bitCount / 8; 75 | if (bitCount % 8 > 0) { 76 | byteCount++; 77 | } 78 | byte[] srcByte = longToBytes(data, byteCount); 79 | int bitsToWrite = (bitCount % 8 == 0) ? 8 : bitCount % 8; 80 | writeBits(srcByte[0], bitsToWrite); 81 | 82 | int i = 1; 83 | //write all the bytes 84 | while (i < byteCount) { 85 | writeBits(srcByte[i], 8); 86 | ++i; 87 | } 88 | } 89 | 90 | /** 91 | * writes a double value to buffer by 64 bits 92 | */ 93 | public void writeDouble(double data) { 94 | writeDouble(data, 64); 95 | } 96 | 97 | /** 98 | * as float/double representation in binary is different than int/long 99 | * its better to write float/double by all bits. 100 | */ 101 | private void writeDouble(double data, int bitCount) { 102 | 103 | if (bitCount < 1) { 104 | return; 105 | } 106 | if (bitCount > 64) { 107 | bitCount = 64; 108 | } 109 | int byteCount = bitCount / 8; 110 | if (bitCount % 8 > 0) { 111 | byteCount++; 112 | } 113 | 114 | byte[] srcByte = longToBytes(BitConverter.DoubleToInt64Bits(data), byteCount); 115 | int bitsToWrite = (bitCount % 8 == 0) ? 8 : bitCount % 8; 116 | writeBits(srcByte[0], bitsToWrite); 117 | 118 | int i = 1; 119 | //write all the bytes 120 | while (i < byteCount) { 121 | writeBits(srcByte[i], 8); 122 | ++i; 123 | } 124 | } 125 | 126 | /** 127 | * writes a float value to buffer by 32 bits 128 | */ 129 | public void writeFloat(float data) { 130 | writeFloat(data, 32); 131 | } 132 | 133 | /** 134 | * as float/double representation in binary is different than int/long 135 | * its better to write float/double by all bits. 136 | */ 137 | private void writeFloat(float data, int bitCount) { 138 | 139 | if (bitCount < 1) { 140 | return; 141 | } 142 | if (bitCount > 32) { 143 | bitCount = 32; 144 | } 145 | int byteCount = bitCount / 8; 146 | if (bitCount % 8 > 0) { 147 | byteCount++; 148 | } 149 | byte[] srcByte = intToBytes(BitConverter.ToInt32(BitConverter.GetBytes(data), 0), byteCount); 150 | int bitsToWrite = (bitCount % 8 == 0) ? 8 : bitCount % 8; 151 | writeBits(srcByte[0], bitsToWrite); 152 | 153 | int i = 1; 154 | //write all the bytes 155 | while (i < byteCount) { 156 | writeBits(srcByte[i], 8); 157 | ++i; 158 | } 159 | } 160 | 161 | /** 162 | * writes a signed int value to buffer by 32 bits 163 | */ 164 | public void writeSigned(int data) { 165 | writeSigned(data, 32); 166 | } 167 | 168 | /** 169 | * writes a signed int value to buffer by specified bits count 170 | * example: writeSigned(-7, 3); 171 | * it first writes number's sign as a bool and then writes data's absolute value as an int 172 | */ 173 | public void writeSigned(int data, int bitCount) { 174 | 175 | bool isNegative = (data < 0); 176 | if (isNegative) { 177 | data *= -1; 178 | } 179 | writeBool(isNegative); 180 | writeInt(data, bitCount); 181 | } 182 | 183 | /** 184 | * writes a signed long value to buffer by 64 bits 185 | */ 186 | public void writeSignedLong(long data) { 187 | writeSignedLong(data, 64); 188 | } 189 | 190 | /** 191 | * writes a signed long value to buffer by specified bits count 192 | * example: writeSignedLong(-7L, 3); 193 | * it first writes number's sign as a bool and then writes data's absolute value as a long 194 | */ 195 | public void writeSignedLong(long data, int bitCount) { 196 | 197 | bool isNegative = (data < 0); 198 | if (isNegative) { 199 | data *= -1; 200 | } 201 | writeBool(isNegative); 202 | writeLong(data, bitCount); 203 | } 204 | 205 | /** 206 | * writes a byte value to buffer by 8 bits 207 | */ 208 | public void writeByte(byte data) { 209 | writeBits(data, 8); 210 | } 211 | 212 | /** 213 | * writes a byte value to buffer by specified bits count 214 | * example: writeByte(0x7, 3); 215 | */ 216 | public void writeByte(byte data, int bitCount) { 217 | writeBits(data, bitCount); 218 | } 219 | 220 | /** 221 | * writes a bool value to buffer by 1 bit 222 | */ 223 | public void writeBool(bool data) { 224 | writeBits(data ? (byte)1 : (byte)0, 1); 225 | } 226 | 227 | /** 228 | * writes a string value to buffer based on string's length 229 | * it first writes string's length as an int value, then writes string's bytes to buffer 230 | */ 231 | public void writeString(string data) { 232 | byte[] bytes = Encoding.UTF8.GetBytes(data); 233 | writeInt(bytes.Length); 234 | foreach (byte element in bytes) { 235 | writeByte(element); 236 | } 237 | } 238 | 239 | private void writeBits(byte data, int bitCount) { 240 | 241 | int nextHead = _head + bitCount; 242 | // reallocate buffer if there is no enough free space for new data 243 | if (nextHead > _capacity) { 244 | reallocateBuffer(System.Math.Max(_capacity * 2, nextHead)); 245 | } 246 | 247 | //// example for _head=9: 248 | // _head{00001001} >> 3 = {00000001} = 1 249 | //// or: 250 | // 9 / 8 = 1 251 | int byteOffset = _head >> 3; // or -> _head / 8 252 | 253 | //// example for _head=9: 254 | // _head{00001001} & {00000111} = {00000001} = 1 255 | int bitOffsetInCurrentByte = _head & 0x7; 256 | 257 | //// example for _head=9: 258 | // ~({11111111} >> 1) = ~({01111111}) = {10000000} 259 | byte currentMask = (byte)~(0xff >> bitOffsetInCurrentByte); // mask of currentByte, ones are written zeros are free 260 | 261 | //// example for _head=9 and currentByte={10000000} and data=7 and bitCount=3: 262 | // ((currentByte{10000000} & currentMask{10000000}) | ((data{00000111} << 8 - bitCount) >> bitOffsetInCurrentByte)) 263 | // = (({10000000}) | ((data{00000111} << 5) >> 1)) 264 | // = (({10000000}) | ({11100000} >> 1)) 265 | // = ({10000000} | {01110000}) 266 | // = {11110000} 267 | _buffer[byteOffset] = (byte)((_buffer[byteOffset] & currentMask) | ((data << 8 - bitCount) >> bitOffsetInCurrentByte)); 268 | 269 | int bitsFreeThisByte = 8 - bitOffsetInCurrentByte; 270 | 271 | // go to next byte if data is bigger than currentByte's free space 272 | if (bitsFreeThisByte < bitCount) { 273 | _buffer[byteOffset + 1] = (byte)((data << 8 - bitCount) << bitsFreeThisByte); 274 | } 275 | _head = nextHead; 276 | } 277 | 278 | private byte[] intToBytes(int num, int byteCount) { 279 | 280 | byte[] result = new byte[byteCount]; 281 | for (int i = 0; byteCount > 0; i++, byteCount--) { 282 | result[i] = (byte)(num >> ((byteCount - 1) * 8)); 283 | } 284 | return result; 285 | } 286 | 287 | private byte[] longToBytes(long num, int byteCount) { 288 | 289 | byte[] result = new byte[byteCount]; 290 | 291 | for (int i = 0; byteCount > 0; i++, byteCount--) { 292 | result[i] = (byte)(num >> ((byteCount - 1) * 8)); 293 | } 294 | return result; 295 | } 296 | 297 | private void reallocateBuffer(int newCapacity) { 298 | 299 | if (_buffer == null) { 300 | _buffer = new byte[newCapacity >> 3]; 301 | } 302 | else { 303 | byte[] tempBuffer = new byte[newCapacity >> 3]; 304 | Array.Copy(_buffer, 0, tempBuffer, 0, _buffer.Length); 305 | _buffer = null; 306 | _buffer = tempBuffer; 307 | } 308 | _capacity = newCapacity; 309 | } 310 | 311 | /** 312 | * returns buffer as a byte array 313 | */ 314 | public byte[] getBuffer() { 315 | byte[] temp = new byte[getByteLength()]; 316 | Array.Copy(_buffer, temp, getByteLength()); 317 | return temp; 318 | } 319 | 320 | /** 321 | * returns buffer length in bits 322 | */ 323 | public int getBitLength() { 324 | return _head; 325 | } 326 | 327 | /** 328 | * returns buffer length in bytes 329 | */ 330 | public int getByteLength() { 331 | return (_head + 7) >> 3; 332 | } 333 | } 334 | 335 | -------------------------------------------------------------------------------- /Assets/Scripts/UdpSocket_BitStream_Utilities/UdpSocketManager.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Threading; 8 | 9 | /** 10 | * This class wraps a C# UdpClient, creates two threads for send & receive 11 | * and provides methods for sending, receiving data and closing threads. 12 | */ 13 | public class UdpSocketManager { 14 | 15 | private readonly object _sendQueueLock = new object(); 16 | private readonly Queue _sendQueue = new Queue(); 17 | private readonly AutoResetEvent _sendQueueSignal = new AutoResetEvent(false); 18 | 19 | private readonly object _receiveQueueLock = new object(); 20 | private readonly Queue _receiveQueue = new Queue(); 21 | 22 | private Thread _receiveThread; 23 | private Thread _sendThread; 24 | 25 | private UdpClient _udpClient; 26 | private readonly object _udpClientLock = new object(); 27 | 28 | private volatile int _listenPort = 0; 29 | private volatile bool _shouldRun = true; 30 | 31 | private IPEndPoint _localIpEndPoint = null; 32 | private readonly object _localIpEndPointLock = new object(); 33 | 34 | private readonly string _serverIp; 35 | private readonly int _serverPort; 36 | private readonly int _clientPort; 37 | 38 | // this field is always used in _udpClientLock blocks, so it doesn't need a seperate lock 39 | private IAsyncResult _currentAsyncResult = null; 40 | 41 | 42 | 43 | 44 | 45 | public UdpSocketManager(string serverIp, int serverPort) { 46 | _serverIp = serverIp; 47 | _serverPort = serverPort; 48 | _clientPort = 0; 49 | } 50 | 51 | 52 | public UdpSocketManager(string serverIp, int serverPort, int clientPort) { 53 | _serverIp = serverIp; 54 | _serverPort = serverPort; 55 | _clientPort = clientPort; 56 | } 57 | 58 | 59 | /** 60 | * Resets SocketManager state to default and starts Send & Receive threads 61 | */ 62 | public IEnumerator initSocket() { 63 | 64 | // check whether send & receive threads are alive, if so close them first 65 | if ((_sendThread != null && _sendThread.IsAlive) || (_receiveThread != null && _receiveThread.IsAlive)) { 66 | closeSocketThreads(); 67 | while ((_sendThread != null && _sendThread.IsAlive) || (_receiveThread != null && _receiveThread.IsAlive)) { 68 | yield return null; 69 | // wait until udp threads closed 70 | } 71 | } 72 | 73 | // reset SocketManager state 74 | _sendQueue.Clear(); 75 | _receiveQueue.Clear(); 76 | _udpClient = null; 77 | _listenPort = 0; 78 | _shouldRun = true; 79 | 80 | // start Send & receive threads 81 | _receiveThread = new Thread( 82 | new ThreadStart(ReceiveThread)); 83 | _receiveThread.IsBackground = true; 84 | _receiveThread.Start(); 85 | 86 | _sendThread = new Thread( 87 | new ThreadStart(SendThread)); 88 | _sendThread.IsBackground = true; 89 | _sendThread.Start(); 90 | } 91 | 92 | 93 | /** 94 | * Adds an array of bytes to Queue for sending to server 95 | */ 96 | public void send(byte[] data) { 97 | 98 | lock (_sendQueueLock) { 99 | _sendQueue.Enqueue(data); 100 | } 101 | _sendQueueSignal.Set(); 102 | } 103 | 104 | 105 | /** 106 | * Reads received byte arrays from queue and return them as a list 107 | */ 108 | public IList receive() { 109 | 110 | IList res = new List(); 111 | lock (_receiveQueueLock) { 112 | while (_receiveQueue.Count > 0) { 113 | res.Add(_receiveQueue.Dequeue()); 114 | } 115 | } 116 | return res; 117 | } 118 | 119 | /** 120 | * Returns current client UDP listen port 121 | */ 122 | public int getListenPort() { 123 | return _listenPort; 124 | } 125 | 126 | /** 127 | * Returns true if listen port has bound successfully 128 | */ 129 | public bool isInitializationCompleted() { 130 | return (_listenPort > 0); 131 | } 132 | 133 | /** 134 | * Closes Send & Receive threads and ends connection 135 | */ 136 | public void closeSocketThreads() { 137 | _shouldRun = false; 138 | _sendQueueSignal.Set(); 139 | 140 | lock (_udpClientLock) { 141 | if (_udpClient != null) { 142 | _udpClient.Close(); 143 | } 144 | } 145 | } 146 | 147 | 148 | private void SendThread() { 149 | while (true) { 150 | bool isLocalIpSet; 151 | lock (_localIpEndPointLock) { 152 | isLocalIpSet = (_localIpEndPoint != null); 153 | } 154 | if (isLocalIpSet) { 155 | break; 156 | } 157 | Debug.Log("UnityUdpSockets, wait for connection establishment and getting client listen port."); 158 | Thread.Sleep(200); 159 | } 160 | lock (_localIpEndPointLock) { 161 | _listenPort = _localIpEndPoint.Port; 162 | } 163 | while (_shouldRun) { 164 | _sendQueueSignal.WaitOne(); 165 | byte[] item = null; 166 | do { 167 | item = null; 168 | lock (_sendQueueLock) { 169 | if (_sendQueue.Count > 0) 170 | item = _sendQueue.Dequeue(); 171 | } 172 | if (item != null) { 173 | lock (_udpClientLock) { 174 | _udpClient.Send(item, item.Length, _serverIp, _serverPort); 175 | } 176 | } 177 | } 178 | while (item != null); // loop until there are items to collect 179 | } 180 | } 181 | 182 | 183 | // i putted UdpClient creation in a seperate thread because im not sure if Bind() method is non-blocking 184 | // and if Bind() is Blocking, it could block Unity's thread 185 | private void ReceiveThread() { 186 | lock (_udpClientLock) { 187 | _udpClient = new UdpClient(); 188 | _udpClient.ExclusiveAddressUse = false; 189 | _udpClient.Client.SetSocketOption( 190 | SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); 191 | IPEndPoint localEp = new IPEndPoint(IPAddress.Any, _clientPort); 192 | _udpClient.Client.Bind(localEp); 193 | var s = new UdpState(localEp, _udpClient); 194 | _currentAsyncResult = _udpClient.BeginReceive(new AsyncCallback(ReceiveCallback), s); 195 | lock (_localIpEndPointLock) { 196 | _localIpEndPoint = ((IPEndPoint)_udpClient.Client.LocalEndPoint); 197 | } 198 | } 199 | } 200 | 201 | private void ReceiveCallback(IAsyncResult asyncResult) { 202 | lock (_udpClientLock) { 203 | if (asyncResult == _currentAsyncResult) { 204 | UdpClient uClient = ((UdpState)(asyncResult.AsyncState)).uClient; 205 | IPEndPoint ipEndPoint = ((UdpState)(asyncResult.AsyncState)).ipEndPoint; 206 | 207 | byte[] data = uClient.EndReceive(asyncResult, ref ipEndPoint); 208 | if (data != null && data.Length > 0) { 209 | lock (_receiveQueueLock) { 210 | _receiveQueue.Enqueue(data); 211 | } 212 | } 213 | 214 | UdpState s = new UdpState(ipEndPoint, uClient); 215 | _currentAsyncResult = _udpClient.BeginReceive(new AsyncCallback(ReceiveCallback), s); 216 | } 217 | } 218 | } 219 | 220 | private class UdpState { 221 | public UdpState(IPEndPoint ipEndPoint, UdpClient uClient) { this.ipEndPoint = ipEndPoint; this.uClient = uClient; } 222 | public IPEndPoint ipEndPoint; 223 | public UdpClient uClient; 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /Assets/Scripts/UdpSocket_BitStream_Utilities/UdpSocketManagerUsageExample.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Text; 3 | 4 | public class UdpSocketManagerUsageExample : MonoBehaviour { 5 | 6 | public static bool isActive = false; 7 | 8 | private UdpSocketManager _udpSocketManager; 9 | private bool _isListenPortLogged = false; 10 | 11 | void Start() { 12 | isActive = true; 13 | _udpSocketManager = new UdpSocketManager("127.0.0.1", 55056); 14 | StartCoroutine(_udpSocketManager.initSocket()); 15 | } 16 | 17 | // Update is called once per frame 18 | void Update() { 19 | if (!_udpSocketManager.isInitializationCompleted()) { 20 | return; 21 | } 22 | if(!_isListenPortLogged) { 23 | Debug.Log("UdpSocketManager, listen port: " + _udpSocketManager.getListenPort()); 24 | _isListenPortLogged = true; 25 | } 26 | foreach (byte[] recPacket in _udpSocketManager.receive()) { 27 | string receivedMsg = Encoding.UTF8.GetString(recPacket); 28 | if(receivedMsg == "Tik") { 29 | _udpSocketManager.send(Encoding.UTF8.GetBytes("Taak")); 30 | Debug.Log("UdpSocketManager, Tik have received and Taak have sent"); 31 | } 32 | } 33 | } 34 | 35 | private void OnDestroy() { 36 | if (_udpSocketManager != null) { 37 | _udpSocketManager.closeSocketThreads(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 M.Aghasi 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. -------------------------------------------------------------------------------- /Packages/UdpSocket_BitStream_Utilities_v1.0.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Aghasi/Unity-UdpSocket-BitStream-Utilities/1b7ef955177f8c3aba1c9c216a13e93addb74bb7/Packages/UdpSocket_BitStream_Utilities_v1.0.unitypackage -------------------------------------------------------------------------------- /Packages/UdpSocket_BitStream_Utilities_v1.1.unitypackage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/M-Aghasi/Unity-UdpSocket-BitStream-Utilities/1b7ef955177f8c3aba1c9c216a13e93addb74bb7/Packages/UdpSocket_BitStream_Utilities_v1.1.unitypackage -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Unity-UdpSocket-BitStream-Utilities 2 | This project provides source codes and a UnityPackage for using c# udp sockets and bitwise memory streams. 3 | 4 | ## Udp sockets 5 | > *For not experiencing .NET compatibility issues after exporting your project to other platforms, change Api compatibility LEVEL to .NET 2.0: File -> Build settings -> Player Settings -> Other settings -> Optimization -> Api compatibility LEVEL = .NET 2.0* 6 | 7 | Although Unity provides a networking api and protocol for socket programming and multiplayer game implementation, sometimes you need to establish your own bare sockets and communication protocol. 8 | 9 | As at the time i didn't find a ready to use, clear and multithread example of c# sockets usage in unity i started to write a simple one for udp communications. 10 | 11 | There is a *UdpSocketManager* class in this package which is responsible for udp communications and has below methods: 12 | * **UdpSocketManager(string serverIp, int serverPort)** receives serverIp & serverPort for further use 13 | * **UdpSocketManager(string serverIp, int serverPort, int clientPort)** receives serverIp, serverPort & clientPort for further use 14 | * **IEnumerator initSocket()** Resets UdpSocketManager state to default and starts Send & Receive threads 15 | * **bool isInitializationCompleted()** Returns true if listen port has bound successfully 16 | * **int getListenPort()** Returns current client UDP listen port 17 | * **void send(byte[] data)** Adds an array of bytes to Queue for sending to server 18 | * **IList receive()** Fetches received byte arrays from queue and return them as a list 19 | * **void closeSocketThreads()** Closes Send & Receive threads and ends listening 20 | 21 | Also there is a *UdpSocketManagerUsageExample* MonoBehavior script in the package which demonstrates a simple udp communication. 22 | 23 | ## Bitwise Memory streams 24 | > *this section of package uses some ideas from the [Multiplayer Game Programming](https://www.pearson.com/us/higher-education/program/Glazer-Multiplayer-Game-Programming-Architecting-Networked-Games/PGM317032.html) book.* 25 | 26 | In realtime multiplayer games you need to send something like 32 packets per second to server(and receive), also your packets shouldn't be bigger than MTU size(1500 bytes with tcp/ip headers) for not being chunked in lower tcp/ip layers. 27 | So the packet size is very important and your game usually has lots of data which are mostly numbers. 28 | In other hand you don't need to send int variables with max value of 127 with traditionally 32 bits, you only need 7 bits! 29 | Also you can convert low-range not very precise float values to ints and send them with fewer bits. 30 | 31 | For saving data with fewer bits like above, you can use Bitwise memory stream classes in this package. 32 | 33 | ### BitwiseMemoryOutputStream 34 | BitwiseMemoryOutputStream class is responsible for writing data with bit precision to stream and has below methods: 35 | * **void writeInt(int data)** writes an int value to buffer by 32 bits 36 | * **void writeInt(int data, int bitCount)** writes an int value to buffer by specified bits count 37 | * **void writeLong(long data)** writes a long value to buffer by 64 bits 38 | * **void writeLong(long data, int bitCount)** writes a long value to buffer by specified bits count 39 | * **void writeDouble(double data)** writes a double value to buffer by 64 bits 40 | * **void writeFloat(float data)** writes a float value to buffer by 32 bits 41 | * **void writeSigned(int data)** writes a signed int value to buffer by 32 bits 42 | * **void writeSigned(int data, int bitCount)** writes a signed int value to buffer by specified bits count 43 | * **void writeSignedLong(long data)** writes a signed long value to buffer by 64 bits 44 | * **void writeSignedLong(long data, int bitCount)** writes a signed long value to buffer by specified bits count 45 | * **void writeByte(byte data)** writes a byte value to buffer by 8 bits 46 | * **void writeByte(byte data, int bitCount)** writes a byte value to buffer by specified bits count 47 | * **void writeBool(bool data)** writes a bool value to buffer by 1 bit 48 | * **void writeString(string data)** writes a string value to buffer based on string's length 49 | * **byte[] getBuffer()** returns buffer as a byte array 50 | * **int getBitLength()** returns buffer length in bits 51 | * **int getByteLength()** returns buffer length in bytes 52 | 53 | ### BitwiseMemoryInputStream 54 | BitwiseMemoryInputStream class is responsible for reading data with bit precision from stream and has below methods: 55 | * **int readInt()** reads an int value from buffer by 32 bits 56 | * **int readInt(int bitCount)** reads an int value from buffer by specified bits count 57 | * **long readLong()** reads a long value from buffer by 64 bits 58 | * **long readLong(int bitCount)** reads a long value from buffer by specified bits count 59 | * **double readDouble()** reads a double value from buffer by 64 bits 60 | * **float readFloat()** reads a float value from buffer by 32 bits 61 | * **int readSignedInt()** reads a signed int value from buffer by 32 bits 62 | * **int readSignedInt(int bitCount)** reads a signed int value from buffer by specified bits count 63 | * **long readSignedLong()** reads a signed long value from buffer by 64 bits 64 | * **long readSignedLong(int bitCount)** reads a signed long value from buffer by specified bits count 65 | * **byte readByte()** reads a byte value from buffer by 8 bits 66 | * **bool readBool()** reads a bool value from buffer by 1 bit 67 | * **string readString()** reads a string value from buffer based on string's length 68 | * **byte[] getBuffer()** returns buffer as a byte array 69 | * **int getRemainingBytes()** returns count of remaining buffer bytes to read 70 | 71 | #### Notices 72 | * You must read data from stream in same order you wrote. 73 | * As float/double representation in binary is different than int/long, i didn't provide methods for writing/reading them with fewer bits. 74 | * As writeSigned methods first write number's sign as a bool and then write data's absolute value as an int or long and writeString method first writes string length as an int and then writes string's bytes, for reading this types you need to use same Stream classes or write a same implementation in other languages. 75 | 76 | > *I have wrote a Java implementation of this bitwise memory stream classes in a different repository which you can use in your Java server: [Java-memory-bit-stream](https://github.com/M-Aghasi/Java-memory-bit-stream).* 77 | 78 | ## How to use 79 | The easiest way to use this utilities is to install it as a UnityPackage, you can find the last build in [Releases](https://github.com/M-Aghasi/Unity-UdpSocket-BitStream-Utilities/releases) section. 80 | 81 | Also you can manually import scripts from [this folder](https://github.com/M-Aghasi/Unity-UdpSocket-BitStream-Utilities/tree/master/Assets/Scripts/UdpSocket_BitStream_Utilities) to your unity project. 82 | --------------------------------------------------------------------------------