TmcClient.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Diagnostics;
  5. using System.Text;
  6. using System.Threading;
  7. using Taobao.Top.Link.Channel;
  8. using Taobao.Top.Link.Endpoints;
  9. using Taobao.Top.Link.Util;
  10. using Top.Api;
  11. using Top.Api.Util;
  12. namespace Top.Tmc
  13. {
  14. /// <summary>消息服务客户端</summary>
  15. public class TmcClient
  16. {
  17. // sign parameters
  18. private const string GROUP_NAME = "group_name";
  19. private const string SDK = "sdk";
  20. private const string INTRANET_IP = "intranet_ip";
  21. private TmcClientIdentity _id;
  22. private string _appSecret;
  23. private string _uri;
  24. private Endpoint _endpoint;
  25. private EndpointProxy _serverProxy;
  26. private volatile bool running;
  27. private int _heartbeatInterval = 45000; // 心跳频率(单位:毫秒)
  28. private int _reconnectIntervalSeconds = 15; // 重连周期(单位:秒)
  29. private int _pullRequestIntervalSeconds = 30; // 定时获取消息周期(单位:秒)
  30. private Timer _reconnectTimer;
  31. private Timer _pullRequestTimer;
  32. public event EventHandler<MessageArgs> OnMessage;
  33. /// <summary>获取或设置Log</summary>
  34. public ITopLogger Log { get; set; }
  35. private bool enableTraceLog = true;
  36. public bool EnableTraceLog
  37. {
  38. get { return this.enableTraceLog;}
  39. set { this.enableTraceLog = value; }
  40. }
  41. /// <summary>获取或设置定时发送拉取请求的周期(单位:秒)</summary>
  42. public int PullRequestIntervalSeconds
  43. {
  44. get { return this._pullRequestIntervalSeconds; }
  45. set
  46. {
  47. this._pullRequestIntervalSeconds = value;
  48. if (this._pullRequestTimer != null)
  49. this._pullRequestTimer.Change(TimeSpan.FromSeconds(this._pullRequestIntervalSeconds)
  50. , TimeSpan.FromSeconds(this._pullRequestIntervalSeconds));
  51. }
  52. }
  53. /// <summary>获取或设置自动重连间隔(单位:秒)</summary>
  54. public int ReconnectIntervalSeconds
  55. {
  56. get { return this._reconnectIntervalSeconds; }
  57. set
  58. {
  59. this._reconnectIntervalSeconds = value;
  60. if (this._reconnectTimer != null)
  61. this._reconnectTimer.Change(TimeSpan.FromSeconds(this._reconnectIntervalSeconds)
  62. , TimeSpan.FromSeconds(this._reconnectIntervalSeconds));
  63. }
  64. }
  65. /// <summary>以默认分组,初始化TMC客户端</summary>
  66. public TmcClient(string appKey, string appSecret) : this(appKey, appSecret, "default") { }
  67. /// <summary>初始化TMC客户端</summary>
  68. public TmcClient(string appKey, string appSecret, string groupName)
  69. {
  70. this._appSecret = appSecret;
  71. this._id = new TmcClientIdentity(appKey, groupName);
  72. this.PrepareEndpoint();
  73. }
  74. /// <summary>连接到 TMC Server</summary>
  75. /// <param name="uri">TMC server address, eg: ws://mc.api.taobao.com/</param>
  76. public void Connect(string uri)
  77. {
  78. this.running = true;
  79. doConnect(uri);
  80. this.StartReconnect();
  81. this.StartPullRequest();
  82. }
  83. private void doConnect(string uri)
  84. {
  85. var signHeader = new Dictionary<string, string>();
  86. var connHeader = new Dictionary<string, object>();
  87. signHeader.Add(Constants.APP_KEY, this._id.AppKey);
  88. connHeader.Add(Constants.APP_KEY, signHeader[Constants.APP_KEY]);
  89. signHeader.Add(GROUP_NAME, this._id.GroupName);
  90. connHeader.Add(GROUP_NAME, signHeader[GROUP_NAME]);
  91. signHeader.Add(Constants.TIMESTAMP, DateTime.Now.Ticks.ToString());
  92. connHeader.Add(Constants.TIMESTAMP, signHeader[Constants.TIMESTAMP]);
  93. connHeader.Add(Constants.SIGN, TopUtils.SignTopRequest(signHeader, this._appSecret, Constants.SIGN_METHOD_MD5));
  94. //extra fields
  95. connHeader.Add(SDK, Constants.SDK_VERSION);
  96. connHeader.Add(INTRANET_IP, TopUtils.GetIntranetIp());
  97. this._serverProxy = this._endpoint.GetEndpoint(new TmcServerIdentity(), uri, connHeader);
  98. this._uri = uri;
  99. this.Log.Info("connected to tmc server: {0}", uri);
  100. }
  101. /// <summary>向指定的主题发布一条与用户无关的消息。</summary>
  102. /// <param name="topic">主题名称</param>
  103. /// <param name="content">严格根据主题定义的消息内容(JSON/XML)</param>
  104. public void Send(string topic, string content)
  105. {
  106. if (string.IsNullOrEmpty(topic))
  107. throw new ArgumentNullException("topic");
  108. if (string.IsNullOrEmpty(content))
  109. throw new ArgumentNullException("content");
  110. IDictionary<string, object> msg = new Dictionary<string, object>();
  111. msg.Add(MessageFields.KIND, MessageKind.Data);
  112. msg.Add(MessageFields.DATA_TOPIC, topic);
  113. msg.Add(MessageFields.DATA_CONTENT, content);
  114. this._serverProxy.SendAndWait(msg, 2000);
  115. }
  116. /// <summary>向指定的主题发布一条与用户相关的消息。</summary>
  117. /// <param name="topic">主题名称</param>
  118. /// <param name="content">严格根据主题定义的消息内容(JSON/XML)</param>
  119. /// <param name="session">用户授权码</param>
  120. public void Send(string topic, string content, string session)
  121. {
  122. if (string.IsNullOrEmpty(topic))
  123. throw new ArgumentNullException("topic");
  124. if (string.IsNullOrEmpty(content))
  125. throw new ArgumentNullException("content");
  126. if (string.IsNullOrEmpty(session))
  127. throw new ArgumentNullException("session");
  128. IDictionary<string, object> msg = new Dictionary<string, object>();
  129. msg.Add(MessageFields.KIND, MessageKind.Data);
  130. msg.Add(MessageFields.DATA_TOPIC, topic);
  131. msg.Add(MessageFields.DATA_CONTENT, content);
  132. msg.Add(MessageFields.DATA_INCOMING_USER_SESSION, session);
  133. this._serverProxy.SendAndWait(msg, 2000);
  134. }
  135. /// <summary>向服务端发送消息拉取请求</summary>
  136. protected internal void PullRequest()
  137. {
  138. IDictionary<string, object> msg = new Dictionary<string, object>();
  139. msg.Add(MessageFields.KIND, MessageKind.PullRequest);
  140. this._serverProxy.Send(msg);
  141. }
  142. /// <summary>确认消息</summary>
  143. protected internal void Confirm(long id)
  144. {
  145. IDictionary<string, object> msg = new Dictionary<string, object>();
  146. msg.Add(MessageFields.KIND, MessageKind.Confirm);
  147. msg.Add(MessageFields.CONFIRM_ID, id);
  148. this._serverProxy.Send(msg);
  149. }
  150. /// <summary>确认消息</summary>
  151. protected internal void Fail(long id,string errorMsg)
  152. {
  153. IDictionary<string, object> msg = new Dictionary<string, object>();
  154. msg.Add(MessageFields.KIND, MessageKind.Failed);
  155. msg.Add(MessageFields.CONFIRM_ID, id);
  156. msg.Add(MessageFields.CONFIRM_MSG, errorMsg);
  157. this._serverProxy.Send(msg);
  158. }
  159. private void PrepareEndpoint()
  160. {
  161. this.Log = Top.Api.Log.Instance;
  162. this._endpoint = new Endpoint(Log, this._id);
  163. this._endpoint.ChannelSelector = new ClientChannelSharedSelector(Log) { HeartbeatPeriod = this._heartbeatInterval };
  164. this._endpoint.OnMessage += new EventHandler<EndpointContext>(InternalOnMessage);
  165. this._endpoint.OnAckMessage += new EventHandler<AckMessageArgs>(InternalOnAckMessage);
  166. }
  167. private void InternalOnMessage(object sender, EndpointContext context)
  168. {
  169. if (enableTraceLog)
  170. {
  171. this.Log.Info("messsage from {0}: {1}", context.MessageFrom, this.Dump(context.Message));
  172. }
  173. if (this.OnMessage == null)
  174. return;
  175. ThreadPool.QueueUserWorkItem(o =>
  176. {
  177. if (!this.running)
  178. {
  179. this.Log.Info(string.Format("message dropped as client closed: {0}", this.Dump(context.Message)));
  180. return;
  181. }
  182. Message msg = this.ParseMessage(context.Message);
  183. var args = new MessageArgs(msg, m => this.Confirm(m.Id));
  184. var sw = new Stopwatch();
  185. try
  186. {
  187. sw.Start();
  188. this.OnMessage(this, args);
  189. sw.Stop();
  190. }
  191. catch (Exception e)
  192. {
  193. args.Fail(e.Message);
  194. }
  195. if (args._isFail)
  196. {
  197. this.Log.Info("process message error: {0}", args._reason);
  198. try
  199. {
  200. this.Fail(msg.Id,args._reason.Length > 128 ? args._reason.Substring(0,128) : args._reason);
  201. }
  202. catch (Exception e)
  203. {
  204. this.Log.Warn(string.Format("try report fail reason {0} error {1}", this.Dump(context.Message), e.StackTrace));
  205. }
  206. return;
  207. }
  208. // prevent confirm attach
  209. if (sw.ElapsedMilliseconds <= 1)
  210. {
  211. Thread.Sleep(10);
  212. }
  213. if (args._isConfirmed){
  214. return;
  215. }
  216. try
  217. {
  218. this.Confirm(msg.Id);
  219. if (enableTraceLog)
  220. {
  221. this.Log.Info("confirm message topic: {0}, dataid: {1}", msg.Topic, msg.Dataid);
  222. }
  223. }
  224. catch (Exception e)
  225. {
  226. this.Log.Warn(string.Format("confirm message {0} error {1}", this.Dump(context.Message), e.StackTrace));
  227. }
  228. });
  229. }
  230. private void InternalOnAckMessage(object sender, AckMessageArgs e)
  231. {
  232. if (this.Log.IsDebugEnabled())
  233. this.Log.Debug("ack messsage from {0}: {1}", e.MessageFrom, e.Message);
  234. }
  235. private void StartReconnect()
  236. {
  237. if (this._reconnectTimer != null) return;
  238. this._reconnectTimer = new Timer(o =>
  239. {
  240. try
  241. {
  242. if (!this._serverProxy.hasValidSender())
  243. {
  244. this.Log.Info("reconning...");
  245. Thread.Sleep(1000);
  246. // 尝试再次关闭移除tunnel, 未知异常导致断开重连时,不会移除出tunnel
  247. this._serverProxy.Close(this._uri, "client closed");
  248. Thread.Sleep(1000);
  249. this.doConnect(this._uri);
  250. }
  251. }
  252. catch (Exception e)
  253. {
  254. this.Log.Warn("reconnect error", e);
  255. }
  256. }, null
  257. , TimeSpan.FromSeconds(this._reconnectIntervalSeconds)
  258. , TimeSpan.FromSeconds(this._reconnectIntervalSeconds));
  259. }
  260. private void StartPullRequest()
  261. {
  262. if (this._pullRequestTimer != null) return;
  263. this._pullRequestTimer = new Timer(o =>
  264. {
  265. try
  266. {
  267. if (this._serverProxy.hasValidSender())
  268. {
  269. this.PullRequest();
  270. }
  271. }
  272. catch (Exception e)
  273. {
  274. this.Log.Warn("pull request error", e);
  275. }
  276. }
  277. , null
  278. , TimeSpan.FromMilliseconds(500)
  279. , TimeSpan.FromSeconds(this.PullRequestIntervalSeconds));
  280. }
  281. private Message ParseMessage(IDictionary<string, object> raw)
  282. {
  283. var msg = new Message();
  284. msg.Id = this.GetValue<long>(raw, MessageFields.OUTGOING_ID);
  285. msg.Topic = this.GetValue<string>(raw, MessageFields.DATA_TOPIC);
  286. msg.PubAppKey = this.GetValue<string>(raw, MessageFields.DATA_OUTGOING_PUBLISHER);
  287. msg.PubTime = this.GetValue<DateTime>(raw, MessageFields.DATA_PUBLISH_TIME);
  288. msg.UserId = this.GetValue<long>(raw, MessageFields.DATA_OUTGOING_USER_ID);
  289. msg.UserNick = this.GetValue<string>(raw, MessageFields.DATA_OUTGOING_USER_NICK);
  290. msg.OutgoingTime = this.GetValue<DateTime>(raw, MessageFields.DATA_ATTACH_OUTGOING_TIME);
  291. msg.Dataid = this.GetValue<object>(raw, MessageFields.DATA_DATAID);
  292. if (!raw.ContainsKey(MessageFields.DATA_CONTENT))
  293. return msg;
  294. msg.Content = raw[MessageFields.DATA_CONTENT] is byte[]
  295. ? Encoding.UTF8.GetString(GZIPHelper.Unzip(raw[MessageFields.DATA_CONTENT] as byte[]))
  296. : (string)raw[MessageFields.DATA_CONTENT];
  297. return msg;
  298. }
  299. private T GetValue<T>(IDictionary<string, object> raw, string key)
  300. {
  301. if (raw.ContainsKey(key))
  302. {
  303. object value = raw[key];
  304. if (value != null)
  305. {
  306. return (T)value;
  307. }
  308. }
  309. return default(T);
  310. }
  311. private string Dump(IDictionary<string, object> raw)
  312. {
  313. var buf = new StringBuilder();
  314. foreach (var i in raw)
  315. buf.AppendFormat("{0}={1}|", i.Key, i.Value);
  316. return buf.ToString();
  317. }
  318. public void Close()
  319. {
  320. this.running = false;
  321. if (this._pullRequestTimer != null)
  322. {
  323. this._pullRequestTimer.Dispose();
  324. this._pullRequestTimer = null;
  325. }
  326. if (this._reconnectTimer != null)
  327. {
  328. this._reconnectTimer.Dispose();
  329. this._reconnectTimer = null;
  330. }
  331. this._serverProxy.Close(this._uri, "client closed");
  332. this.Log.Warn("tmc client closed");
  333. }
  334. public bool Online
  335. {
  336. get
  337. {
  338. return this._serverProxy != null && this._serverProxy.hasValidSender();
  339. }
  340. }
  341. }
  342. }