.Net 实现SMPP 短信服务

在项目中接到需要开发短信服务的需求,运营商是澳门的CTM,使用SMPP协议。
接到需求的时候有点不知所措,然后查找了一些资料,在NUGET上找到AberrantSMPP插件,属于免费的,但是使用中有一些坑,在这里发布出来。

1、首先需要配置好运营商提供的账号密码等信息,这里比较简单,但是需要实例化一个单例,并且内存中只能用这一个单例去调用短信服务,运营商会检测账号如果同时登录三个客户端,就会自动断开。

		protected static SMPPCommunicator client = null;
        public static readonly object clientLock = new object();
        public static readonly object bindLock = new object();
        
        public static SMPPCommunicator GetSMPPClint(SmsDto smsDto)
        {
            try
            {
                Common.Logging.LogUtils.Api2PushMsgLog("GetSMPPClint begin");
                if (client == null)
                {
                    lock (clientLock)
                    {
                        if (client == null)
                        {
                            client = new SMPPCommunicator();

                            client.Host = smsDto.host;
                            client.Port = (ushort)smsDto.port;
                            client.SystemId = smsDto.username;
                            client.Password = smsDto.password;
                            client.EnquireLinkInterval = 25;
                            client.ResponseTimeout = 3000;
                            client.ReBindInterval = 3;
                            client.SystemType = "_dev";

                            client.BindType = AberrantSMPP.Packet.Request.SmppBind.BindingType.BindAsTransceiver;
                            client.NpiType = AberrantSMPP.Packet.Pdu.NpiType.Unknown;
                            client.TonType = AberrantSMPP.Packet.Pdu.TonType.Unknown;
                            client.Version = AberrantSMPP.Packet.Pdu.SmppVersionType.Version3_4;

                            //client.OnBind += (s, e) => { Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnBind:" + e.ResponsePdu); };
                            //client.OnBindResp += (s, e) => { Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnBindResp:" + e.ResponsePdu); };

                            //client.OnAlert += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg Alert: " + e.ResponsePdu);
                            //client.OnCancelSm += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnCancelSm: " + e.ResponsePdu);
                            //client.OnCancelSmResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnCancelResp: " + e.ResponsePdu);
                            client.OnClose += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnClose: " + e.GetType());
                            //client.OnDataSm += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnDataSm: " + e.ResponsePdu);
                            //client.OnDataSmResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnDataResp: " + e.ResponsePdu);
                            //client.OnDeliverSm += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnDeliverSm: " + e.ResponsePdu);
                            //client.OnDeliverSmResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnDeliverSmResp: " + e.ResponsePdu);
                            //client.OnEnquireLink += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnEnquireLink: " + e.ResponsePdu);
                            //client.OnEnquireLinkResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnEnquireLinkResp: " + e.ResponsePdu);
                            client.OnError += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnError: " + e.ThrownException.Message);
                            //client.OnGenericNack += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnGenericNack: " + e.ResponsePdu);
                            //client.OnQuerySm += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnQuerySm: " + e.ResponsePdu);
                            //client.OnQuerySmResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnQuerySmResp: " + e.ResponsePdu);
                            //client.OnReplaceSm += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnReplaceSm: " + e.ResponsePdu);
                            //client.OnReplaceSmResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnReplaceSmResp: " + e.ResponsePdu);
                            //client.OnSubmitMulti += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnSubmitMulti: " + e.ResponsePdu);
                            //client.OnSubmitMultiResp += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnSubmitMultiResp: " + e.ResponsePdu);
                            //client.OnSubmitSm += (s, e) => Com.Niox.PC2.Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnSubmitSm: " + e.ResponsePdu);
                            //client.OnUnbind += (s, e) => Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnUnbind: " + e.ResponsePdu);
Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnUnboundResp: " + e.ResponsePdu);
                            client.Disposed += (s, e) =>
                            {  Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg Disposed: " + e.GetType());
                                client = null;
                            };
                            client.OnSubmitSmResp += new SMPPCommunicator.SubmitSmRespEventHandler((t, p) =>
                            {
                                {
                                    try
                                    {
                                        var res = p.ResponsePdu as SmppSubmitSmResp;

                                        Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg OnSubmitSm:" + p.ResponsePdu + " MsgId:" + res.MessageId.Trim() + " SequenceNumber:" + res.SequenceNumber.ToString());
 Common.Logging.LogUtils.Api2PushMsgLog(string.Format("更新消息表开始:resNumber=" + res.SequenceNumber));
                                        if (res != null && res.SequenceNumber != 0 && res.CommandStatus == AberrantSMPP.Packet.CommandStatus.ESME_ROK)
                                        {
                                            int upStateResult = 0;
                                            Common.Logging.LogUtils.Api2PushMsgLog(string.Format("更新消息表开始:resNumber=" + res.SequenceNumber));
                                            using (RegisterDao dao = new RegisterDao())
                                            {
                                                upStateResult = dao.UpdateSmsStatus("SMPP" + res.SequenceNumber.ToString(), 1);
                                            }
                                            Common.Logging.LogUtils.Api2PushMsgLog(string.Format("更新消息表结束: {0}", upStateResult));
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        Common.Logging.LogUtils.Api2ExceptionLog("OnSubmitSmResp 解析出错", ex);
                                    }
                                };
                            });
                        }
                    }
                }
                Bind();
            }
            catch (Exception ex)
            {
                Common.Logging.LogUtils.Api2ExceptionLog("GetSMPPClint 错误", ex);
            }
            return client;
        }

host、port、username、password、systemtype 都是运营商提供,实例化成功后,本地服务会用client发送短信。回调的事件这里,不需要的可以屏蔽掉,不然这个会吃很大的线程。OnSubmitSmResp 这个回调比较重要,这个事件里面表明发送成功,这里通过CommandStatus 来判断发送是否成功,注意要写好日志。

2、配置短信时,参数要与运营商提供的一致,比如SourceAddressTon、SourceAddressNpi、DestinationAddressTon、DestinationAddressNpi,这些参数需要实例化为短信的实体。

var req = new SmppSubmitSm()
                {
                    //var req = new SmppDataSm() {
                    AlertOnMsgDelivery = 0x1,
                    DataCoding = DataCoding.UCS2,
                    SourceAddress = smsDto.fromNum,
                    SourceAddressTon = Pdu.TonType.National,
                    SourceAddressNpi = Pdu.NpiType.National,
                    DestinationAddress = smsDto.toNum,
                    DestinationAddressTon = Pdu.TonType.International,
                    DestinationAddressNpi = Pdu.NpiType.ISDN,
                    ValidityPeriod = "000000235959000R", 
                    LanguageIndicator = LanguageIndicator.Unspecified,
                    MessagePayload = txt,
                    PriorityFlag = Pdu.PriorityType.Highest,
                    RegisteredDelivery =  (Pdu.RegisteredDeliveryType)0x1e,
                };
                if (!string.IsNullOrEmpty(smsSeq))
                {
                    req.SequenceNumber = uint.Parse(smsSeq);
                }
                Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg SendPdu Begin:req=" + req.ToJson());
                var sendResult = client.SendPdu(req);
                Common.Logging.LogUtils.Api2PushMsgLog("SendSMPPMsg SendPdu end:sendResult=" + sendResult);

上面的参数要与运营商所给出的一致,不然开发时会遇到短信发不出去的情况。SequenceNumber 这个比较重要,是一个序列号,在本地的数据中也要存储,用来查找CTM的短信服务。这里我是用数据库自增长的一个序列。

3、Bind是client调用的方法,初始化client时会给运营商的服务端发送绑定指令,是一个bool的返回值,表示成功与否,只有绑定成功了才能发送短信。

        public static bool Bind()
        {
           Common.Logging.LogUtils.Api2PushMsgLog("Bind begin");
            if (!client.Bound)
            {
                lock (bindLock)
                {
                    if (!client.Bound)
                    {
                        var isBind = client.Bind();
                        Common.Logging.LogUtils.Api2PushMsgLog("Bind end:" + isBind);
                        return isBind;
                    }
                }

            }
            Common.Logging.LogUtils.Api2PushMsgLog("Bind end:" + client.Bound);
            return client.Bound;
        }

4、到这里基本就可以发送短信了,日志一定要写好。之前我这个服务写好之后,部署了一段时间,总是发现短信过一段时间就发不出去,后来找到CTM,他们反馈说是有短信流量的阈值,正常是每秒5条,后来与客户一起找到他们的经理进行会议协商,开放到每秒150个,现在服务非常稳定。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值