沒發過短信的年輕人肯定是屬於那種受保護的稀有動物,通訊發達的今天短信已經成爲人們交流的重要手段,其中也蘊含着巨大的市場和經濟利益,掌握短信技術的人才也受到各大公司的追捧是目前職場上耀眼的明星。本文介紹了短信的原理和實現方法,重點說明了短信的編碼方式、AT指令以及用C#實現串口通訊的方法。
前言
目前,主有三種發送短信的方式:
1、 網關方式:就是向當地的電信部門申請,不需要額外的設備,適用於大型的通信公司,像華爲、傲天、中興、亞信等。
2、 終端方式:就是藉助像GSM MODEM之類的設置(支持AT指令的手機也行),通過數據線連接電腦,來發送短信,用這種方法比較適用於小型及個人。要實現這種方式必須理解串口通信、AT指令、短信編碼、解碼,這也是本文討論的重點。
3、 利用一些網站來實現,方式簡單,不過對網站依賴性太高,對網絡的要求也比較高,非常不適於進行項目開發
終端短信連接示意圖
原理篇
短信編碼
在收發短信方面,按時間產生先後,共產生了三種模式:Block Mode、基於AT指令的Text Mode、基於AT指令的PDU Modem, Text Mode比較簡單,多款諾基亞手機均支持該模式。西門子的手機大多隻支持PDU模式,PDU模式是發送或接收手機SMS信息的一種方法,短信息正文經過十六進制編碼後被傳送。目前,PDU已取代Block Mode,因我們主要探討PDU模式的發送。以西門子3508手機爲例。
SMS是由Etsi所制定的一個規範(GSM 03.40 和 GSM 03.38)。當使用7-bits編碼時,它可以發送最多160個字符;但用8-bit編碼,最多可以發送140個字符,通常無法直接通過手機顯示;還有用16-bit編碼時,最多70個字符,被用來顯示Unicode(UCS2)文本信息,可以被大多數的手機所顯示。我們今天討論的是UCS2編碼,也就是說,最多隻能發送70個字符,不管英文還是中文。
現例如我們現在要發送如下信息,向我的手機13715342642發送"你好,Hello!"。在沒有發送之前,你要清楚,手機SIM卡所在地的短信中心號,並不是你現在所在地方的短信中心號,像我在深圳,深圳的短信中心號是:8613800755000,即使我現在到外地,短信中心號仍是深圳。從上面我們得到了下面的信息:
接收的手機號:13715342642
短信中心號:8613800755000
短信內容:你好,Hello!
在實際使用中,上面這些信息並不爲手機所執行,要進行編碼手機纔會執行,先不管,看看編碼後的信息:
0891683108705500F011000D91683117352446F2000800124F60597DFF0C00480065006C006C006F0021
看不懂吧,我來解釋一下:
08 - 指的是短信中心號的長度,也就是指(91)+( 683108705500F0)的長度
91 - 指的是短信息中心號碼類型。91是TON/NPI遵守International/E.164標準,指在號碼前需加'+'號;此外還有其它數值,但91最常用。
683108705500F0 - 短信息中心號碼。由於位置上略有處理,實際號碼應爲:8613800731500(字母F是指長度減1)。這需要根據不同的地域作相應的修改。前面的(08)+(91)+( 683108705500F0)實際上就構成了整個短信的一部份,通稱短消息中心地址(Address of the SMSC)。
11 - 文件頭字節
00 - 信息類型(TP-Message-Reference)
0D - 被叫號碼長度
91 - 被叫號碼類型
其實在實際處理中,我們通常把11000D91寫死在程序中,因爲在國內,這些數據都是不會改變的。
683117352446F2 -被叫號碼,經過了位移處理,實際號碼爲"8613715342642"。上面的(00)+(0D)+(91)+( 683117352446F2),構成了整個短信的第二部份目的地址(TP-Destination-Address)。
00 - 協議標識TP-PID,這裏一般爲00
08 - 數據編碼方案TP-DCS(TP-Data-Coding-Scheme),採用前面說的USC2(16bit)數據編碼
00 - 有效期TP-VP(TP-Valid-Period)
12-長度TP-UDL(TP-User-Data-Length),也就是4F60597DFF0C00480065006C006C的長度 36 / 2 = 18 的十六進 12
4F60597DFF0C00480065006C006C 006F0021- 這裏就是短信內容了,實際內容爲:"你好,Hello!"程序實現,請參考本文章所帶源程序的PDUdecoding.cs。
AT指令
說到AT指令可多了,有厚厚的一本書,不屬於我們今天討論的範圍,在這裏我僅討論在發送短信中必須要用的幾個AT指令。
與SMS有關的GSM AT指令(from GSM07.05)如表1所示:
AT 指令 | 功 能 |
AT+CMGC | Send an SMS command(發出一條短消息命令) |
AT+CMGD | Delete SMS message(刪除SIM卡內存的短消息) |
AT+CMGF | Select SMS message formate(選擇短消息信息格式:0-PDU;1-文本) |
AT+CMGL | List SMS message from preferred store(列出SIM卡中的短消息PDU/text: 0/"REC UNREAD"-未讀,1/"REC READ"-已讀,2/"STO UNSENT"-待發,3/"STO SENT"-已發,4/"ALL"-全部的) |
AT+CMGR | Read SMS message(讀短消息) |
AT+CMGS | Send SMS message(發送短消息) |
AT+CMGW | Write SMS message to memory(向SIM內存中寫入待發的短消息) |
AT+CMSS | Send SMS message from storage(從SIN|M內存中發送短消息) |
AT+CNMI | New SMS message indications(顯示新收到的短消息) |
AT+CPMS | Preferred SMS message storage(選擇短消息內存) |
AT+CSCA | SMS service center address(短消息中心地址) |
AT+CSCB | Select cell broadcast messages(選擇蜂窩廣播消息) |
AT+CSMP | Set SMS text mode parameters(設置短消息文本模式參數) |
AT+CSMS | Select Message Service(選擇短消息服務) |
我現在以實例來說明這些指令的使用方法:
先用手機數據線將手機連接到電腦串口,並將串口的波特率設置爲19200,可以開始了。
1、首先測試你的連接及手機是否支持AT指令,請在你的串口調試程序中輸入:
AT<回車>
屏幕上返回"OK"表明計算機與手機連接正常,那樣我們就可以進行其它的AT指令測試了
2、設置短信發送格式
AT+CMGF=1<回車>
屏幕上返回"OK"表明現在短信的發送方式爲PDU方式,如果是設置爲TEXT方式,則,AT+CMGF=0<回車>
3、 發送短信
發送內容及手要號仍舊同上面在編碼中的一樣,編碼後,得到要發送的數據如下
0891683108705505F011000D91683117352446F2000800124F60597D002C00480065006C006C006F0021
我們用如下指令來發送
AT+CMGS=33<回車>
如果返回">",就把上面編碼數據輸入,並以CTRL+Z結尾,稍等一下,你就可以看到返回OK啦。
說明一下,爲什麼AT+CMGS=33呢,是這樣得來的:
11000D91683117352446F2000800124F60597D002C00480065006C006C006F0021
這一段字符串的長度除以2得到的結果,上面的字符串,短信中心號加上短信內容得到的,怎麼得到的,請回顧一下解碼部份
在我們前面的討論中,一條完整的短信發送,只要執行三條AT指令,AT、AT+CMGS=?、AT+CMGS=?就可以了。由於篇幅,我只能在這裏提到這麼多,大家要是想了解更多,可以向各手機廠商索取AT指令白皮書,裏面很詳細的。
上面講到的,只能爲我們實際中作準備,我們還必須要一個發送途徑,根據我們的需要,我們選擇投資最少,實現比較方便的串口通信。注意,串口通過數據線跟手機相連,用AT指令來實現發送短信,在我們選擇數據線時,建議購買原廠所配,非原廠所配,在使用過程中,經常出現一些莫明其妙的問題,比如,手機屏幕黑了,手機老是提示電池電量不足之類的。
串口通信
在C#中要實現串口通信,很多人都不知所措,在論壇上經常可以看到"怎麼用MSCOMM實現串口通信"、"怎樣能過串口與設備相連"諸如此類的問題。其實國外的網友早就把這些列入FAQ中了。
通常,在C#中實現串口通信,我們有四種方法:
第一:通過MSCOMM控件這是最簡單的,最方便的方法。可功能上很難做到控制自如,同時這個控件並不是系統本身所帶,所以還得註冊,不在本文討論範圍。可以訪問http://www.devhood.com/tutorials/tutorial_details.aspx?tutorial_id=320 ,一個國外網友的寫的教程,作者很熱心,我曾有發郵件給他,很快就回復了。
第二:微軟在.NET新推出了一個串口控件,基於.NET的P/Invoke調用方法實現,詳細的大家可以訪問微軟網站http://msdn.microsoft.com/msdnmag/issues/02/10/NETSerialComm/default.aspx,方便得到更多資料。
第三:就是用第三方控件啦,可一般都要付費的,不太合實際,不作考慮
第四:自己用API寫串口通信,這樣難度高點,但對於我們來說,可以方便實現自己想要的各種功能
在本文,我們採用第四種方法來實現串口通信,不過不是自己寫,用一個國外網友現成的已經封裝好的類庫,不過功能簡單點,相對我們來說已經夠用了。
在整個終端短信的操作過程中,與串口的通信,只用到了四個功能,打開、寫、讀、關閉串口。下面是類庫對這四個功能的定義:
打開串口:
函數原型:public void Open()
說明:打開事先設置好的端口
示例:
using JustinIO; static JustinIO.CommPort ss_port = new JustinIO.CommPort(); ss_port.PortNum = COM1; //端口號 ss_port.BaudRate = 19200; //串口通信波特率 ss_port.ByteSize = 8; //數據位 ss_port.Parity = 0; //奇偶校驗 ss_port.StopBits = 1;//停止位 ss_port.ReadTimeout = 1000; //讀超時 try { if (ss_port.Opened) { ss_port.Close(); ss_port.Open(); //打開串口 } else { ss_port.Open();//打開串口 } return true; } catch(Exception e) { MessageBox.Show("錯誤:" + e.Message); return false; } |
寫串口:
函數原型:public void Write(byte[] WriteBytes)
WriteBytes 就是你的寫入的字節,注意,字符串要轉換成字節數組才能進行通信
示例:
ss_port.Write(Encoding.ASCII.GetBytes("AT+CGMI/r")); //獲取手機品牌
讀串口:
函數原型:public byte[] Read(int NumBytes)
NumBytes 讀入緩存數,注意讀取來的是字節數組,要實際應用中要進行字符轉換
示例:
string response = Encoding.ASCII.GetString(ss_port.Read(128)); //讀取128個字節緩存
關閉串口:
函數原型:ss_port.Close()
示例:
ss_port.Close();
由於篇幅,以及串口通信涉及內容廣泛,我在這裏只講這些。
在上面我們已經把終端短信所需的各種原始技術有所瞭解,是可以小試牛刀的時候了。
實踐篇
在整個開始的時候,你要準備以下軟硬件:
硬件:西門子3508或C35系列手機一個
西門子手機通信數據線一條
軟件:VS.NET(C#)
短信編碼類庫(PDUdecoding.cs)
串口通信類庫(JustinIO.cs)
當所要求的軟硬件都準備好後,我們就可以正式開始了。下面以我自己的測試用例爲大家詳細介紹。
做什麼事情都應該有計劃,雖然我們的測試用例很簡單,但還是畫個簡單的流程圖:
有了流程圖,還只是明白了程序怎麼運行,再看看界面,會讓你更心動的了。
圖二、短信終端C#版界面圖
再不開始,就有人罵我了。下在我講的開發環境是在VS.NET(C#)中。COME GO,GO…
步驟一、打開VS.NET,新建項目->Visual C#項目->Windows應用程序,名稱中輸入你的工程名就行啦,我的是smsForCsharp
步驟二、參照上面的界面圖,設計你的程序界面,下面是我程序中各控件的主要屬性
控件名稱 | 控件Name屬性 | 說明 |
TextBox | targetNumber | 接收手機號碼 |
TextBox | CenterNumber | 短信中心號 |
TextBox | smsState | 發送短信後,返回的信息。注意設置控件爲多行 |
TextBox | smsContent | 短信內容,同樣,注意設置爲多行 |
ComboBox | ConnectPort | 連接手機的端口,例:COM1/COM2 |
ComboBox | ConnectBaudRate | 串口連接的波特率,在串口通信中很重要的 |
Button | btnSend | 發送按鈕 |
Button | btnConnect | 連接按鈕,主要用於程序的初始化 |
Button | btnExit | 退出按鈕 |
步驟三、將PDUdecoding.cs與JustinIO.cs拷入剛剛新建工程目錄,並打開解決方案資源管理器,右鍵添加現有項,選中兩個文件就行了,這裏再打開類視圖,裏面是不是多了兩個類,JustinIO與SMS類啊,如圖三,要是沒有,那你再試。
圖三,添加類後的類視圖
步驟四、引用命名空間,用代碼查看方式打開Form1.cs(這裏以我電腦爲準,如果你自己更改過,請以你電腦爲準),在代碼前面加上
using JustinIO; using SMS; using System.IO; using System.Text; |
步驟五、在smsFormCsharp類中,添加兩個字段ss_port、sms,分別爲JustinIO及SMS的對象,如下
步驟六、添加串口初始化代碼,如下:
/// <summary> /// 初始化串口 /// </summary> public bool InitCom(string m_port, int m_baudrate) { ss_port.PortNum = m_port;//串口號 ss_port.BaudRate = m_baudrate;//波特率 ss_port.ByteSize = 8;//數據位 ss_port.Parity = 0;// ss_port.StopBits = 1;//停止位 ss_port.ReadTimeout = 1000;//讀超時 try { if (ss_port.Opened) { ss_port.Close(); ss_port.Open(); } else { ss_port.Open();//打開串口 } return true; } catch(Exception e) { MessageBox.Show("錯誤:" + e.Message); return false; } } |
將上述代碼直接拷入你的程序中,並確保添加在Main主函數的後面,按F5,調試應該沒什麼問題,不過上面還沒有實際任何看得見的功能,僅僅是打開了串口而以。
步驟七、打開串口後,我們就應該初始化程序,取得手機的名牌,型號,以及短信中心號,雙擊連接按鈕,並把下面代碼拷入程序中:
/// <summary> /// 初始化代碼,並獲取手機相關信息 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnConnect_Click(object sender, System.EventArgs e) { bool opened = InitCom(ConnectPort.SelectedItem.ToString(),Convert.ToInt32(ConnectBaudRate.SelectedItem.ToString()));//打開並初始化串口 bool Connected = false; if (opened) { ss_port.Write(Encoding.ASCII.GetBytes("AT+CGMI/r")); //獲取手機品牌 string response = Encoding.ASCII.GetString(ss_port.Read(128)); if (response.Length > 0) { ConnectState.Text = response.Substring(10,7); Connected = true; } else { ConnectState.Text = "與手機連接不成功"; Connected = false; } ss_port.Write(Encoding.ASCII.GetBytes("AT+CGMM/r"));//獲取手機型號 response = Encoding.ASCII.GetString(ss_port.Read(128)); if(response.Length > 0) { ConnectState.Text =ConnectState.Text+ " " + response.Substring(10,5) + " 連接中......"; Connected = true; } else { ConnectState.Text = "與手機連接不成功"; Connected = false; } ss_port.Write(Encoding.ASCII.GetBytes("AT+CSCA?/r"));//獲取手機短信中心號 response = Encoding.ASCII.GetString(ss_port.Read(128)); if(response.Length > 0) { CenterNumber.Text = response.Substring(20,13); Connected = true; } else { Connected = false; } if (Connected == true) { btnConnect.Enabled = false; btnSend.Enabled = true; } else { btnConnect.Enabled = true; btnSend.Enabled = false; } } } |
到這裏,你可以按F5,編譯調試,通過,在確保你的手機與電腦連接正常下,點擊連接按鈕看看,是不是像我的一樣,手機型號及短信中心號者正常顯示出來了。
圖四、連接後程序界面
步驟八、看到上在的結果,是不是感覺到離成功發送短信很近啦,看這麼長的文章,費了大家不少時間,再不亮出發短信部份,對不起大家了。
雙擊發送按鈕,將下面代碼拷入程序中。
/// <summary> /// 發送短信 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnSend_Click(object sender, System.EventArgs e) { string decodedSMS = sms.smsDecodedsms(CenterNumber.Text,targetNumber.Text,smsContent.Text); byte[] buf =Encoding.ASCII.GetBytes(String.Format("AT+CMGS={0}/r",sms.nLength)); ss_port.Write(buf); string response = Encoding.ASCII.GetString(ss_port.Read(128)); string SendState = ""; if( response.Length > 0 && response.EndsWith("> ")) { ss_port.Write(Encoding.ASCII.GetBytes(String.Format("{0}/x01a",decodedSMS))); SendState = "發送成功!"; } else { SendState = "發送失敗"; } string Result = String.Format("{0},{1},{2},/n/r",targetNumber.Text,smsContent.Text,SendState); smsState.Text += Result; } |
快按F5吧!神啊,快通過吧!不用求神了,已經通過了,現在你就可以發短信了,請確保手機可以正常連接電腦。按連接,然後填入你要的發送的目標手機號,並在內容中添入你要發送的內容,發送吧!成功了!成功了是這樣子的!看你的跟我的一樣嗎?
圖五、發送成功
還有一些事 不要忘了,記得添加退出代碼。雙擊退出,添加下面代碼:
/// <summary> /// 關閉串口,退出程序 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void btnExit_Click(object sender, System.EventArgs e) { ss_port.Close(); Application.Exit(); } |
到這裏都告一個段落了,所有的功能都完成了!不過由於這僅僅是一個演示用例,還有很多沒有考慮,像串口通信中的,在實際操作不可這樣操作的,應該用多線程來處理,一個專門用來讀串口,一個專門用來寫串口。還有程序中很多防出錯代碼沒有添加進去,希望有心有朋友添加,並公佈出來,這也是我寫這篇文章希望看到的結果。請勿將本程序直接用於實際中,真誠提醒你!
終於寫完了,我也放鬆了許多,本來很早就應該完成了,因爲一些個人原因,沒有及時寫完,向那些曾經問過我相關問題,沒有及時回覆的朋友,抱歉一聲,希望你們繼續支持我!
調試環境:
Windows 2000 Professional、Visual Studio.NET、西門子3508手機、西門子專用數據線。
常見問題: 第一, 手機品牌,因爲不同產商的手機,對AT指令的支持不同,所以請選擇適合你手機AT指令,像NOKIA的就只能用TEXT模式的AT指令。 第二, 數據線,問題出得最多的地方也就是數據,如果接上數據線後,你的手機顯示爲黑屏,建議你換數據線。 第三, 手機SIM卡上的短信中心號設置,請確保在你的手機上可以發送短信。 第四, 請你先用串口調試工具調試手機與電腦的連接,這樣對你整個工作都是一個保證。 |