这是2005年6月云南移动短信网关升级到3.0时写的,在SP那稳定运行了很长时间的。因为SP倒闭了,贴出来给有兴趣的朋友参考。
优点:支持多线程、滑动窗口、异步发送、全事件模式、自动识别ASCII、GBK、UCS-2
缺点:不支持长短信自动分页、不支持PROVISION接口(偶的PROVISION接口是用WEB SERVICE实现的)
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using System.Net.Sockets;
using System.Security.Cryptography;
namespace Tiray.SMS
{
/// <summary>
/// CMPP30 的摘要说明。
/// </summary>
public class CMPP30
{
#region Constants
public const Byte CMPP_VERSION_30 =0x30;
public const Byte CMPP_VERSION_21 =0x20;
public const UInt32 CMD_ERROR =0xFFFFFFFF;
public const UInt32 CMD_CONNECT =0x00000001;
public const UInt32 CMD_CONNECT_RESP =0x80000001;
public const UInt32 CMD_TERMINATE =0x00000002; // 终止连接
public const UInt32 CMD_TERMINATE_RESP =0x80000002; // 终止连接应答
public const UInt32 CMD_SUBMIT =0x00000004; // 提交短信
public const UInt32 CMD_SUBMIT_RESP =0x80000004; // 提交短信应答
public const UInt32 CMD_DELIVER =0x00000005; // 短信下发
public const UInt32 CMD_DELIVER_RESP =0x80000005; // 下发短信应答
public const UInt32 CMD_QUERY =0x00000006; // 短信状态查询
public const UInt32 CMD_QUERY_RESP =0x80000006; // 短信状态查询应答
public const UInt32 CMD_CANCEL =0x00000007; // 删除短信
public const UInt32 CMD_CANCEL_RESP =0x80000007; // 删除短信应答
public const UInt32 CMD_ACTIVE_TEST =0x00000008; // 激活测试
public const UInt32 CMD_ACTIVE_TEST_RESP =0x80000008; // 激活测试应答
#endregion
#region Protected Member Variables;
protected string m_strSPID;//SP企业代码;
protected string m_strPassword;//SP密码;
protected string m_strAddress;//短信网关地址
protected int m_iPort;//短信网关端口号;
protected static UInt32 m_iSeqID=0;//命令的序号
protected int m_iSlidingWindowSize=16;//滑动窗口大小(W)
protected int m_iActiveTestSpan=150;//ACTIVETEST的时间间隔(C,以秒为单位),标准为180
protected DateTime m_dtLastTransferTime;//最近一次网络传输时间
protected int m_iTimeOut=60;//响应超时时间(T,以秒为单位)
protected int m_iSendCount=3;//最大发送次数(N)
protected DATA_PACKAGE[] SlidingWindow=null;
protected TcpClient m_TcpClient=null;
protected NetworkStream m_NetworkStream=null;
protected Queue m_MessageQueue=null;//消息队列,用于保存所有待发送数据
protected int m_iTcpClientTimeout=5;//TcpClient接收和发送超时(以秒为单位)
protected int m_iSendSpan=10;//发送间隔,以毫秒为单位
#endregion
#region Worker Thread
protected System.Threading.Thread m_SendThread=null;
protected System.Threading.Thread m_ReceiveThread=null;
protected AutoResetEvent m_eventSendExit=new AutoResetEvent(false);
protected AutoResetEvent m_eventReceiveExit=new AutoResetEvent(false);
protected AutoResetEvent m_eventConnect=new AutoResetEvent(false);
protected AutoResetEvent m_eventDisconnect=new AutoResetEvent(false);
protected ManualResetEvent m_eventSend=new ManualResetEvent(false);
protected ManualResetEvent m_eventReceive=new ManualResetEvent(false);
protected void SendThreadProc()
{
while(true)
{
if(m_eventSendExit.WaitOne(TimeSpan.FromMilliseconds(0),false))
{
Disconnect();
break;
}
if(m_eventConnect.WaitOne(TimeSpan.FromMilliseconds(0),false))//连接
{
if(Connect())//连接上,开始发送和接收
{
m_eventSend.Set();
m_eventReceive.Set();
}
else
{
Close();
Thread.Sleep(5000);
m_eventConnect.Set();
}
}
if(m_eventDisconnect.WaitOne(TimeSpan.FromMilliseconds(0),false))//拆除连接
{
m_eventSend.Reset();
m_eventReceive.Reset();
Disconnect();
Thread.Sleep(5000);
m_eventConnect.Set();
}
if((m_eventSend.WaitOne(TimeSpan.FromMilliseconds(0),false))&&(m_NetworkStream!=null))
{
bool bOK=true;
ActiveTest();
Monitor.Enter(SlidingWindow);
for(int i=0;i<m_iSlidingWindowSize;i++)//首先用消息队列中的数据填充滑动窗口
{
if(SlidingWindow[i].Status==0)
{
DATA_PACKAGE dp=new DATA_PACKAGE();
dp.Data=null;
Monitor.Enter(m_MessageQueue);
if(m_MessageQueue.Count>0)
{
dp =(DATA_PACKAGE)m_MessageQueue.Dequeue();
SlidingWindow[i]=dp;
}
Monitor.Exit(m_MessageQueue);
}
}
for(int i=0;i<m_iSlidingWindowSize;i++)
{
DATA_PACKAGE dp =SlidingWindow[i];
if((dp.Status==1)&&(dp.SendCount==0))//第一次发送
{
bOK=Send(dp);
if((bOK)&&(dp.Command>0x80000000))//发送的是Response类的消息,不需等待Response
{
SlidingWindow[i].Status=0;//清空窗口
}
else if((bOK)&&(dp.Command<0x80000000))//发送的是需要等待Response的消息
{
SlidingWindow[i].SendTime=DateTime.Now;
SlidingWindow[i].SendCount++;
}
else
{
bOK=false;
break;//网络出错
}
}
else if((dp.Status==1)&&(dp.SendCount>0))//第N次发送
{
if(dp.SendCount>m_iSendCount-1)//已发送m_iSendCount次,丢弃数据包
{
SlidingWindow[i].Status=0;//清空窗口
if(dp.Command==CMPP30.CMD_ACTIVE_TEST)//是ActiveTest
{
bOK=false;
break;//ActiveTest出错
}
}
else
{
TimeSpan ts=DateTime.Now-dp.SendTime;
if(ts.TotalSeconds>=m_iTimeOut)//超时后未收到回应包
{
bOK=Send(dp);//再次发送
if(bOK)
{
SlidingWindow[i].SendTime=DateTime.Now;
SlidingWindow[i].SendCount++;
}
else
{
bOK=false;
break;//网络出错
}
}
}
}
}
Monitor.Exit(SlidingWindow);
if(!bOK)
{
Close();//关闭连接
Thread.Sleep(5000);//等待5秒
m_eventSend.Reset();
m_eventConnect.Set();
}
}
}
}
protected void ReceiveThreadProc()
{
while(true)
{
if(m_eventReceiveExit.WaitOne(TimeSpan.FromMilliseconds(0),false))
{
break;
}
if((m_eventReceive.WaitOne(TimeSpan.FromMilliseconds(0),false)&&(m_NetworkStream!=null)))
{
CMPP_HEAD Head=ReadHead();
if(Head.CommandID==CMPP30.CMD_SUBMIT_RESP)
{
ReadSubmitResp(Head);
}
else if(Head.CommandID==CMPP30.CMD_ACTIVE_TEST)
{
ActiveTestResponse(Head.SequenceID);
}
else if(Head.CommandID==CMPP30.CMD_ACTIVE_TEST_RESP)
{
ReadActiveTestResponse(Head);
}
else if(Head.CommandID==CMPP30.CMD_DELIVER)
{
ReadDeliver(Head);
}
else if(Head.CommandID==CMPP30.CMD_ERROR)//网络故障
{
m_eventReceive.Reset();
m_eventDisconnect.Set();
}
}
}
}
#endregion
#region Constructor
public CMPP30(string SPID,string Password,string Address,int Port)
{
m_strSPID=SPID;
m_strPassword=Password;
m_strAddress=Address;
m_iPort=Port;
SlidingWindow=new DATA_PACKAGE[m_iSlidingWindowSize];//初始化滑动窗口
for(int i=0;i<m_iSlidingWindowSize;i++)
SlidingWindow[i]=new DATA_PACKAGE();
m_MessageQueue=new Queue();
}
#endregion
#region SMSEvents
public event Tiray.SMS.SMSEventHandler SMSStateChanged;
protected void RaiseEvent(SMS_STATE State,Object Data)
{
if(null!=SMSStateChanged)
{
SMSEventArgs e=new SMSEventArgs();
e.Time=DateTime.Now;
e.State=State;
e.Data=Data;
SMSStateChanged(this,e);
}
}
#endregion
#region Protected Methods
protected UInt32 TimeStamp(DateTime dt)
{
string str=String.Format("{0:MMddhhmmss}",dt);
return Convert.ToUInt32(str);
}
protected UInt32 CreateID()
{
UInt32 id=m_iSeqID;
m_iSeqID++;
if(m_iSeqID>UInt32.MaxValue)
m_iSeqID=0;
return id;
}
protected Byte[] CreateDigest(DateTime dt)
{
int iLength=25+m_strPassword.Length;
Byte[] btContent=new Byte[iLength];
Array.Clear(btContent,0,iLength);
int iPos=0;
foreach(char ch in m_strSPID)
{
btContent[iPos]=(Byte)ch;
iPos++;
}
iPos+=9;
foreach(char ch in m_strPassword)
{
btContent[iPos]=(Byte)ch;
iPos++;
}
string strTimeStamp=String.Format("{0:MMddhhmmss}",dt);
foreach(char ch in strTimeStamp)
{
btContent[iPos]=(Byte)ch;
iPos++;
}
MD5 md5 = new MD5CryptoServiceProvider();
return md5.ComputeHash(btContent);
}
protected bool Close()
{
if(m_NetworkStream!=null)
m_NetworkStream.Close();
if(m_TcpClient!=null)
m_TcpClient.Close();
m_TcpClient=null;
m_NetworkStream=null;
return true;
}
protected bool Connect()
{
bool bOK=true;
string strError=string.Empty;
CMPP_CONNECT_RESP resp=new CMPP_CONNECT_RESP();
try
{
m_TcpClient=new TcpClient();
m_TcpClient.ReceiveTimeout=m_TcpClient.SendTimeout=m_iTcpClientTimeout*1000;
m_TcpClient.Connect(m_strAddress,m_iPort);
m_NetworkStream=m_TcpClient.GetStream();
DateTime dt=DateTime.Now;
CMPP_CONNECT conn=new CMPP_CONNECT();
conn.Head=new CMPP_HEAD();
conn.Head.CommandID=CMPP30.CMD_CONNECT;
conn.Head.SequenceID=CreateID();
conn.SourceAddress=m_strSPID;
conn.TimeStamp=TimeStamp(dt);
conn.AuthenticatorSource=CreateDigest(dt);
conn.Version=CMPP_VERSION_30;
Byte[] buffer=conn.GetBuffer();
m_NetworkStream.Write(buffer,0,(Int32)conn.Head.TotalLength);
int iSpan=0;
bool bTimeOut=false;
while(!m_NetworkStream.DataAvailable)//等待RESPONSE 5秒
{
Thread.Sleep(10);
iSpan++;
if(iSpan>500)
{
bTimeOut=true;
break;
}
}
if(!bTimeOut)
{
CMPP_HEAD Head=ReadHead();
if(Head.CommandID==CMD_CONNECT_RESP)
{
resp=ReadConnectResp(Head);
if(resp.Status==0)
bOK=true;
else
{
bOK=false;
strError="未正确接收CONNECT_RESP";
}
}
}
else
{
bOK=false;
strError="等待CONNECT_RESP超时";
}
}
catch(Exception e)
{
strError=e.Message;
bOK=false;
}
if(bOK)
RaiseEvent(SMS_STATE.SP_CONNECT,resp);
else
RaiseEvent(SMS_STATE.SP_CONNECT_ERROR,strError);
return bOK;
}
protected bool Disconnect()
{
bool bOK=true;
string strError=string.Empty;
try
{
DateTime dt=DateTime.Now;
CMPP_HEAD Head=new CMPP_HEAD();
Head.CommandID=CMPP30.CMD_TERMINATE;
Head.SequenceID=CreateID();
Head.TotalLength=(UInt32)Marshal.SizeOf(Head);
Byte[] buffer=Head.GetBuffer();
m_NetworkStream.Write(buffer,0,(Int32)Head.TotalLength);
int iSpan=0;
bool bTimeOut=false;
while(!m_NetworkStream.DataAvailable)//等待RESPONSE 5秒
{
Thread.Sleep(10);
iSpan++;
if(iSpan>500)
{
bTimeOut=true;
break;
}
}
if(!bTimeOut)
{
Head=ReadHead();
if(Head.CommandID==CMD_TERMINATE_RESP)
bOK=true;
else
{
bOK=false;
strError="未正确接收TERMINATE_RESP";
}
}
else
{
bOK=false;
strError="等待TERMINATE_RESP超时";
}
}
catch (Exception ex)
{
bOK=false;
strError=ex.Message;
}
if(m_NetworkStream!=null)
m_NetworkStream.Close();
if(m_TcpClient!=null)
m_TcpClient.Close();
m_TcpClient=null;
m_NetworkStream=null;
if(bOK)
RaiseEvent(SMS_STATE.SP_DISCONNECT,null);
else
RaiseEvent(SMS_STATE.SP_DISCONNECT_ERROR,strError);
return bOK;
}
protected bool Send(DATA_PACKAGE dp)
{
bool bOK=true;
string strError=string.Empty;
SMS_STATE state=SMS_STATE.UNKNOW_ERROR;
try
{
Thread.Sleep(m_iSendSpan);
Byte[] btData=null;
if(dp.Command==CMD_ACTIVE_TEST)
{
btData=((CMPP_HEAD)dp.Data).GetBuffer();
state=SMS_STATE.ACTIVE_TEST;
}
else if(dp.Command==CMD_ACTIVE_TEST_RESP)
{
btData=((CMPP_ACTIVE_TEST_RESP)dp.Data).GetBuffer();
state=SMS_STATE.ACTIVE_TEST_RESPONSE;
}
else if(dp.Command==CMD_DELIVER_RESP)
{
btData=((CMPP_DELIVER_RESP)dp.Data).GetBuffer();
state=SMS_STATE.DELIVER_RESPONSE;
}
else if(dp.Command==CMD_SUBMIT)
{
btData=((CMPP_SUBMIT)dp.Data).GetBuffer();
state=SMS_STATE.SUBMIT;
}
m_NetworkStream.Write(btData,0,btData.Length);
m_dtLastTransferTime=DateTime.Now;
}
catch(Exception ex)
{
bOK=false;
strError=ex.Message;
}
if(bOK)
{
RaiseEvent(state,dp.Data);
}
else
{
if(state==SMS_STATE.ACTIVE_TEST)
state=SMS_STATE.ACTIVE_TEST_ERROR;
else if(state==SMS_STATE.ACTIVE_TEST_RESPONSE)
state=SMS_STATE.ACTIVE_TEST_RESPONSE_ERROR;
else if(state==SMS_STATE.DELIVER_RESPONSE)
state=SMS_STATE.DELIVER_RESPONSE_ERROR;
else if(state==SMS_STATE.SUBMIT)
state=SMS_STATE.SUBMIT_ERROR;
RaiseEvent(state,strError);
}
return bOK;
}