在项目中接到需要开发短信服务的需求,运营商是澳门的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个,现在服务非常稳定。