| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389 |
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Diagnostics;
- using System.Text;
- using System.Threading;
- using Taobao.Top.Link.Channel;
- using Taobao.Top.Link.Endpoints;
- using Taobao.Top.Link.Util;
- using Top.Api;
- using Top.Api.Util;
- namespace Top.Tmc
- {
- /// <summary>消息服务客户端</summary>
- public class TmcClient
- {
- // sign parameters
- private const string GROUP_NAME = "group_name";
- private const string SDK = "sdk";
- private const string INTRANET_IP = "intranet_ip";
- private TmcClientIdentity _id;
- private string _appSecret;
- private string _uri;
- private Endpoint _endpoint;
- private EndpointProxy _serverProxy;
- private volatile bool running;
- private int _heartbeatInterval = 45000; // 心跳频率(单位:毫秒)
- private int _reconnectIntervalSeconds = 15; // 重连周期(单位:秒)
- private int _pullRequestIntervalSeconds = 30; // 定时获取消息周期(单位:秒)
- private Timer _reconnectTimer;
- private Timer _pullRequestTimer;
- public event EventHandler<MessageArgs> OnMessage;
- /// <summary>获取或设置Log</summary>
- public ITopLogger Log { get; set; }
- private bool enableTraceLog = true;
- public bool EnableTraceLog
- {
- get { return this.enableTraceLog;}
- set { this.enableTraceLog = value; }
- }
- /// <summary>获取或设置定时发送拉取请求的周期(单位:秒)</summary>
- public int PullRequestIntervalSeconds
- {
- get { return this._pullRequestIntervalSeconds; }
- set
- {
- this._pullRequestIntervalSeconds = value;
- if (this._pullRequestTimer != null)
- this._pullRequestTimer.Change(TimeSpan.FromSeconds(this._pullRequestIntervalSeconds)
- , TimeSpan.FromSeconds(this._pullRequestIntervalSeconds));
- }
- }
- /// <summary>获取或设置自动重连间隔(单位:秒)</summary>
- public int ReconnectIntervalSeconds
- {
- get { return this._reconnectIntervalSeconds; }
- set
- {
- this._reconnectIntervalSeconds = value;
- if (this._reconnectTimer != null)
- this._reconnectTimer.Change(TimeSpan.FromSeconds(this._reconnectIntervalSeconds)
- , TimeSpan.FromSeconds(this._reconnectIntervalSeconds));
- }
- }
- /// <summary>以默认分组,初始化TMC客户端</summary>
- public TmcClient(string appKey, string appSecret) : this(appKey, appSecret, "default") { }
- /// <summary>初始化TMC客户端</summary>
- public TmcClient(string appKey, string appSecret, string groupName)
- {
- this._appSecret = appSecret;
- this._id = new TmcClientIdentity(appKey, groupName);
- this.PrepareEndpoint();
- }
- /// <summary>连接到 TMC Server</summary>
- /// <param name="uri">TMC server address, eg: ws://mc.api.taobao.com/</param>
- public void Connect(string uri)
- {
- this.running = true;
- doConnect(uri);
- this.StartReconnect();
- this.StartPullRequest();
- }
- private void doConnect(string uri)
- {
- var signHeader = new Dictionary<string, string>();
- var connHeader = new Dictionary<string, object>();
- signHeader.Add(Constants.APP_KEY, this._id.AppKey);
- connHeader.Add(Constants.APP_KEY, signHeader[Constants.APP_KEY]);
- signHeader.Add(GROUP_NAME, this._id.GroupName);
- connHeader.Add(GROUP_NAME, signHeader[GROUP_NAME]);
- signHeader.Add(Constants.TIMESTAMP, DateTime.Now.Ticks.ToString());
- connHeader.Add(Constants.TIMESTAMP, signHeader[Constants.TIMESTAMP]);
- connHeader.Add(Constants.SIGN, TopUtils.SignTopRequest(signHeader, this._appSecret, Constants.SIGN_METHOD_MD5));
- //extra fields
- connHeader.Add(SDK, Constants.SDK_VERSION);
- connHeader.Add(INTRANET_IP, TopUtils.GetIntranetIp());
- this._serverProxy = this._endpoint.GetEndpoint(new TmcServerIdentity(), uri, connHeader);
- this._uri = uri;
- this.Log.Info("connected to tmc server: {0}", uri);
- }
- /// <summary>向指定的主题发布一条与用户无关的消息。</summary>
- /// <param name="topic">主题名称</param>
- /// <param name="content">严格根据主题定义的消息内容(JSON/XML)</param>
- public void Send(string topic, string content)
- {
- if (string.IsNullOrEmpty(topic))
- throw new ArgumentNullException("topic");
- if (string.IsNullOrEmpty(content))
- throw new ArgumentNullException("content");
- IDictionary<string, object> msg = new Dictionary<string, object>();
- msg.Add(MessageFields.KIND, MessageKind.Data);
- msg.Add(MessageFields.DATA_TOPIC, topic);
- msg.Add(MessageFields.DATA_CONTENT, content);
- this._serverProxy.SendAndWait(msg, 2000);
- }
- /// <summary>向指定的主题发布一条与用户相关的消息。</summary>
- /// <param name="topic">主题名称</param>
- /// <param name="content">严格根据主题定义的消息内容(JSON/XML)</param>
- /// <param name="session">用户授权码</param>
- public void Send(string topic, string content, string session)
- {
- if (string.IsNullOrEmpty(topic))
- throw new ArgumentNullException("topic");
- if (string.IsNullOrEmpty(content))
- throw new ArgumentNullException("content");
- if (string.IsNullOrEmpty(session))
- throw new ArgumentNullException("session");
- IDictionary<string, object> msg = new Dictionary<string, object>();
- msg.Add(MessageFields.KIND, MessageKind.Data);
- msg.Add(MessageFields.DATA_TOPIC, topic);
- msg.Add(MessageFields.DATA_CONTENT, content);
- msg.Add(MessageFields.DATA_INCOMING_USER_SESSION, session);
- this._serverProxy.SendAndWait(msg, 2000);
- }
- /// <summary>向服务端发送消息拉取请求</summary>
- protected internal void PullRequest()
- {
- IDictionary<string, object> msg = new Dictionary<string, object>();
- msg.Add(MessageFields.KIND, MessageKind.PullRequest);
- this._serverProxy.Send(msg);
- }
- /// <summary>确认消息</summary>
- protected internal void Confirm(long id)
- {
- IDictionary<string, object> msg = new Dictionary<string, object>();
- msg.Add(MessageFields.KIND, MessageKind.Confirm);
- msg.Add(MessageFields.CONFIRM_ID, id);
- this._serverProxy.Send(msg);
- }
- /// <summary>确认消息</summary>
- protected internal void Fail(long id,string errorMsg)
- {
- IDictionary<string, object> msg = new Dictionary<string, object>();
- msg.Add(MessageFields.KIND, MessageKind.Failed);
- msg.Add(MessageFields.CONFIRM_ID, id);
- msg.Add(MessageFields.CONFIRM_MSG, errorMsg);
- this._serverProxy.Send(msg);
- }
- private void PrepareEndpoint()
- {
- this.Log = Top.Api.Log.Instance;
- this._endpoint = new Endpoint(Log, this._id);
- this._endpoint.ChannelSelector = new ClientChannelSharedSelector(Log) { HeartbeatPeriod = this._heartbeatInterval };
- this._endpoint.OnMessage += new EventHandler<EndpointContext>(InternalOnMessage);
- this._endpoint.OnAckMessage += new EventHandler<AckMessageArgs>(InternalOnAckMessage);
- }
- private void InternalOnMessage(object sender, EndpointContext context)
- {
- if (enableTraceLog)
- {
- this.Log.Info("messsage from {0}: {1}", context.MessageFrom, this.Dump(context.Message));
- }
-
- if (this.OnMessage == null)
- return;
- ThreadPool.QueueUserWorkItem(o =>
- {
- if (!this.running)
- {
- this.Log.Info(string.Format("message dropped as client closed: {0}", this.Dump(context.Message)));
- return;
- }
- Message msg = this.ParseMessage(context.Message);
- var args = new MessageArgs(msg, m => this.Confirm(m.Id));
- var sw = new Stopwatch();
- try
- {
- sw.Start();
- this.OnMessage(this, args);
- sw.Stop();
- }
- catch (Exception e)
- {
- args.Fail(e.Message);
- }
- if (args._isFail)
- {
- this.Log.Info("process message error: {0}", args._reason);
- try
- {
- this.Fail(msg.Id,args._reason.Length > 128 ? args._reason.Substring(0,128) : args._reason);
- }
- catch (Exception e)
- {
- this.Log.Warn(string.Format("try report fail reason {0} error {1}", this.Dump(context.Message), e.StackTrace));
- }
- return;
- }
- // prevent confirm attach
- if (sw.ElapsedMilliseconds <= 1)
- {
- Thread.Sleep(10);
- }
- if (args._isConfirmed){
- return;
- }
- try
- {
- this.Confirm(msg.Id);
- if (enableTraceLog)
- {
- this.Log.Info("confirm message topic: {0}, dataid: {1}", msg.Topic, msg.Dataid);
- }
- }
- catch (Exception e)
- {
- this.Log.Warn(string.Format("confirm message {0} error {1}", this.Dump(context.Message), e.StackTrace));
- }
- });
- }
- private void InternalOnAckMessage(object sender, AckMessageArgs e)
- {
- if (this.Log.IsDebugEnabled())
- this.Log.Debug("ack messsage from {0}: {1}", e.MessageFrom, e.Message);
- }
- private void StartReconnect()
- {
- if (this._reconnectTimer != null) return;
- this._reconnectTimer = new Timer(o =>
- {
- try
- {
- if (!this._serverProxy.hasValidSender())
- {
- this.Log.Info("reconning...");
- Thread.Sleep(1000);
- // 尝试再次关闭移除tunnel, 未知异常导致断开重连时,不会移除出tunnel
- this._serverProxy.Close(this._uri, "client closed");
- Thread.Sleep(1000);
- this.doConnect(this._uri);
- }
- }
- catch (Exception e)
- {
- this.Log.Warn("reconnect error", e);
- }
- }, null
- , TimeSpan.FromSeconds(this._reconnectIntervalSeconds)
- , TimeSpan.FromSeconds(this._reconnectIntervalSeconds));
- }
- private void StartPullRequest()
- {
- if (this._pullRequestTimer != null) return;
- this._pullRequestTimer = new Timer(o =>
- {
- try
- {
- if (this._serverProxy.hasValidSender())
- {
- this.PullRequest();
- }
- }
- catch (Exception e)
- {
- this.Log.Warn("pull request error", e);
- }
- }
- , null
- , TimeSpan.FromMilliseconds(500)
- , TimeSpan.FromSeconds(this.PullRequestIntervalSeconds));
- }
- private Message ParseMessage(IDictionary<string, object> raw)
- {
- var msg = new Message();
- msg.Id = this.GetValue<long>(raw, MessageFields.OUTGOING_ID);
- msg.Topic = this.GetValue<string>(raw, MessageFields.DATA_TOPIC);
- msg.PubAppKey = this.GetValue<string>(raw, MessageFields.DATA_OUTGOING_PUBLISHER);
- msg.PubTime = this.GetValue<DateTime>(raw, MessageFields.DATA_PUBLISH_TIME);
- msg.UserId = this.GetValue<long>(raw, MessageFields.DATA_OUTGOING_USER_ID);
- msg.UserNick = this.GetValue<string>(raw, MessageFields.DATA_OUTGOING_USER_NICK);
- msg.OutgoingTime = this.GetValue<DateTime>(raw, MessageFields.DATA_ATTACH_OUTGOING_TIME);
- msg.Dataid = this.GetValue<object>(raw, MessageFields.DATA_DATAID);
- if (!raw.ContainsKey(MessageFields.DATA_CONTENT))
- return msg;
- msg.Content = raw[MessageFields.DATA_CONTENT] is byte[]
- ? Encoding.UTF8.GetString(GZIPHelper.Unzip(raw[MessageFields.DATA_CONTENT] as byte[]))
- : (string)raw[MessageFields.DATA_CONTENT];
- return msg;
- }
- private T GetValue<T>(IDictionary<string, object> raw, string key)
- {
- if (raw.ContainsKey(key))
- {
- object value = raw[key];
- if (value != null)
- {
- return (T)value;
- }
- }
- return default(T);
- }
- private string Dump(IDictionary<string, object> raw)
- {
- var buf = new StringBuilder();
- foreach (var i in raw)
- buf.AppendFormat("{0}={1}|", i.Key, i.Value);
- return buf.ToString();
- }
- public void Close()
- {
- this.running = false;
- if (this._pullRequestTimer != null)
- {
- this._pullRequestTimer.Dispose();
- this._pullRequestTimer = null;
- }
- if (this._reconnectTimer != null)
- {
- this._reconnectTimer.Dispose();
- this._reconnectTimer = null;
- }
- this._serverProxy.Close(this._uri, "client closed");
- this.Log.Warn("tmc client closed");
- }
- public bool Online
- {
- get
- {
- return this._serverProxy != null && this._serverProxy.hasValidSender();
- }
- }
- }
- }
|