中英文手机短信 PDU 串 编码(UCS2) 解码(UCS2,7-Bit) 程序 ( in C# )
//=====================================================================================   
/*  关于手机短信(SMS)的编码模式及其规则,读者可以查阅相关规范, 如 GSM03.08, GSM03.40, 
GSM07.05等. 科脑工作室的 bhw98 先生在文献<<通过串口收发短消息(上/下篇)>>中通过实例给出了 
详细说明, <<Visual C++/Turbo C 串口通信编程实践>>(电子工业出版社)一书中对其原文进行了全 
面重载, 有兴趣的读者可参考以上文献. 
    本文给出通过串口收发手机中英文短信的全部 C# 实现过程.中英文短信内容编码均采用UCS2模 
式, 考虑到有些手机在纯英文模式下编码为 7-Bit模式, 为了能对这些内容解读, 文中也提供了 
7-Bit 模式短信内容解码程序; 文中编码解码过程未使用任何第三方控件. 
    文中提供了两个主要函数 SendSms()和 ReceiveSms(), 直接调用就可实现短信发送和接收, 例 
如: SendSms("13800755500","13423714101","神州六号将于近期发射!"); 即完成发送;  
ReceiveSms(); 即完成接收, 内容存于字符数组 ReceTele[],ReceTime[],ReceText[]中; 
    开发平台: Windows 2003 Server; Visual Studio.NET 2003 ;添加 VS 98 MSComm控件; 
    硬件: 西门子TC35i终端或其它无线MODEM;  
    西门子TC35i终端AT指令集可从以下网站下载: 
    
http://www.wlt.net.cn/index0/tc35i/tc35i.htm, 文件名为:tc35i_atc_v0103_1073581.pdf  
主要程序代码如下: 
[作者约定: 若某一行以注释开头, 则该注释用于说明该行后面内容;否则, 若以代码开头,则其后 
注释用于说明本行内容]      
*/  
//=====================================================================================   
using System;  
using System.Drawing;  
using System.Collections;  
using System.ComponentModel;  
using System.Windows.Forms;  
using System.Data;  
using System.Text; //Encoding 等函数名字空间   
using System.Threading; // Thread.Sleep 函数名字空间   
  
namespace TC35iSMS  
{  
    public class Form1 : System.Windows.Forms.Form  
    {  
        //下列字符串数组用于存放已收短信相关内容   
        private string[] ReceTele=new string[40]; //电话号码    
        private string[] ReceTime=new string[40]; //日期时间   
        private string[] ReceText=new string[40]; //短信正文   
        private System.Windows.Forms.TextBox txtSmscNumber;  
        private System.Windows.Forms.ListBox RecMessListBox;  
        private System.Windows.Forms.TextBox txtDestNumber;  
        private System.Windows.Forms.TextBox txtSmsText;  
        private System.Windows.Forms.Button btnReceive;  
        private System.Windows.Forms.Label lbMessage;  
        private System.Windows.Forms.Button btnSend;  
        private System.Windows.Forms.Label label3;  
        //MsComm串口控件,可从 VS 98 选择安装;即将发行的 VS.NET 2005 中   
        //自带有串口控件,用户可直接使用   
        private AxMSCommLib.AxMSComm COM1;  
        private System.Windows.Forms.Label label1;  
        private System.Windows.Forms.Label label2;       
  
        private System.ComponentModel.Container components = null;  
  
        public Form1()  
        {  
            InitializeComponent();  
        }  
  
        protected override void Dispose( bool disposing )  
        {  
            if( disposing )  
            {  
                if (components != null)   
                {  
                    components.Dispose();  
                }  
            }  
            base.Dispose( disposing );  
        }  
 
        #region Windows 窗体设计器生成的代码   
        /// <summary>   
        /// 设计器支持所需的方法 - 不要使用代码编辑器修改   
        /// 此方法的内容。   
        /// </summary>   
        private void InitializeComponent()  
        {  
            System.Resources.ResourceManager resources = new System.Resources.ResourceManager(typeof(Form1));  
            this.txtSmscNumber = new System.Windows.Forms.TextBox();  
            this.RecMessListBox = new System.Windows.Forms.ListBox();  
            this.txtDestNumber = new System.Windows.Forms.TextBox();  
            this.txtSmsText = new System.Windows.Forms.TextBox();  
            this.btnReceive = new System.Windows.Forms.Button();  
            this.lbMessage = new System.Windows.Forms.Label();  
            this.btnSend = new System.Windows.Forms.Button();  
            this.label3 = new System.Windows.Forms.Label();  
            this.COM1 = new AxMSCommLib.AxMSComm();  
            this.label1 = new System.Windows.Forms.Label();  
            this.label2 = new System.Windows.Forms.Label();  
            ((System.ComponentModel.ISupportInitialize)(this.COM1)).BeginInit();  
            this.SuspendLayout();  
            //    
            // txtSmscNumber   
            //    
            this.txtSmscNumber.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));  
            this.txtSmscNumber.ForeColor = System.Drawing.Color.Blue;  
            this.txtSmscNumber.Location = new System.Drawing.Point(24, 32);  
            this.txtSmscNumber.MaxLength = 11;  
            this.txtSmscNumber.Name = "txtSmscNumber";  
            this.txtSmscNumber.Size = new System.Drawing.Size(80, 21);  
            this.txtSmscNumber.TabIndex = 42;  
            this.txtSmscNumber.Text = "13800755500";  
            //    
            // RecMessListBox   
            //    
            this.RecMessListBox.ItemHeight = 12;  
            this.RecMessListBox.Location = new System.Drawing.Point(8, 96);  
            this.RecMessListBox.Name = "RecMessListBox";  
            this.RecMessListBox.ScrollAlwaysVisible = true;  
            this.RecMessListBox.Size = new System.Drawing.Size(536, 280);  
            this.RecMessListBox.TabIndex = 41;  
            //    
            // txtDestNumber   
            //    
            this.txtDestNumber.ForeColor = System.Drawing.Color.Blue;  
            this.txtDestNumber.Location = new System.Drawing.Point(552, 120);  
            this.txtDestNumber.MaxLength = 13;  
            this.txtDestNumber.Name = "txtDestNumber";  
            this.txtDestNumber.Size = new System.Drawing.Size(88, 21);  
            this.txtDestNumber.TabIndex = 37;  
            this.txtDestNumber.Text = "13423714101";  
            //    
            // txtSmsText   
            //    
            this.txtSmsText.ForeColor = System.Drawing.Color.Blue;  
            this.txtSmsText.Location = new System.Drawing.Point(208, 8);  
            this.txtSmsText.MaxLength = 70;  
            this.txtSmsText.Multiline = true;  
            this.txtSmsText.Name = "txtSmsText";  
            this.txtSmsText.Size = new System.Drawing.Size(432, 80);  
            this.txtSmsText.TabIndex = 35;  
            this.txtSmsText.Text = "";  
            //    
            // btnReceive   
            //    
            this.btnReceive.Font = new System.Drawing.Font("宋体", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));  
            this.btnReceive.ForeColor = System.Drawing.Color.Blue;  
            this.btnReceive.Location = new System.Drawing.Point(560, 248);  
            this.btnReceive.Name = "btnReceive";  
            this.btnReceive.Size = new System.Drawing.Size(80, 24);  
            this.btnReceive.TabIndex = 40;  
            this.btnReceive.Text = "接 收";  
            this.btnReceive.Click += new System.EventHandler(this.btnReceive_Click);  
            //    
            // lbMessage   
            //    
            this.lbMessage.Font = new System.Drawing.Font("宋体", 12F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));  
            this.lbMessage.ForeColor = System.Drawing.Color.Red;  
            this.lbMessage.Location = new System.Drawing.Point(16, 64);  
            this.lbMessage.Name = "lbMessage";  
            this.lbMessage.Size = new System.Drawing.Size(184, 23);  
            this.lbMessage.TabIndex = 38;  
            //    
            // btnSend   
            //    
            this.btnSend.Font = new System.Drawing.Font("宋体", 10F, System.Drawing.FontStyle.Bold, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));  
            this.btnSend.Location = new System.Drawing.Point(560, 184);  
            this.btnSend.Name = "btnSend";  
            this.btnSend.Size = new System.Drawing.Size(80, 23);  
            this.btnSend.TabIndex = 1;  
            this.btnSend.Text = "发 送";  
            this.btnSend.Click += new System.EventHandler(this.btnSend_Click);  
            //    
            // label3   
            //    
            this.label3.ForeColor = System.Drawing.Color.Blue;  
            this.label3.Location = new System.Drawing.Point(144, 8);  
            this.label3.Name = "label3";  
            this.label3.Size = new System.Drawing.Size(56, 16);  
            this.label3.TabIndex = 34;  
            this.label3.Text = "发送内容";  
            //    
            // COM1   
            //    
            this.COM1.Enabled = true;  
            this.COM1.Location = new System.Drawing.Point(96, 32);  
            this.COM1.Name = "COM1";  
            this.COM1.OcxState = ((System.Windows.Forms.AxHost.State)(resources.GetObject("COM1.OcxState")));  
            this.COM1.Size = new System.Drawing.Size(38, 38);  
            this.COM1.TabIndex = 43;  
            //    
            // label1   
            //    
            this.label1.ForeColor = System.Drawing.Color.Black;  
            this.label1.Location = new System.Drawing.Point(8, 8);  
            this.label1.Name = "label1";  
            this.label1.Size = new System.Drawing.Size(112, 16);  
            this.label1.TabIndex = 44;  
            this.label1.Text = "中心号码(不需+86)";  
            //    
            // label2   
            //    
            this.label2.Font = new System.Drawing.Font("宋体", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((System.Byte)(134)));  
            this.label2.ForeColor = System.Drawing.Color.Blue;  
            this.label2.Location = new System.Drawing.Point(568, 96);  
            this.label2.Name = "label2";  
            this.label2.Size = new System.Drawing.Size(56, 16);  
            this.label2.TabIndex = 45;  
            this.label2.Text = "对方号码";  
            //    
            // Form1   
            //    
            this.AutoScaleBaseSize = new System.Drawing.Size(6, 14);  
            this.ClientSize = new System.Drawing.Size(648, 381);  
            this.Controls.Add(this.label2);  
            this.Controls.Add(this.label1);  
            this.Controls.Add(this.COM1);  
            this.Controls.Add(this.txtSmscNumber);  
            this.Controls.Add(this.RecMessListBox);  
            this.Controls.Add(this.txtDestNumber);  
            this.Controls.Add(this.txtSmsText);  
            this.Controls.Add(this.btnReceive);  
            this.Controls.Add(this.lbMessage);  
            this.Controls.Add(this.btnSend);  
            this.Controls.Add(this.label3);  
            this.Name = "Form1";  
            this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen;  
            this.Text = "中英文手机短信 PDU 串 编码(UCS2) 解码(UCS2,7-Bit) 程序 ( in C# by 李仓海 )";  
            this.Load += new System.EventHandler(this.Form1_Load);  
            ((System.ComponentModel.ISupportInitialize)(this.COM1)).EndInit();  
            this.ResumeLayout(false);  
  
        }  
        #endregion   
  
        [STAThread]  
        static void Main()   
        {  
            Application.Run(new Form1());  
        }  
//=====================================================================================   
private void Form1_Load(object sender, System.EventArgs e)  
{  
    COM1.CommPort=1; //硬件连于 COM1   
    //串口设定: 波特率19200,无校验,8位数据位,1位停止位   
    COM1.Settings="19200,n,8,1";   
    COM1.InBufferSize=1024*8; //8K 读缓冲   
    COM1.PortOpen=true;//打开串口            
    for(int i=0;i<40;i++) //字符串数组清空    
    {  
        ReceTele[i]=""; //电话号码   
        ReceTime[i]=""; //日期时间   
        ReceText[i]=""; //短信正文   
    }  
}  
//=====================================================================================   
private void btnSend_Click(object sender, System.EventArgs e) //点击发送按扭   
{  
    this.btnSend.Enabled=false; //发送过程其它功能按扭禁用   
    this.btnReceive.Enabled=false;  
    this.SendSms(this.txtSmscNumber.Text,this.txtDestNumber.Text,  
                 this.txtSmsText.Text);  
    this.btnSend.Enabled=true; //开启功能按扭   
    this.btnReceive.Enabled=true;  
}  
//=====================================================================================   
private void btnReceive_Click(object sender, System.EventArgs e)//点击接收按扭   
{  
    this.btnSend.Enabled=false;//接收过程其它功能按扭禁用   
    this.btnReceive.Enabled=false;  
    this.ReceiveSms(); //调用接收函数, 接收内容存于相应数组   
    this.RecMessListBox.Items.Clear();//短信显示栏清空   
    int Sms_Number=0; //短信数目变量   
    for (int i=0;i<40;i++) //显示已收短信内容   
    {  
        if ( ReceText[i].ToString().Length>0)  
        {  
            Sms_Number+=1;  
            this.RecMessListBox.Items.Add("来自:"+ReceTele[i]+  
                                         " 时间:"+ReceTime[i]);     
            this.RecMessListBox.Items.Add("内容:"+ReceText[i]);     
            this.RecMessListBox.Items.Add(" "); //短信间隔用空行   
            ReceTele[i]=""; //显示后字符串数组清空    
            ReceTime[i]="";  
            ReceText[i]="";  
        }  
    }  
    if(Sms_Number==0)  
        this.lbMessage.Text="暂无短信!";  
    else  
        this.lbMessage.Text="收到 "+Sms_Number.ToString()+" 条新短信!";  
    this.btnSend.Enabled=true;//开启功能按扭   
    this.btnReceive.Enabled=true;  
}  
//=====================================================================================   
private void SendSms(string Smsc_Number,string Dest_Number,string Sms_Text)//发送函数   
{  
    string pdu=""; //开始合成 PDU 串   
    pdu+="089168";   
    //SMSC中心号码补F凑成偶数   
    char[] tmpSmscNumber=(Smsc_Number+"F").ToCharArray();   
    for(int i=0;i<tmpSmscNumber.Length;i+=2) //字符两两对调   
    {  
        pdu+=tmpSmscNumber[i+1].ToString();  
        pdu+=tmpSmscNumber[i].ToString();  
    }  
    pdu+="11000D9168";  
    char[] tmpDestNumber=(Dest_Number+"F").ToCharArray();//对方号码   
    for(int i=0;i<tmpDestNumber.Length;i+=2) //字符两两对调   
    {  
        pdu+=tmpDestNumber[i+1].ToString();  
        pdu+=tmpDestNumber[i].ToString();  
    }  
    pdu+="000800"; //08 表示采用 UCS2 编码   
    //短信正文转为Unicode                                        
    byte[] tmpSmsText=Encoding.Unicode.GetBytes(Sms_Text);  
    pdu+=tmpSmsText.Length.ToString("X2"); //正文内容长度   
    for(int i=0;i<tmpSmsText.Length;i+=2) //高低字节对调    
    {  
        pdu+=tmpSmsText[i+1].ToString("X2");//("X2")转为16进制   
        pdu+=tmpSmsText[i].ToString("X2");  
    } //PDU串 完成   
    int tmpLength=(pdu.Length-18)/2;//除去SMSC段长度   
    //向串口发Send an SMS 命令   
    this.COM1.Output="AT+CMGC="+tmpLength+(char)13;    
    Thread.Sleep(500);//延时 0.5 秒   
    this.COM1.Output=pdu;//输出 PDU 串   
    this.COM1.Output=((char)26).ToString();//结束   
    Thread.Sleep(1500);//延时 1.5 秒   
}  
//=====================================================================================   
private void ReceiveSms() //接收函数   
{  
    //向串口发 List SMS Messages from preferred store 指令   
    this.COM1.Output="AT+CMGL=0"+(char)13;                       
    Thread.Sleep(1000); //延时 1 秒    
    if(this.COM1.InBufferCount<=0)  
        return;   
    string ans="";//清空应答串   
    //该循环用于当卡中有多条(>=5)未读短信时可一次全部读出   
    while(this.COM1.InBufferCount>0)  
    {  
        ans+=this.COM1.Input.ToString(); //将串口 Buff 内容存入应答串 ans   
        Thread.Sleep(3000); //延迟3秒     
    }  
    //下列循环将已读短信逐一从SIM卡内存中删除,否则若SIM卡内存不足将再无发收发!    
    for (int i=1;i<=40;i++)     
    {  
        string m_id="+CMGL: "+i.ToString();        
        if(ans.IndexOf(m_id,0)>=0) //取得 SIM 卡内存中短信 ID 号   
        {  
            Thread.Sleep(500);//延时 0.5 秒   
            this.COM1.Output="AT+CMGD="+i+(char)13; //删除SIM卡内存中短信   
        }  
    }  
    //开始查找是否有合格 PDU 串   
    if(ans.IndexOf("+CMGL:",0)<0 ||ans.IndexOf("OK",0)<0   
                 || ans.IndexOf("ERROR",0)>=0)      
    { ans=""; return;}  
    ans=ans.Substring(ans.IndexOf("+CMGL:",0));  
    int MessNo=0; //存储短信相关内容的数组下标   
    string tmpPdu="";    
    while(ans.IndexOf("0891")!=-1)  
    {  
        ans=ans.Substring(ans.IndexOf("0891",0));   
        int NextOne=ans.IndexOf("+CMGL:");  
        if(NextOne!=-1) //同时收到多条 SMSes   
        {  
            tmpPdu=ans.Substring(0,NextOne-2);//读取第一条PDU串   
            ans=ans.Substring(NextOne);//定位到下一条   
        }  
        else  
        {  
            int LastOk=ans.IndexOf("OK",0); //仅收到一条 SMS   
            tmpPdu=ans.Substring(0,LastOk-4);//读取PDU串   
            ans="";//应答串清空   
        }  
        string EncodeType=tmpPdu.Substring(40,2);//编码方式   
        if(EncodeType=="08" || EncodeType=="00")  
        {  
            ReceTele[MessNo]=this.GetTeleFromPdu(tmpPdu);//电话号码    
            ReceTime[MessNo]=this.GetTimeFromPdu(tmpPdu);//日期时间   
            //以下为短信正文解析   
            if(EncodeType=="08") //UCS2 解码   
                ReceText[MessNo]=this.GetTextFromPdu_UCS2(tmpPdu);   
            else  //7-Bit 解码       
                ReceText[MessNo]=this.GetTextFromPdu_7Bit(tmpPdu);  
            MessNo+=1;  
        }  
    }  // end of while             
}  
//=====================================================================================   
private string GetTeleFromPdu(string pdu) //解析 TeleNumber 函数   
{  
    //截取PDU串中短信发送方电话号码源码   
    string TeleInPdu=pdu.Substring(26,12);  
    string Tele="";  
    char[] d=TeleInPdu.ToCharArray();//存入字符数组   
    for (int i=0;i<d.Length;i+=2)//字符两两对调   
    {  
        Tele+=d[i+1].ToString();  
        Tele+=d[i].ToString();  
    }  
    Tele=Tele.Substring(0,11);//去掉末位"F"   
    return Tele;//返回发送方电话号码   
}  
//=====================================================================================   
private string GetTimeFromPdu(string pdu) //解析 DateTime 函数   
{  
    //截取PDU串中短信日期时间源码   
    string TimeInPdu=pdu.Substring(42,12);  
    string Time="";  
    char[] d=TimeInPdu.ToCharArray();//存入字符数组   
    for (int i=0;i<d.Length;i+=2)//字符两两对调   
    {  
        Time+=d[i+1].ToString();  
        Time+=d[i].ToString();  
    }  
    Time="20"+Time; //将年份前加 20 形成 4 位格式,以下为日期时间输出格式控制   
    Time=Time.Substring(0,4)+"-"+Time.Substring(4,2)+"-"  
        +Time.Substring(6,2)+" "+Time.Substring(8,2)+":"  
        +Time.Substring(10,2)+":"+Time.Substring(12,2);   
    return Time; //返回短信日期时间[年-月-日 时:分:秒]   
}  
//=====================================================================================   
private string GetTextFromPdu_UCS2(string pdu) //解析短信正文函数_UCS2编码   
{  
    //截取PDU串中短信正文部分源码,读者也可用BitConverter函数实现部分转换   
    string TextInPdu=pdu.Substring(58);  
    string Text="";  
    char[] d=TextInPdu.ToCharArray();//存入字符数组   
    for (int i=0;i<d.Length;i+=4)                  
    {  
        int unicode_nu=0;  
        for (int m=0;m<4;m++) //计算 Unicode 十进制值   
            unicode_nu+=HexToDec(d[i+m])*(1<<((3-m)*4));  
        Text+=(char)unicode_nu; //输出 Unicode 对应字符   
    }  
    return Text;//返回短信正文内容   
}  
//=====================================================================================   
private int HexToDec(char Hex) //16 进制转 10 进制   
{  
    int Dec;  
    if(Hex>='0' && Hex<='9')  
        Dec=Convert.ToInt16(Hex-'0'); //0-9   
    else  
        Dec=Convert.ToInt16(Hex-'A')+10;// A-F   
    return Dec;//返回 10 进制值   
}  
//=====================================================================================   
//该段内容为作者根据 7-Bit 编码原理图,应用数学矩阵关系式方法导出的解码流程,相信一定有   
//更简便的方法, 恳请读者批评指教!! Email: 
sztcm@public.szptt.net.cn   
private string GetTextFromPdu_7Bit(string pdu) //解析短信正文函数_7-Bit编码   
{  
    string TextInPdu=pdu.Substring(58);//截取PDU串中短信正文部分源码   
    string Text="";  
    while (TextInPdu.Length%14!=0) //最后一组不满7个成员时补"0"   
        TextInPdu+="0";  
    char[] a=TextInPdu.ToCharArray(); //将源码存入字符数组 a[]   
    string b="";  
    for(int i=0;i<a.Length;i++) //将源码转为二进制并存入字符串 b   
        b+=GetBinary(a[i]);  
    char[] total=b.ToCharArray(); //将二进制码存入字符数组 total[]   
    for ( int j=0;j<total.Length;j+=56) //56位二进制码为一组,循环所有组     
    {  
        char[] s=new char[56];                     
        for(int i=0;i<56;i++)  //将一组二进制码拷贝到字符数组 s[]   
            s[i]=total[i+j];  
        char[] d=new char[56];                     
//-------------------------------------------------------------------------------------   
        for(int i=0;i<7;i++) //组内解码得到目标二进制码数组 d[]   
            d[i]=s[i+1];  
        for(int k=1;k<=6;k++)  
        {  
            for( int i=k*7;i<(k+1)*7-k; i++)  
                d[i]=s[i+(k*2+1)];  
            for ( int i=(k+1)*7-k;i<(k+1)*7;i++)  
                d[i]=s[i-((7-k)*2+1)];  
        }  
        for ( int i=49;i<56;i++)     
            d[i]=s[i-1];  
//-------------------------------------------------------------------------------------   
        for(int k=0;k<56;k+=7) //组内循环得到目标 ASCII 字符           
        {  
            int ascii_nu=0;  
            for (int m=0;m<7;m++)  
              ascii_nu+=Convert.ToInt16(d[k+m].ToString())*(1<<(6-m));  
            Text+=(char)ascii_nu; //输出 ASCII 码相应字符   
        }  
    }  // END OF loop j   
    return Text;  
}  
//=====================================================================================   
private string GetBinary(char Hex) //16 进制转 2 进制   
{  
    int Dec;  
    if(Hex>='0' && Hex<='9')  
        Dec=Convert.ToInt16(Hex-'0');  
    else  
        Dec=Convert.ToInt16(Hex-'A')+10;  
    int displayMask=1<<3;  
    StringBuilder Bin=new StringBuilder();  
    for(int i=0;i<4;i++)  
    {  
        //append 0 or 1 depending on result of masking         
        Bin.Append( (Dec & displayMask)==0 ? "0":"1" );  
        //shift left so that mask will find bit of next digit    
        //during next iteration of loop   
        Dec<<=1;  
    }    
    return Bin.ToString();  
}  
//=====================================================================================   
    }  // END OF class Form1   
} // END OF namespace TC35iSMS