PageRenderTime 51ms CodeModel.GetById 25ms RepoModel.GetById 1ms app.codeStats 0ms

/src/Castle.Zmq/Socket.cs

https://github.com/castleproject/castlezmq
C# | 431 lines | 317 code | 74 blank | 40 comment | 88 complexity | a079cd4d920f24bf64fa6c2a8ee9e40a MD5 | raw file
Possible License(s): LGPL-3.0
  1. namespace Castle.Zmq
  2. {
  3. using System;
  4. using System.Runtime.InteropServices;
  5. using System.Runtime.Remoting.Services;
  6. using System.Text;
  7. public class Socket : IZmqSocket, IDisposable
  8. {
  9. private readonly SocketType _type;
  10. private volatile bool _disposed;
  11. internal IntPtr SocketPtr;
  12. #if DEBUG
  13. private Context _context;
  14. #endif
  15. public const int NoTimeout = 0;
  16. public const int InfiniteTimeout = -1;
  17. /// <summary>
  18. ///
  19. /// </summary>
  20. /// <param name="context"></param>
  21. /// <param name="type"></param>
  22. /// <param name="rcvTimeoutInMilliseconds"></param>
  23. public Socket(Context context, SocketType type, int rcvTimeoutInMilliseconds = NoTimeout)
  24. {
  25. if (context == null) throw new ArgumentNullException("context");
  26. if (type < SocketType.Pair || type > SocketType.XSub) throw new ArgumentException("Invalid socket type", "socketType");
  27. if (rcvTimeoutInMilliseconds < 0) throw new ArgumentException("Invalid rcvTimeout. Must be greater than zero", "rcvTimeoutInMilliseconds");
  28. if (context._contextPtr == IntPtr.Zero) throw new ArgumentException("Specified context has been disposed", "context");
  29. this._type = type;
  30. this.SocketPtr = Native.Socket.zmq_socket(context._contextPtr, (int)type);
  31. if (this.SocketPtr == IntPtr.Zero)
  32. {
  33. Native.ThrowZmqError("Socket creation ");
  34. }
  35. #if DEBUG
  36. _context = context;
  37. context.Track(this);
  38. #endif
  39. if (rcvTimeoutInMilliseconds != NoTimeout)
  40. {
  41. // Just in case, to avoid memory leaks
  42. try
  43. {
  44. this.SetOption(SocketOpt.RCVTIMEO, rcvTimeoutInMilliseconds);
  45. }
  46. catch (Exception)
  47. {
  48. this.InternalDispose(true);
  49. throw;
  50. }
  51. }
  52. }
  53. ~Socket()
  54. {
  55. InternalDispose(false);
  56. }
  57. public SocketType SocketType
  58. {
  59. // not sure this is the best way, since they MAY change
  60. get { return this._type; }
  61. }
  62. #region Bind and Connect
  63. public void Bind(string endpoint)
  64. {
  65. if (string.IsNullOrEmpty(endpoint)) throw new ArgumentNullException("endpoint");
  66. EnsureNotDisposed();
  67. var res = Native.Socket.zmq_bind(this.SocketPtr, endpoint);
  68. if (res == Native.ErrorCode) Native.ThrowZmqError("Binding " + endpoint);
  69. }
  70. public void Unbind(string endpoint)
  71. {
  72. if (string.IsNullOrEmpty(endpoint)) throw new ArgumentNullException("endpoint");
  73. EnsureNotDisposed();
  74. var res = Native.Socket.zmq_unbind(this.SocketPtr, endpoint);
  75. if (res == Native.ErrorCode) Native.ThrowZmqError("Unbinding " + endpoint);
  76. }
  77. public void Connect(string endpoint)
  78. {
  79. if (string.IsNullOrEmpty(endpoint)) throw new ArgumentNullException("endpoint");
  80. EnsureNotDisposed();
  81. var res = Native.Socket.zmq_connect(this.SocketPtr, endpoint);
  82. if (res == Native.ErrorCode)
  83. {
  84. if (Native.LastError() == 128)
  85. {
  86. System.Diagnostics.Debugger.Break();
  87. }
  88. Native.ThrowZmqError("Connecting " + endpoint);
  89. }
  90. }
  91. public void Disconnect(string endpoint)
  92. {
  93. if (string.IsNullOrEmpty(endpoint)) throw new ArgumentNullException("endpoint");
  94. EnsureNotDisposed();
  95. var res = Native.Socket.zmq_disconnect(this.SocketPtr, endpoint);
  96. if (res == Native.ErrorCode) Native.ThrowZmqError("Disconnecting " + endpoint);
  97. }
  98. #endregion
  99. public byte[] Recv(bool noWait = false)
  100. {
  101. EnsureNotDisposed();
  102. using (var frame = new MsgFrame())
  103. {
  104. var flags = noWait ? Native.Socket.DONTWAIT : 0;
  105. again:
  106. var res = Native.MsgFrame.zmq_msg_recv(frame._msgPtr, this.SocketPtr, flags);
  107. if (res == Native.ErrorCode)
  108. {
  109. var error = Native.LastError();
  110. if (error == ZmqErrorCode.EAGAIN || error == ZmqErrorCode.EINTR) goto again;
  111. if (LogAdapter.LogEnabled)
  112. {
  113. LogAdapter.LogError("Socket", "Recv interrupted with " + error + " - " + Native.LastErrorString(error));
  114. }
  115. Native.ThrowZmqError(error, "Recv");
  116. }
  117. else
  118. {
  119. return frame.ToBytes();
  120. }
  121. }
  122. return null;
  123. }
  124. public void Send(byte[] buffer, bool hasMoreToSend = false, bool noWait = false)
  125. {
  126. if (buffer == null) throw new ArgumentNullException("buffer");
  127. EnsureNotDisposed();
  128. var flags = hasMoreToSend ? Native.Socket.SNDMORE : 0;
  129. flags += noWait ? Native.Socket.DONTWAIT : 0;
  130. var len = buffer.Length;
  131. again:
  132. var res = Native.Socket.zmq_send(this.SocketPtr, buffer, len, flags);
  133. // for now we're treating EAGAIN as error.
  134. // not sure that's OK
  135. if (res == Native.ErrorCode)
  136. {
  137. var error = Native.LastError();
  138. if (error == ZmqErrorCode.EINTR || error == ZmqErrorCode.EAGAIN) goto again;
  139. if (LogAdapter.LogEnabled)
  140. {
  141. LogAdapter.LogError("Socket", "Send interrupted with " + error + " - " + Native.LastErrorString(error));
  142. }
  143. Native.ThrowZmqError("Send");
  144. }
  145. }
  146. /// <summary>
  147. /// The SUBSCRIBE option shall establish a new message filter on a ZMQ_SUB socket.
  148. /// Newly created ZMQ_SUB sockets shall filter out all incoming messages, therefore
  149. /// you should call this option to establish an initial message filter.
  150. /// </summary>
  151. /// <param name="topic">topic name</param>
  152. public void Subscribe(string topic)
  153. {
  154. if (topic == null) throw new ArgumentNullException("topic");
  155. // should we assert socketType = ZMQ_SUB ?
  156. EnsureNotDisposed();
  157. var buf = Encoding.UTF8.GetBytes(topic);
  158. SocketExtensions.SetOption(this, SocketOpt.SUBSCRIBE, buf);
  159. }
  160. /// <summary>
  161. /// The ZMQ_UNSUBSCRIBE option shall remove an existing message filter on a ZMQ_SUB socket.
  162. /// The filter specified must match an existing filter previously established with the
  163. /// ZMQ_SUBSCRIBE option. If the socket has several instances of the same filter attached
  164. /// the ZMQ_UNSUBSCRIBE option shall remove only one instance, leaving the rest in place and functional.
  165. /// </summary>
  166. /// <param name="topic">topic name</param>
  167. public void Unsubscribe(string topic)
  168. {
  169. if (topic == null) throw new ArgumentNullException("topic");
  170. // should we assert socketType = ZMQ_SUB ?
  171. EnsureNotDisposed();
  172. var buf = Encoding.UTF8.GetBytes(topic);
  173. SocketExtensions.SetOption(this, SocketOpt.UNSUBSCRIBE, buf);
  174. }
  175. /// <summary>
  176. ///
  177. /// </summary>
  178. /// <remarks>
  179. /// Caution: All options, with the exception of
  180. /// ZMQ_SUBSCRIBE, ZMQ_UNSUBSCRIBE, ZMQ_LINGER, ZMQ_ROUTER_MANDATORY, ZMQ_PROBE_ROUTER,
  181. /// ZMQ_XPUB_VERBOSE, ZMQ_REQ_CORRELATE, and ZMQ_REQ_RELAXED,
  182. /// only take effect for subsequent socket bind/connects.
  183. /// </remarks>
  184. /// <typeparam name="T"></typeparam>
  185. /// <param name="option"></param>
  186. /// <param name="value"></param>
  187. public void SetOption<T>(int option, T value)
  188. {
  189. EnsureNotDisposed();
  190. if (Object.ReferenceEquals(value, null)) throw new ArgumentNullException("value");
  191. InternalSetOption(option, typeof(T), value, ignoreError: false);
  192. }
  193. public T GetOption<T>(int option)
  194. {
  195. EnsureNotDisposed();
  196. var retT = typeof(T);
  197. return (T)this.InternalGetOption(retT, option);
  198. }
  199. public void Dispose()
  200. {
  201. this.InternalDispose(true);
  202. }
  203. private void InternalDispose(bool isDispose)
  204. {
  205. if (_disposed) return;
  206. if (isDispose)
  207. {
  208. GC.SuppressFinalize(this);
  209. }
  210. _disposed = true;
  211. TryCancelLinger();
  212. var res = Native.Socket.zmq_close(this.SocketPtr);
  213. if (res == Native.ErrorCode)
  214. {
  215. // we cannot throw in dispose.
  216. var msg = "Error disposing socket: " + Native.LastErrorString();
  217. System.Diagnostics.Trace.TraceError(msg);
  218. System.Diagnostics.Debug.WriteLine(msg);
  219. if (LogAdapter.LogEnabled)
  220. {
  221. LogAdapter.LogError("Socket", msg);
  222. }
  223. }
  224. #if DEBUG
  225. _context.Untrack(this);
  226. #endif
  227. }
  228. private void EnsureNotDisposed()
  229. {
  230. if (_disposed) throw new ObjectDisposedException("Socket was disposed");
  231. }
  232. private void InternalSetOption(int option, Type valueType, object value, bool ignoreError)
  233. {
  234. // it would be great to assert that the option and value match their expected type
  235. // as it varies per option
  236. Action<IntPtr, object> marshaller = null;
  237. var bufferSize = 0;
  238. if (valueType == typeof (int))
  239. {
  240. bufferSize = sizeof (int);
  241. marshaller = (ptr, v) => Marshal.WriteInt32(ptr, (int) v);
  242. }
  243. else if (valueType == typeof (Int64))
  244. {
  245. bufferSize = sizeof(Int64);
  246. marshaller = (ptr, v) => Marshal.WriteInt64(ptr, (long)v);
  247. }
  248. else if (valueType == typeof(bool))
  249. {
  250. bufferSize = sizeof(int);
  251. marshaller = (ptr, v) =>
  252. {
  253. var b = (bool) v;
  254. Marshal.WriteInt32(ptr, b ? 1 : 0);
  255. };
  256. }
  257. else if (valueType == typeof(byte[]))
  258. {
  259. var b = (byte[]) value;
  260. bufferSize = b.Length;
  261. marshaller = (ptr, v) =>
  262. {
  263. var array = (byte[])v;
  264. Marshal.Copy(array, 0, ptr, array.Length);
  265. };
  266. }
  267. else if (valueType == typeof (string))
  268. {
  269. var s = (string)value;
  270. bufferSize = Encoding.UTF8.GetBytes(s).Length;
  271. marshaller = (ptr, v) =>
  272. {
  273. var vStr = (string)value;
  274. var array = Encoding.UTF8.GetBytes(vStr);
  275. Marshal.Copy(array, 0, ptr, array.Length);
  276. };
  277. }
  278. else
  279. {
  280. throw new ArgumentException("Unsupported type for value " + valueType.Name);
  281. }
  282. MarshalExt.AllocAndRun(bufPtr =>
  283. {
  284. marshaller(bufPtr, value);
  285. var res = Native.Socket.zmq_setsockopt(this.SocketPtr, option, bufPtr, bufferSize);
  286. if (!ignoreError && res == Native.ErrorCode)
  287. Native.ThrowZmqError("setting option " + option + " with value " + value);
  288. }, bufferSize);
  289. }
  290. private object InternalGetOption(Type retType, int option)
  291. {
  292. Func<IntPtr, Int64, object> unmarshaller;
  293. Int64 bufferLen;
  294. BuildUnmarshaller(retType, out unmarshaller, out bufferLen);
  295. object retValue = null;
  296. MarshalExt.AllocAndRun(sizePtr =>
  297. {
  298. Marshal.WriteInt64(sizePtr, bufferLen);
  299. MarshalExt.AllocAndRun(bufferPtr =>
  300. {
  301. var res = Native.Socket.zmq_getsockopt(this.SocketPtr, option, bufferPtr, sizePtr);
  302. if (res == Native.ErrorCode) Native.ThrowZmqError();
  303. retValue = unmarshaller(bufferPtr, bufferLen);
  304. }, (int)bufferLen);
  305. }, sizeof(Int64));
  306. return retValue;
  307. }
  308. private void BuildUnmarshaller(Type retType, out Func<IntPtr, long, object> unmarshaller, out long bufferLen)
  309. {
  310. if (retType == typeof(Int32))
  311. {
  312. unmarshaller = (ptr, len) => Marshal.ReadInt32(ptr);
  313. bufferLen = sizeof(Int32);
  314. }
  315. else if (retType == typeof(Int64))
  316. {
  317. unmarshaller = (ptr, len) => Marshal.ReadInt64(ptr);
  318. bufferLen = sizeof(Int64);
  319. }
  320. else if (retType == typeof(bool))
  321. {
  322. unmarshaller = (ptr, len) => Marshal.ReadInt32(ptr) != 0;
  323. bufferLen = sizeof(Int32);
  324. }
  325. else if (retType == typeof(byte[]))
  326. {
  327. unmarshaller = (ptr, len) =>
  328. {
  329. var buffer = new byte[len];
  330. if (len > 0)
  331. Marshal.Copy(ptr, buffer, 0, (int)len);
  332. return buffer;
  333. };
  334. bufferLen = 255L;
  335. }
  336. else if (retType == typeof(string))
  337. {
  338. unmarshaller = (ptr, len) =>
  339. {
  340. var buffer = new byte[len];
  341. if (len > 0)
  342. Marshal.Copy(ptr, buffer, 0, (int)len);
  343. return Encoding.UTF8.GetString(buffer);
  344. };
  345. bufferLen = 255L;
  346. }
  347. else
  348. {
  349. throw new ArgumentException("Unsupported option type: " + retType.Name);
  350. }
  351. }
  352. private void TryCancelLinger()
  353. {
  354. InternalSetOption((int)SocketOpt.LINGER, typeof(int), 0, ignoreError: true);
  355. }
  356. }
  357. }