├── ExampleSocketIO.cs ├── README └── SocketIO ├── SocketIoClient.cs ├── SocketIoClientConnection.cs ├── SocketIoClientException.cs ├── SocketIoClientHandshake.cs └── SocketIoClientReceiver.cs /ExampleSocketIO.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System; 5 | //using LitJson; /* For json use litjson.sourceforge.net */ 6 | 7 | // Inherit from SocketIoClient Class 8 | public class Socket : SocketIoClient { 9 | 10 | public string host = "localhost"; 11 | public int port = 5000; 12 | 13 | void Awake() { 14 | // For Webplayer sandbox: 15 | Security.PrefetchSocketPolicy(host, port); 16 | // Setup Socket Connection 17 | SetupClient("ws://"+host+":"+port+"/socket.io/websocket"); 18 | } 19 | 20 | void Start() { 21 | // Connect client and start up read thread 22 | StartClient(); 23 | } 24 | 25 | public void Update() { 26 | // Calls "HandleMessage" if a message was in queue 27 | ProcessQueue(); 28 | } 29 | 30 | // overrides default "onMessage" behaviour: 31 | public override void HandleMessage(string msg) { 32 | print(msg); 33 | //JsonData message = JsonMapper.ToObject(msg); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Usage: 2 | - Put these files somewhere in your Unity3D project 3 | - Set host and port of socket in "Example.cs" 4 | - Append Example.cs to a GameObject in Unity editor 5 | - Connects to the socket.io socket just as any other 6 | socket.io client. Uses the WebSocket transport. 7 | 8 | Disclaimer: 9 | - Does not support secure connections. Also the handshake doesn't 10 | work yet. 11 | 12 | Roadmap: 13 | - Use events instead of interface implementation 14 | - Fix handshakes 15 | -------------------------------------------------------------------------------- /SocketIO/SocketIoClient.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | public class SocketIoClient : MonoBehaviour { 6 | 7 | [HideInInspector] 8 | public Queue queue; 9 | 10 | public SocketIoClientConnection websocket; 11 | 12 | public void ProcessQueue() { 13 | while(queue.Count>0) { 14 | HandleMessage(queue.Dequeue()); 15 | } 16 | } 17 | 18 | public virtual void HandleMessage(string msg) { 19 | print("SocketIoClient: " + msg); 20 | } 21 | 22 | public void Send(string msg) { 23 | websocket.send(msg); 24 | } 25 | 26 | public virtual void OnOpen() { 27 | print("SocketIoClient: [open]"); 28 | } 29 | 30 | public virtual void OnClose() { 31 | print("SocketIoClient: [closed]"); 32 | } 33 | 34 | public virtual void Log(string msg) { 35 | print(msg); 36 | } 37 | 38 | public virtual void OnApplicationQuit() { 39 | websocket.close(); 40 | } 41 | 42 | public void SetupClient(string url) { 43 | try { 44 | websocket = new SocketIoClientConnection(new Uri(url)); 45 | websocket.setEventHandler(this); 46 | websocket.connect(); 47 | } catch (SocketIoClientException wse) { 48 | print(wse.ToString()); 49 | } 50 | 51 | } 52 | 53 | public void StartClient() { 54 | queue = new Queue(); 55 | } 56 | } -------------------------------------------------------------------------------- /SocketIO/SocketIoClientConnection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Text; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Collections; 7 | using System.Threading; 8 | using System.Collections.Generic; 9 | 10 | public class SocketIoClientConnection { 11 | private Uri url = null; 12 | 13 | private SocketIoClient eventHandler = null; 14 | 15 | private volatile bool connected = false; 16 | 17 | 18 | public TcpClient socket = null; 19 | public Stream stream = null; 20 | private StreamReader input = null; 21 | 22 | private SocketIoClientReceiver receiver = null; 23 | private SocketIoClientHandshake handshake = null; 24 | 25 | private System.Object lockThis = new System.Object(); 26 | 27 | public SocketIoClientConnection(Uri url) : this(url, null) {} 28 | 29 | public SocketIoClientConnection(Uri url, string protocol) { 30 | this.url = url; 31 | handshake = new SocketIoClientHandshake(url, protocol); 32 | } 33 | 34 | 35 | public void setEventHandler(SocketIoClient eventHandler) { 36 | this.eventHandler = eventHandler; 37 | } 38 | 39 | 40 | public SocketIoClient getEventHandler() { 41 | return this.eventHandler; 42 | } 43 | 44 | 45 | public void connect() { 46 | try { 47 | if (connected) { 48 | throw new SocketIoClientException("already connected"); 49 | } 50 | 51 | socket = createSocket(); 52 | stream = socket.GetStream(); 53 | 54 | input = new StreamReader(stream); 55 | byte[] bytes = handshake.getHandshake(); 56 | stream.Write(bytes, 0, bytes.Length); 57 | stream.Flush(); 58 | 59 | bool handshakeComplete = false; 60 | List handshakeLines = new List(); 61 | string line; 62 | 63 | while(!handshakeComplete) { 64 | line = input.ReadLine().Trim(); 65 | if (line.Length>0) { 66 | handshakeLines.Add(line); 67 | } else { 68 | handshakeComplete = true; 69 | } 70 | } 71 | 72 | char[] response = new char[16]; 73 | input.ReadBlock(response, 0, response.Length); 74 | 75 | handshake.verifyServerStatusLine(handshakeLines[0]); 76 | 77 | /* Verifying handshake fails... */ 78 | //handshake.verifyServerResponse(response); 79 | 80 | handshakeLines.RemoveAt(0); 81 | 82 | Dictionary headers = new Dictionary(); 83 | foreach (string l in handshakeLines) { 84 | string[] keyValue = l.Split(new char[] {':'},2); 85 | headers.Add(keyValue[0].Trim(), keyValue[1].Trim()); 86 | } 87 | 88 | handshake.verifyServerHandshakeHeaders(headers); 89 | receiver = new SocketIoClientReceiver(this); 90 | connected = true; 91 | eventHandler.OnOpen(); 92 | (new Thread(receiver.run)).Start(); 93 | } catch (SocketIoClientException wse) { 94 | throw wse; 95 | } catch (IOException ioe) { 96 | throw new SocketIoClientException("error while connecting: " + ioe.StackTrace, ioe); 97 | } 98 | } 99 | 100 | public void send(string data) { 101 | lock (lockThis) { 102 | if (!connected) { 103 | throw new SocketIoClientException("error while sending text data: not connected"); 104 | } 105 | 106 | try { 107 | byte[] msg = Encoding.UTF8.GetBytes(data); 108 | byte[] length = Encoding.UTF8.GetBytes(msg.Length+""); 109 | stream.WriteByte(0x00); 110 | stream.WriteByte(0x7e); // ~ 111 | stream.WriteByte(0x6d); // m 112 | stream.WriteByte(0x7e); // ~ 113 | stream.Write(length, 0, length.Length); 114 | stream.WriteByte(0x7e); // ~ 115 | stream.WriteByte(0x6d); // m 116 | stream.WriteByte(0x7e); // ~ 117 | stream.Write(msg, 0, msg.Length); 118 | stream.WriteByte(0xff); 119 | stream.Flush(); 120 | 121 | } catch (IOException ioe) { 122 | throw new SocketIoClientException("error while sending text data", ioe); 123 | } 124 | } 125 | } 126 | 127 | 128 | public void handleReceiverError() { 129 | try { 130 | if (connected) { 131 | close(); 132 | } 133 | } catch (SocketIoClientException) { 134 | //Console.WriteLine(wse.StackTrace); 135 | } 136 | } 137 | 138 | 139 | public void close() { 140 | lock (lockThis) { 141 | if (!connected) { 142 | //Console.WriteLine("Trying to close, but not connected"); 143 | return; 144 | } 145 | 146 | if (receiver.isRunning()) { 147 | receiver.stopit(); 148 | } 149 | 150 | sendCloseHandshake(); 151 | 152 | closeStreams(); 153 | 154 | eventHandler.OnClose(); 155 | } 156 | } 157 | 158 | 159 | private void sendCloseHandshake() { 160 | lock (lockThis) { 161 | if (!connected) { 162 | throw new SocketIoClientException("error while sending close handshake: not connected"); 163 | } 164 | 165 | try { 166 | stream.WriteByte(0x80); 167 | stream.WriteByte(0x01); 168 | stream.WriteByte(0x00); 169 | stream.WriteByte(0x00); 170 | stream.Flush(); 171 | } 172 | catch (IOException ioe) { 173 | throw new SocketIoClientException("error while sending close handshake", ioe); 174 | } 175 | 176 | connected = false; 177 | } 178 | } 179 | 180 | 181 | private TcpClient createSocket() { 182 | string scheme = url.Scheme; 183 | string host = url.Host; 184 | int port = url.Port; 185 | 186 | TcpClient socket; 187 | 188 | if (scheme != null && scheme.Equals("ws")) { 189 | if (port == -1) { 190 | port = 80; 191 | } 192 | 193 | try { 194 | socket = new TcpClient(host, port); 195 | } catch (IOException ioe) { 196 | throw new SocketIoClientException("error while creating socket to " + url, ioe); 197 | } 198 | } else if (scheme != null && scheme.Equals("wss")) { 199 | throw new SocketIoClientException("Secure Sockets not implemented"); 200 | } else { 201 | throw new SocketIoClientException("unsupported protocol: " + scheme); 202 | } 203 | 204 | return socket; 205 | } 206 | 207 | 208 | private void closeStreams() { 209 | try { 210 | input.Close(); 211 | stream.Close(); 212 | socket.Close(); 213 | } catch (IOException ioe) { 214 | throw new SocketIoClientException("error while closing SocketIoClient connection: ", ioe); 215 | } 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /SocketIO/SocketIoClientException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | public class SocketIoClientException : Exception { 4 | #pragma warning disable 0414 5 | private static long serialVersionUID = 1L; 6 | #pragma warning restore 0414 7 | 8 | public SocketIoClientException(string message) : base(message) {} 9 | 10 | public SocketIoClientException(string message, Exception t) : base(message, t) {} 11 | } 12 | -------------------------------------------------------------------------------- /SocketIO/SocketIoClientHandshake.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Net; 6 | using System.Net.Sockets; 7 | using System.Text; 8 | 9 | public class SocketIoClientHandshake { 10 | private string key1 = null; 11 | private string key2 = null; 12 | private byte[] key3 = null; 13 | public byte[] expectedServerResponse = null; 14 | 15 | private Uri url = null; 16 | private string origin = null; 17 | private string protocol = null; 18 | private Random r = new Random(); 19 | 20 | public SocketIoClientHandshake(Uri url, string protocol) { 21 | this.url = url; 22 | this.protocol = null; 23 | generateKeys(); 24 | } 25 | 26 | public byte[] getHandshake() { 27 | string path = url.LocalPath; 28 | string host = url.Host; 29 | origin = "http://" + host; 30 | 31 | string handshake = "GET " + path + " HTTP/1.1\r\n" + 32 | "Upgrade: WebSocket\r\n" + 33 | "Connection: Upgrade\r\n" + 34 | "Host: " + host + "\r\n" + 35 | "Origin: " + origin + "\r\n" /*+ 36 | "Sec-WebSocket-Key2: " + key2 + "\r\n" + 37 | "Sec-WebSocket-Key1: " + key1 + "\r\n"*/; 38 | /* Not using handshake currently */ 39 | 40 | if (protocol != null) { 41 | handshake += "Sec-WebSocket-Protocol: " + protocol + "\r\n"; 42 | } 43 | 44 | handshake += "\r\n"; 45 | 46 | return Encoding.UTF8.GetBytes(handshake); 47 | } 48 | 49 | 50 | public void verifyServerResponse(byte[] response) { 51 | if (!response.Equals(expectedServerResponse)) { 52 | throw new SocketIoClientException("Handshake failed"); 53 | } 54 | } 55 | 56 | 57 | public void verifyServerStatusLine(string statusLine) { 58 | int statusCode = int.Parse(statusLine.Substring(9, 3)); 59 | if (statusCode == 407) { 60 | throw new SocketIoClientException("connection failed: proxy authentication not supported"); 61 | } 62 | else if (statusCode == 404) { 63 | throw new SocketIoClientException("connection failed: 404 not found"); 64 | } 65 | else if (statusCode != 101) { 66 | throw new SocketIoClientException("connection failed: unknown status code " + statusCode); 67 | } 68 | } 69 | 70 | 71 | public void verifyServerHandshakeHeaders(Dictionary headers) { 72 | if (!headers["Upgrade"].Equals("WebSocket")) { 73 | throw new SocketIoClientException("connection failed: missing header field in server handshake: Upgrade"); 74 | } else if (!headers["Connection"].Equals("Upgrade")) { 75 | throw new SocketIoClientException("connection failed: missing header field in server handshake: Connection"); 76 | } else if (!headers["WebSocket-Origin"].Equals(origin)) { 77 | /* should be "Sec-"WebSocket-Origin if handshakes worked */ 78 | throw new SocketIoClientException("connection failed: missing header field in server handshake: Sec-WebSocket-Origin"); 79 | } 80 | } 81 | 82 | 83 | private void generateKeys() { 84 | 85 | int spaces1 = r.Next(1,12); 86 | int spaces2 = r.Next(1,12); 87 | 88 | int max1 = int.MaxValue / spaces1; 89 | int max2 = int.MaxValue / spaces2; 90 | 91 | int number1 = r.Next(0, max1); 92 | int number2 = r.Next(0, max2); 93 | 94 | int product1 = number1 * spaces1; 95 | int product2 = number2 * spaces2; 96 | 97 | key1 = product1.ToString(); 98 | key2 = product2.ToString(); 99 | 100 | key1 = insertRandomCharacters(key1); 101 | key2 = insertRandomCharacters(key2); 102 | 103 | key1 = insertSpaces(key1, spaces1); 104 | key2 = insertSpaces(key2, spaces2); 105 | 106 | key3 = createRandomBytes(); 107 | 108 | 109 | //ByteBuffer buffer = ByteBuffer.allocate(4); 110 | MemoryStream buffer = new MemoryStream(4); 111 | //buffer.putInt(number1); 112 | using (BinaryWriter writer = new BinaryWriter(buffer)) { 113 | writer.Write(number1); 114 | } 115 | //byte[] number1Array = buffer.array(); 116 | byte[] number1Array = buffer.ToArray(); 117 | 118 | //buffer = ByteBuffer.allocate(4); 119 | buffer = new MemoryStream(4); 120 | //buffer.putInt(number2); 121 | using (BinaryWriter writer = new BinaryWriter(buffer)) { 122 | writer.Write(number2); 123 | } 124 | //byte[] number2Array = buffer.array(); 125 | byte[] number2Array = buffer.ToArray(); 126 | byte[] challenge = new byte[16]; 127 | Array.Copy(number1Array, 0, challenge, 0, 4); 128 | Array.Copy(number2Array, 0, challenge, 4, 4); 129 | Array.Copy(key3, 0, challenge, 8, 8); 130 | 131 | expectedServerResponse = md5(challenge); 132 | } 133 | 134 | 135 | private string insertRandomCharacters(string key) { 136 | int count = r.Next(1, 12); 137 | 138 | char[] randomChars = new char[count]; 139 | int randCount = 0; 140 | while (randCount < count) { 141 | int rand = (int) (((float)r.NextDouble()) * 0x7e + 0x21); 142 | if (((0x21 < rand) && (rand < 0x2f)) || ((0x3a < rand) && (rand < 0x7e))) { 143 | randomChars[randCount] = (char) rand; 144 | randCount += 1; 145 | } 146 | } 147 | 148 | for (int i = 0; i < count; i++) { 149 | int split = r.Next(0, key.Length); 150 | String part1 = key.Substring(0, split); 151 | String part2 = key.Substring(split); 152 | key = part1 + randomChars[i] + part2; 153 | } 154 | 155 | return key; 156 | } 157 | 158 | 159 | private string insertSpaces(String key, int spaces) { 160 | for (int i = 0; i < spaces; i++) { 161 | int split = r.Next(0, key.Length); 162 | String part1 = key.Substring(0, split); 163 | String part2 = key.Substring(split); 164 | key = part1 + " " + part2; 165 | } 166 | 167 | return key; 168 | } 169 | 170 | 171 | private byte[] createRandomBytes() { 172 | byte[] bytes = new byte[8]; 173 | 174 | for (int i = 0; i < 8; i++) { 175 | bytes[i] = (byte) r.Next(0, 255); 176 | } 177 | 178 | return bytes; 179 | } 180 | 181 | 182 | private byte[] md5(byte[] bytes) { 183 | System.Security.Cryptography.MD5 md = System.Security.Cryptography.MD5.Create(); 184 | return md.ComputeHash(bytes); 185 | } 186 | } -------------------------------------------------------------------------------- /SocketIO/SocketIoClientReceiver.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System; 3 | using System.Threading; 4 | using System.IO; 5 | using System.Collections; 6 | using System.Collections.Generic; 7 | 8 | 9 | public class SocketIoClientReceiver { 10 | private SocketIoClientConnection SocketIoClient = null; 11 | private SocketIoClient eventHandler = null; 12 | 13 | private volatile bool stop = false; 14 | 15 | public SocketIoClientReceiver(SocketIoClientConnection SocketIoClient) { 16 | this.SocketIoClient = SocketIoClient; 17 | this.eventHandler = SocketIoClient.getEventHandler(); 18 | } 19 | 20 | 21 | public void run() { 22 | List messageBytes = new List(); 23 | bool frameStart = false; 24 | while (!stop) { 25 | try { 26 | byte b = (byte)SocketIoClient.stream.ReadByte(); 27 | if (b == 0x00) { 28 | //"~m~XXXX~m~" 29 | b = (byte)SocketIoClient.stream.ReadByte(); // ~ 30 | b = (byte)SocketIoClient.stream.ReadByte(); // m 31 | b = (byte)SocketIoClient.stream.ReadByte(); // ~ 32 | b = (byte)SocketIoClient.stream.ReadByte(); // X 33 | List bytes = new List(); 34 | while (true) { 35 | if (b==0x7e) break; 36 | else { 37 | bytes.Add((char)b); 38 | b = (byte)SocketIoClient.stream.ReadByte(); 39 | } 40 | } 41 | b = (byte)SocketIoClient.stream.ReadByte(); // m 42 | b = (byte)SocketIoClient.stream.ReadByte(); // ~ 43 | string number = ""; 44 | foreach (char c in bytes) { 45 | number += c; 46 | } 47 | //eventHandler.Log("Lenght: "+number); 48 | frameStart = true; 49 | } else if (b == 0xff && frameStart == true) { 50 | frameStart = false; 51 | string msg = System.Text.Encoding.UTF8.GetString(messageBytes.ToArray()); 52 | eventHandler.queue.Enqueue(msg); 53 | messageBytes.Clear(); 54 | } else if (frameStart == true){ 55 | // filter out some wierd payload & respond heartbeats 56 | if (b==0x7e && messageBytes.Count==0) { 57 | b = (byte)SocketIoClient.stream.ReadByte(); 58 | if (b==(byte)'h'){ 59 | b = (byte)SocketIoClient.stream.ReadByte(); // tilde 60 | b = (byte)SocketIoClient.stream.ReadByte(); 61 | List bytes = new List(); 62 | while (true) { 63 | if (b==0xff || b==0x00 || b==0x0a || b==0x0d) break; 64 | else { 65 | bytes.Add((char)b); 66 | b = (byte)SocketIoClient.stream.ReadByte(); 67 | } 68 | } 69 | string number = ""; 70 | foreach (char c in bytes) { 71 | number += c; 72 | } 73 | SocketIoClient.send("~h~"+number); 74 | } 75 | frameStart = false; 76 | } else { 77 | messageBytes.Add(b); 78 | } 79 | } else if ((int)b == -1) { 80 | handleError(); 81 | } 82 | } catch (IOException) { 83 | handleError(); 84 | } 85 | } 86 | } 87 | 88 | 89 | public void stopit() { 90 | stop = true; 91 | } 92 | 93 | 94 | public bool isRunning() { 95 | return !stop; 96 | } 97 | 98 | 99 | private void handleError() { 100 | stopit(); 101 | SocketIoClient.handleReceiverError(); 102 | } 103 | } --------------------------------------------------------------------------------