Socket.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  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. // The transport already established the connection
  67. if (Manager.State == SocketManager.States.Open)
  68. OnTransportOpen(Manager.Socket, null);
  69. else
  70. {
  71. // We want to receive a message when we are connected.
  72. Manager.Socket.Off(EventNames.Connect, OnTransportOpen);
  73. Manager.Socket.On(EventNames.Connect, OnTransportOpen);
  74. if (Manager.Options.AutoConnect && Manager.State == SocketManager.States.Initial)
  75. Manager.Open();
  76. }
  77. }
  78. /// <summary>
  79. /// Disconnects this socket/namespace.
  80. /// </summary>
  81. public void Disconnect()
  82. {
  83. (this as ISocket).Disconnect(true);
  84. }
  85. /// <summary>
  86. /// Disconnects this socket/namespace.
  87. /// </summary>
  88. void ISocket.Disconnect(bool remove)
  89. {
  90. // Send a disconnect packet to the server
  91. if (IsOpen)
  92. {
  93. Packet packet = new Packet(TransportEventTypes.Message, SocketIOEventTypes.Disconnect, this.Namespace, string.Empty);
  94. (Manager as IManager).SendPacket(packet);
  95. // IsOpen must be false, because in the OnPacket preprocessing the packet would call this function again
  96. IsOpen = false;
  97. (this as ISocket).OnPacket(packet);
  98. }
  99. if (AckCallbacks != null)
  100. AckCallbacks.Clear();
  101. if (remove)
  102. {
  103. EventCallbacks.Clear();
  104. (Manager as IManager).Remove(this);
  105. }
  106. }
  107. #endregion
  108. #region Emit Implementations
  109. public Socket Emit(string eventName, params object[] args)
  110. {
  111. return Emit(eventName, null, args);
  112. }
  113. public Socket Emit(string eventName, SocketIOAckCallback callback, params object[] args)
  114. {
  115. bool blackListed = EventNames.IsBlacklisted(eventName);
  116. if (blackListed)
  117. throw new ArgumentException("Blacklisted event: " + eventName);
  118. arguments.Clear();
  119. arguments.Add(eventName);
  120. // Find and swap any binary data(byte[]) to a placeholder string.
  121. // Server side these will be swapped back.
  122. List<byte[]> attachments = null;
  123. if (args != null && args.Length > 0)
  124. {
  125. int idx = 0;
  126. for (int i = 0; i < args.Length; ++i)
  127. {
  128. byte[] binData = args[i] as byte[];
  129. if (binData != null)
  130. {
  131. if (attachments == null)
  132. attachments = new List<byte[]>();
  133. Dictionary<string, object> placeholderObj = new Dictionary<string, object>(2);
  134. placeholderObj.Add(Packet.Placeholder, true);
  135. placeholderObj.Add("num", idx++);
  136. arguments.Add(placeholderObj);
  137. attachments.Add(binData);
  138. }
  139. else
  140. arguments.Add(args[i]);
  141. }
  142. }
  143. string payload = null;
  144. try
  145. {
  146. payload = Manager.Encoder.Encode(arguments);
  147. }
  148. catch(Exception ex)
  149. {
  150. (this as ISocket).EmitError(SocketIOErrors.Internal, "Error while encoding payload: " + ex.Message + " " + ex.StackTrace);
  151. return this;
  152. }
  153. // We don't use it further in this function, so we can clear it to not hold any unwanted reference.
  154. arguments.Clear();
  155. if (payload == null)
  156. throw new ArgumentException("Encoding the arguments to JSON failed!");
  157. int id = 0;
  158. if (callback != null)
  159. {
  160. id = Manager.NextAckId;
  161. if (AckCallbacks == null)
  162. AckCallbacks = new Dictionary<int, SocketIOAckCallback>();
  163. AckCallbacks[id] = callback;
  164. }
  165. Packet packet = new Packet(TransportEventTypes.Message,
  166. attachments == null ? SocketIOEventTypes.Event : SocketIOEventTypes.BinaryEvent,
  167. this.Namespace,
  168. payload,
  169. 0,
  170. id);
  171. if (attachments != null)
  172. packet.Attachments = attachments; // This will set the AttachmentCount property too.
  173. (Manager as IManager).SendPacket(packet);
  174. return this;
  175. }
  176. public Socket EmitAck(Packet originalPacket, params object[] args)
  177. {
  178. if (originalPacket == null)
  179. throw new ArgumentNullException("originalPacket == null!");
  180. if (/*originalPacket.Id == 0 ||*/
  181. (originalPacket.SocketIOEvent != SocketIOEventTypes.Event && originalPacket.SocketIOEvent != SocketIOEventTypes.BinaryEvent))
  182. throw new ArgumentException("Wrong packet - you can't send an Ack for a packet with id == 0 and SocketIOEvent != Event or SocketIOEvent != BinaryEvent!");
  183. arguments.Clear();
  184. if (args != null && args.Length > 0)
  185. arguments.AddRange(args);
  186. string payload = null;
  187. try
  188. {
  189. payload = Manager.Encoder.Encode(arguments);
  190. }
  191. catch (Exception ex)
  192. {
  193. (this as ISocket).EmitError(SocketIOErrors.Internal, "Error while encoding payload: " + ex.Message + " " + ex.StackTrace);
  194. return this;
  195. }
  196. if (payload == null)
  197. throw new ArgumentException("Encoding the arguments to JSON failed!");
  198. Packet packet = new Packet(TransportEventTypes.Message,
  199. originalPacket.SocketIOEvent == SocketIOEventTypes.Event ? SocketIOEventTypes.Ack : SocketIOEventTypes.BinaryAck,
  200. this.Namespace,
  201. payload,
  202. 0,
  203. originalPacket.Id);
  204. (Manager as IManager).SendPacket(packet);
  205. return this;
  206. }
  207. #endregion
  208. #region On Implementations
  209. /// <summary>
  210. /// Register a callback for a given name
  211. /// </summary>
  212. public void On(string eventName, SocketIOCallback callback)
  213. {
  214. EventCallbacks.Register(eventName, callback, false, this.AutoDecodePayload);
  215. }
  216. public void On(SocketIOEventTypes type, SocketIOCallback callback)
  217. {
  218. string eventName = EventNames.GetNameFor(type);
  219. EventCallbacks.Register(eventName, callback, false, this.AutoDecodePayload);
  220. }
  221. public void On(string eventName, SocketIOCallback callback, bool autoDecodePayload)
  222. {
  223. EventCallbacks.Register(eventName, callback, false, autoDecodePayload);
  224. }
  225. public void On(SocketIOEventTypes type, SocketIOCallback callback, bool autoDecodePayload)
  226. {
  227. string eventName = EventNames.GetNameFor(type);
  228. EventCallbacks.Register(eventName, callback, false, autoDecodePayload);
  229. }
  230. #endregion
  231. #region Once Implementations
  232. public void Once(string eventName, SocketIOCallback callback)
  233. {
  234. EventCallbacks.Register(eventName, callback, true, this.AutoDecodePayload);
  235. }
  236. public void Once(SocketIOEventTypes type, SocketIOCallback callback)
  237. {
  238. EventCallbacks.Register(EventNames.GetNameFor(type), callback, true, this.AutoDecodePayload);
  239. }
  240. public void Once(string eventName, SocketIOCallback callback, bool autoDecodePayload)
  241. {
  242. EventCallbacks.Register(eventName, callback, true, autoDecodePayload);
  243. }
  244. public void Once(SocketIOEventTypes type, SocketIOCallback callback, bool autoDecodePayload)
  245. {
  246. EventCallbacks.Register(EventNames.GetNameFor(type), callback, true, autoDecodePayload);
  247. }
  248. #endregion
  249. #region Off Implementations
  250. /// <summary>
  251. /// Remove all callbacks for all events.
  252. /// </summary>
  253. public void Off()
  254. {
  255. EventCallbacks.Clear();
  256. }
  257. /// <summary>
  258. /// Removes all callbacks to the given event.
  259. /// </summary>
  260. public void Off(string eventName)
  261. {
  262. EventCallbacks.Unregister(eventName);
  263. }
  264. /// <summary>
  265. /// Removes all callbacks to the given event.
  266. /// </summary>
  267. public void Off(SocketIOEventTypes type)
  268. {
  269. Off(EventNames.GetNameFor(type));
  270. }
  271. /// <summary>
  272. /// Remove the specified callback.
  273. /// </summary>
  274. public void Off(string eventName, SocketIOCallback callback)
  275. {
  276. EventCallbacks.Unregister(eventName, callback);
  277. }
  278. /// <summary>
  279. /// Remove the specified callback.
  280. /// </summary>
  281. public void Off(SocketIOEventTypes type, SocketIOCallback callback)
  282. {
  283. EventCallbacks.Unregister(EventNames.GetNameFor(type), callback);
  284. }
  285. #endregion
  286. #region Packet Handling
  287. /// <summary>
  288. /// Last call of the OnPacket chain(Transport -> Manager -> Socket), we will dispatch the event if there is any callback
  289. /// </summary>
  290. void ISocket.OnPacket(Packet packet)
  291. {
  292. // Some preprocessing of the packet
  293. switch(packet.SocketIOEvent)
  294. {
  295. case SocketIOEventTypes.Connect:
  296. this.Id = this.Namespace != "/" ? this.Namespace + "#" + this.Manager.Handshake.Sid : this.Manager.Handshake.Sid;
  297. break;
  298. case SocketIOEventTypes.Disconnect:
  299. if (IsOpen)
  300. {
  301. IsOpen = false;
  302. EventCallbacks.Call(EventNames.GetNameFor(SocketIOEventTypes.Disconnect), packet);
  303. Disconnect();
  304. }
  305. break;
  306. // Create an Error object from the server-sent json string
  307. case SocketIOEventTypes.Error:
  308. bool success = false;
  309. object result = JSON.Json.Decode(packet.Payload, ref success);
  310. if (success)
  311. {
  312. var errDict = result as Dictionary<string, object>;
  313. Error err;
  314. if (errDict != null && errDict.ContainsKey("code"))
  315. err = new Error((SocketIOErrors)Convert.ToInt32(errDict["code"]), errDict["message"] as string);
  316. else
  317. err = new Error(SocketIOErrors.Custom, packet.Payload);
  318. EventCallbacks.Call(EventNames.GetNameFor(SocketIOEventTypes.Error), packet, err);
  319. return;
  320. }
  321. break;
  322. }
  323. // Dispatch the event to all subscriber
  324. EventCallbacks.Call(packet);
  325. // call Ack callbacks
  326. if ((packet.SocketIOEvent == SocketIOEventTypes.Ack || packet.SocketIOEvent == SocketIOEventTypes.BinaryAck) && AckCallbacks != null)
  327. {
  328. SocketIOAckCallback ackCallback = null;
  329. if (AckCallbacks.TryGetValue(packet.Id, out ackCallback) &&
  330. ackCallback != null)
  331. {
  332. try
  333. {
  334. ackCallback(this, packet, this.AutoDecodePayload ? packet.Decode(Manager.Encoder) : null);
  335. }
  336. catch (Exception ex)
  337. {
  338. HTTPManager.Logger.Exception("Socket", "ackCallback", ex);
  339. }
  340. }
  341. AckCallbacks.Remove(packet.Id);
  342. }
  343. }
  344. #endregion
  345. /// <summary>
  346. /// Emits an internal packet-less event to the user level.
  347. /// </summary>
  348. void ISocket.EmitEvent(SocketIOEventTypes type, params object[] args)
  349. {
  350. (this as ISocket).EmitEvent(EventNames.GetNameFor(type), args);
  351. }
  352. /// <summary>
  353. /// Emits an internal packet-less event to the user level.
  354. /// </summary>
  355. void ISocket.EmitEvent(string eventName, params object[] args)
  356. {
  357. if (!string.IsNullOrEmpty(eventName))
  358. EventCallbacks.Call(eventName, null, args);
  359. }
  360. void ISocket.EmitError(SocketIOErrors errCode, string msg)
  361. {
  362. (this as ISocket).EmitEvent(SocketIOEventTypes.Error, new Error(errCode, msg));
  363. }
  364. #region Private Helper Functions
  365. /// <summary>
  366. /// Called when a "connect" event received to the root namespace
  367. /// </summary>
  368. private void OnTransportOpen(Socket socket, Packet packet, params object[] args)
  369. {
  370. // If this is not the root namespace, then we send a connect message to the server
  371. if (this.Namespace != "/")
  372. (Manager as IManager).SendPacket(new Packet(TransportEventTypes.Message, SocketIOEventTypes.Connect, this.Namespace, string.Empty));
  373. // and we are now open
  374. IsOpen = true;
  375. }
  376. #endregion
  377. }
  378. }
  379. #endif