Socket.cs 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. #if !BESTHTTP_DISABLE_SOCKETIO
  2. using System;
  3. using System.Collections.Generic;
  4. namespace BestHTTP.SocketIO
  5. {
  6. using BestHTTP;
  7. using BestHTTP.SocketIO.Events;
  8. /// <summary>
  9. /// This class represents a Socket.IO namespace.
  10. /// </summary>
  11. public sealed class Socket : ISocket
  12. {
  13. #region Public Properties
  14. /// <summary>
  15. /// The SocketManager instance that created this socket.
  16. /// </summary>
  17. public SocketManager Manager { get; private set; }
  18. /// <summary>
  19. /// The namespace that this socket is bound to.
  20. /// </summary>
  21. public string Namespace { get; private set; }
  22. /// <summary>
  23. /// Unique Id of the socket.
  24. /// </summary>
  25. public string Id { get; private set; }
  26. /// <summary>
  27. /// True if the socket is connected and open to the server. False otherwise.
  28. /// </summary>
  29. public bool IsOpen { get; private set; }
  30. /// <summary>
  31. /// While this property is True, the socket will decode the Packet's Payload data using the parent SocketManager's Encoder. You must set this property before any event subscription! Its default value is True;
  32. /// </summary>
  33. public bool AutoDecodePayload { get; set; }
  34. #endregion
  35. #region Privates
  36. /// <summary>
  37. /// A table to store acknowledgment callbacks associated to the given ids.
  38. /// </summary>
  39. private Dictionary<int, SocketIOAckCallback> AckCallbacks;
  40. /// <summary>
  41. /// Tha callback table that helps this class to manage event subscription and dispatching events.
  42. /// </summary>
  43. private EventTable EventCallbacks;
  44. /// <summary>
  45. /// Cached list to spare some GC alloc.
  46. /// </summary>
  47. private List<object> arguments = new List<object>();
  48. #endregion
  49. /// <summary>
  50. /// Internal constructor.
  51. /// </summary>
  52. internal Socket(string nsp, SocketManager manager)
  53. {
  54. this.Namespace = nsp;
  55. this.Manager = manager;
  56. this.IsOpen = false;
  57. this.AutoDecodePayload = true;
  58. this.EventCallbacks = new EventTable(this);
  59. }
  60. #region Socket Handling
  61. /// <summary>
  62. /// Internal function to start opening the socket.
  63. /// </summary>
  64. void ISocket.Open()
  65. {
  66. HTTPManager.Logger.Information("Socket", string.Format("Open - Manager.State = {0}", Manager.State));
  67. // The transport already established the connection
  68. if (Manager.State == SocketManager.States.Open)
  69. OnTransportOpen();
  70. else if (Manager.Options.AutoConnect && Manager.State == SocketManager.States.Initial)
  71. Manager.Open();
  72. }
  73. /// <summary>
  74. /// Disconnects this socket/namespace.
  75. /// </summary>
  76. public void Disconnect()
  77. {
  78. (this as ISocket).Disconnect(true);
  79. }
  80. /// <summary>
  81. /// Disconnects this socket/namespace.
  82. /// </summary>
  83. void ISocket.Disconnect(bool remove)
  84. {
  85. // Send a disconnect packet to the server
  86. if (IsOpen)
  87. {
  88. Packet packet = new Packet(TransportEventTypes.Message, SocketIOEventTypes.Disconnect, this.Namespace, string.Empty);
  89. (Manager as IManager).SendPacket(packet);
  90. // IsOpen must be false, because in the OnPacket preprocessing the packet would call this function again
  91. IsOpen = false;
  92. (this as ISocket).OnPacket(packet);
  93. }
  94. if (AckCallbacks != null)
  95. AckCallbacks.Clear();
  96. if (remove)
  97. {
  98. EventCallbacks.Clear();
  99. (Manager as IManager).Remove(this);
  100. }
  101. }
  102. #endregion
  103. #region Emit Implementations
  104. public Socket Emit(string eventName, params object[] args)
  105. {
  106. return Emit(eventName, null, args);
  107. }
  108. public Socket Emit(string eventName, SocketIOAckCallback callback, params object[] args)
  109. {
  110. bool blackListed = EventNames.IsBlacklisted(eventName);
  111. if (blackListed)
  112. throw new ArgumentException("Blacklisted event: " + eventName);
  113. arguments.Clear();
  114. arguments.Add(eventName);
  115. // Find and swap any binary data(byte[]) to a placeholder string.
  116. // Server side these will be swapped back.
  117. List<byte[]> attachments = null;
  118. if (args != null && args.Length > 0)
  119. {
  120. int idx = 0;
  121. for (int i = 0; i < args.Length; ++i)
  122. {
  123. byte[] binData = args[i] as byte[];
  124. if (binData != null)
  125. {
  126. if (attachments == null)
  127. attachments = new List<byte[]>();
  128. Dictionary<string, object> placeholderObj = new Dictionary<string, object>(2);
  129. placeholderObj.Add(Packet.Placeholder, true);
  130. placeholderObj.Add("num", idx++);
  131. arguments.Add(placeholderObj);
  132. attachments.Add(binData);
  133. }
  134. else
  135. arguments.Add(args[i]);
  136. }
  137. }
  138. string payload = null;
  139. try
  140. {
  141. payload = Manager.Encoder.Encode(arguments);
  142. }
  143. catch(Exception ex)
  144. {
  145. (this as ISocket).EmitError(SocketIOErrors.Internal, "Error while encoding payload: " + ex.Message + " " + ex.StackTrace);
  146. return this;
  147. }
  148. // We don't use it further in this function, so we can clear it to not hold any unwanted reference.
  149. arguments.Clear();
  150. if (payload == null)
  151. throw new ArgumentException("Encoding the arguments to JSON failed!");
  152. int id = 0;
  153. if (callback != null)
  154. {
  155. id = Manager.NextAckId;
  156. if (AckCallbacks == null)
  157. AckCallbacks = new Dictionary<int, SocketIOAckCallback>();
  158. AckCallbacks[id] = callback;
  159. }
  160. Packet packet = new Packet(TransportEventTypes.Message,
  161. attachments == null ? SocketIOEventTypes.Event : SocketIOEventTypes.BinaryEvent,
  162. this.Namespace,
  163. payload,
  164. 0,
  165. id);
  166. if (attachments != null)
  167. packet.Attachments = attachments; // This will set the AttachmentCount property too.
  168. (Manager as IManager).SendPacket(packet);
  169. return this;
  170. }
  171. public Socket EmitAck(Packet originalPacket, params object[] args)
  172. {
  173. if (originalPacket == null)
  174. throw new ArgumentNullException("originalPacket == null!");
  175. if (/*originalPacket.Id == 0 ||*/
  176. (originalPacket.SocketIOEvent != SocketIOEventTypes.Event && originalPacket.SocketIOEvent != SocketIOEventTypes.BinaryEvent))
  177. throw new ArgumentException("Wrong packet - you can't send an Ack for a packet with id == 0 and SocketIOEvent != Event or SocketIOEvent != BinaryEvent!");
  178. arguments.Clear();
  179. if (args != null && args.Length > 0)
  180. arguments.AddRange(args);
  181. string payload = null;
  182. try
  183. {
  184. payload = Manager.Encoder.Encode(arguments);
  185. }
  186. catch (Exception ex)
  187. {
  188. (this as ISocket).EmitError(SocketIOErrors.Internal, "Error while encoding payload: " + ex.Message + " " + ex.StackTrace);
  189. return this;
  190. }
  191. if (payload == null)
  192. throw new ArgumentException("Encoding the arguments to JSON failed!");
  193. Packet packet = new Packet(TransportEventTypes.Message,
  194. originalPacket.SocketIOEvent == SocketIOEventTypes.Event ? SocketIOEventTypes.Ack : SocketIOEventTypes.BinaryAck,
  195. this.Namespace,
  196. payload,
  197. 0,
  198. originalPacket.Id);
  199. (Manager as IManager).SendPacket(packet);
  200. return this;
  201. }
  202. #endregion
  203. #region On Implementations
  204. /// <summary>
  205. /// Register a callback for a given name
  206. /// </summary>
  207. public void On(string eventName, SocketIOCallback callback)
  208. {
  209. EventCallbacks.Register(eventName, callback, false, this.AutoDecodePayload);
  210. }
  211. public void On(SocketIOEventTypes type, SocketIOCallback callback)
  212. {
  213. string eventName = EventNames.GetNameFor(type);
  214. EventCallbacks.Register(eventName, callback, false, this.AutoDecodePayload);
  215. }
  216. public void On(string eventName, SocketIOCallback callback, bool autoDecodePayload)
  217. {
  218. EventCallbacks.Register(eventName, callback, false, autoDecodePayload);
  219. }
  220. public void On(SocketIOEventTypes type, SocketIOCallback callback, bool autoDecodePayload)
  221. {
  222. string eventName = EventNames.GetNameFor(type);
  223. EventCallbacks.Register(eventName, callback, false, autoDecodePayload);
  224. }
  225. #endregion
  226. #region Once Implementations
  227. public void Once(string eventName, SocketIOCallback callback)
  228. {
  229. EventCallbacks.Register(eventName, callback, true, this.AutoDecodePayload);
  230. }
  231. public void Once(SocketIOEventTypes type, SocketIOCallback callback)
  232. {
  233. EventCallbacks.Register(EventNames.GetNameFor(type), callback, true, this.AutoDecodePayload);
  234. }
  235. public void Once(string eventName, SocketIOCallback callback, bool autoDecodePayload)
  236. {
  237. EventCallbacks.Register(eventName, callback, true, autoDecodePayload);
  238. }
  239. public void Once(SocketIOEventTypes type, SocketIOCallback callback, bool autoDecodePayload)
  240. {
  241. EventCallbacks.Register(EventNames.GetNameFor(type), callback, true, autoDecodePayload);
  242. }
  243. #endregion
  244. #region Off Implementations
  245. /// <summary>
  246. /// Remove all callbacks for all events.
  247. /// </summary>
  248. public void Off()
  249. {
  250. EventCallbacks.Clear();
  251. }
  252. /// <summary>
  253. /// Removes all callbacks to the given event.
  254. /// </summary>
  255. public void Off(string eventName)
  256. {
  257. EventCallbacks.Unregister(eventName);
  258. }
  259. /// <summary>
  260. /// Removes all callbacks to the given event.
  261. /// </summary>
  262. public void Off(SocketIOEventTypes type)
  263. {
  264. Off(EventNames.GetNameFor(type));
  265. }
  266. /// <summary>
  267. /// Remove the specified callback.
  268. /// </summary>
  269. public void Off(string eventName, SocketIOCallback callback)
  270. {
  271. EventCallbacks.Unregister(eventName, callback);
  272. }
  273. /// <summary>
  274. /// Remove the specified callback.
  275. /// </summary>
  276. public void Off(SocketIOEventTypes type, SocketIOCallback callback)
  277. {
  278. EventCallbacks.Unregister(EventNames.GetNameFor(type), callback);
  279. }
  280. #endregion
  281. #region Packet Handling
  282. /// <summary>
  283. /// Last call of the OnPacket chain(Transport -> Manager -> Socket), we will dispatch the event if there is any callback
  284. /// </summary>
  285. void ISocket.OnPacket(Packet packet)
  286. {
  287. // Some preprocessing of the packet
  288. switch(packet.SocketIOEvent)
  289. {
  290. case SocketIOEventTypes.Connect:
  291. if (this.Manager.Options.ServerVersion != SupportedSocketIOVersions.v3)
  292. {
  293. this.Id = this.Namespace != "/" ? this.Namespace + "#" + this.Manager.Handshake.Sid : this.Manager.Handshake.Sid;
  294. }
  295. else
  296. {
  297. var data = JSON.Json.Decode(packet.Payload) as Dictionary<string, object>;
  298. this.Id = data["sid"].ToString();
  299. }
  300. this.IsOpen = true;
  301. break;
  302. case SocketIOEventTypes.Disconnect:
  303. if (IsOpen)
  304. {
  305. IsOpen = false;
  306. EventCallbacks.Call(EventNames.GetNameFor(SocketIOEventTypes.Disconnect), packet);
  307. Disconnect();
  308. }
  309. break;
  310. // Create an Error object from the server-sent json string
  311. case SocketIOEventTypes.Error:
  312. bool success = false;
  313. object result = JSON.Json.Decode(packet.Payload, ref success);
  314. if (success)
  315. {
  316. var errDict = result as Dictionary<string, object>;
  317. Error err = null;
  318. if (errDict != null)
  319. {
  320. object tmpObject = null;
  321. string code = null;
  322. if (errDict.TryGetValue("code", out tmpObject))
  323. code = tmpObject.ToString();
  324. int errorCode;
  325. if (code != null && int.TryParse(code, out errorCode) && errorCode >= 0 && errorCode <= 7)
  326. {
  327. errDict.TryGetValue("message", out tmpObject);
  328. err = new Error((SocketIOErrors)errorCode, tmpObject != null ? tmpObject.ToString() : string.Empty);
  329. }
  330. }
  331. if (err == null)
  332. err = new Error(SocketIOErrors.Custom, packet.Payload);
  333. EventCallbacks.Call(EventNames.GetNameFor(SocketIOEventTypes.Error), packet, err);
  334. return;
  335. }
  336. break;
  337. }
  338. // Dispatch the event to all subscriber
  339. EventCallbacks.Call(packet);
  340. // call Ack callbacks
  341. if ((packet.SocketIOEvent == SocketIOEventTypes.Ack || packet.SocketIOEvent == SocketIOEventTypes.BinaryAck) && AckCallbacks != null)
  342. {
  343. SocketIOAckCallback ackCallback = null;
  344. if (AckCallbacks.TryGetValue(packet.Id, out ackCallback) &&
  345. ackCallback != null)
  346. {
  347. try
  348. {
  349. ackCallback(this, packet, this.AutoDecodePayload ? packet.Decode(Manager.Encoder) : null);
  350. }
  351. catch (Exception ex)
  352. {
  353. HTTPManager.Logger.Exception("Socket", "ackCallback", ex);
  354. }
  355. }
  356. AckCallbacks.Remove(packet.Id);
  357. }
  358. }
  359. #endregion
  360. /// <summary>
  361. /// Emits an internal packet-less event to the user level.
  362. /// </summary>
  363. void ISocket.EmitEvent(SocketIOEventTypes type, params object[] args)
  364. {
  365. (this as ISocket).EmitEvent(EventNames.GetNameFor(type), args);
  366. }
  367. /// <summary>
  368. /// Emits an internal packet-less event to the user level.
  369. /// </summary>
  370. void ISocket.EmitEvent(string eventName, params object[] args)
  371. {
  372. if (!string.IsNullOrEmpty(eventName))
  373. EventCallbacks.Call(eventName, null, args);
  374. }
  375. void ISocket.EmitError(SocketIOErrors errCode, string msg)
  376. {
  377. (this as ISocket).EmitEvent(SocketIOEventTypes.Error, new Error(errCode, msg));
  378. }
  379. #region Private Helper Functions
  380. /// <summary>
  381. /// Called when the underlying transport is connected
  382. /// </summary>
  383. internal void OnTransportOpen()
  384. {
  385. HTTPManager.Logger.Information("Socket", "OnTransportOpen - IsOpen: " + this.IsOpen);
  386. if (this.IsOpen)
  387. return;
  388. if (this.Namespace != "/" || this.Manager.Options.ServerVersion == SupportedSocketIOVersions.v3)
  389. {
  390. try
  391. {
  392. string authData = null;
  393. if (this.Manager.Options.ServerVersion == SupportedSocketIOVersions.v3)
  394. authData = this.Manager.Options.Auth != null ? this.Manager.Options.Auth(this.Manager, this) : "{}";
  395. (Manager as IManager).SendPacket(new Packet(TransportEventTypes.Message, SocketIOEventTypes.Connect, this.Namespace, authData));
  396. }
  397. catch (Exception ex)
  398. {
  399. HTTPManager.Logger.Exception("Socket", "OnTransportOpen", ex);
  400. }
  401. }
  402. else
  403. this.IsOpen = true;
  404. }
  405. #endregion
  406. }
  407. }
  408. #endif