这两天在家呆着,由于公司接到一个客户需求,需要实现手机监控。这个任务落到了我的脑袋上,所以需要做出一套通用的收发短信的东西,周五做了一套同步收发的工具,但是一直苦恼于短信的解码问题,因此,周末在家自己研究了一下关于手机的编码解码问题。
0891683108501905F0040D91683176056198F70008902020906461230866258282597D76
0891683108501935F5240BA13113738894F00008901092718193230866258282597D76
上面分别是移动和CDMA发来的短信PDU编码。
1、发送短信PDU编码:
目前,发送短消息常用Text和PDU(Protocol Data Unit,协议数据单元)模式。使用Text模式收发短信代码简单,实现起来十分容易,但最大的缺点是不能收发中文短信;而PDU模式不仅支持中文短信,也能发送英文短信。PDU模式收发短信可以使用3种编码:7-bit、8-bit和UCS2编码。7-bit编码用于发送普通的ASCII字符,8-bit编码通常用于发送数据消息,UCS2编码用于发送Unicode字符。一般的PDU编码由A B C D E F G H I J K L M十三项组成。
A:短信息中心地址长度,2位十六进制数(1字节)。
B:短信息中心号码类型,2位十六进制数。
C:短信息中心号码,B+C的长度将由A中的数据决定。
D:文件头字节,2位十六进制数。
E:信息类型,2位十六进制数。
F:被叫号码长度,2位十六进制数。
G:被叫号码类型,2位十六进制数,取值同B。
H:被叫号码,长度由F中的数据决定。
I:协议标识,2位十六进制数。
J:数据编码方案,2位十六进制数。
K:有效期,2位十六进制数。
L:用户数据长度,2位十六进制数。
M:用户数据,其长度由L中的数据决定。J中设定采用UCS2编码,这里是中英文的Unicode字符。
PDU编码协议简单说明
例1 发送:SMSC号码是+8613800250500,对方号码是13693092030,消息内容是“Hello!”。从手机发出的PDU串可以是
08 91 68 31 08 20 05 05 F0 11 00 0D 91 68 31 96 03 29 30 F0 00 00 00 06 C8 32 9B FD 0E 01
对照规范,具体分析:
分段 含义 说明
08 SMSC地址信息的长度 共8个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
11 基本参数(TP-MTI/VFP) 发送,TP-VP用相对格式
00 消息基准值(TP-MR) 0
0D 目标地址数字个数 共13个十进制数(不包括91和‘F’)
91 目标地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 目标地址(TP-DA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
00 用户信息编码方式(TP-DCS) 7-bit编码
00 有效期(TP-VP) 5分钟
06 用户信息长度(TP-UDL) 实际长度6个字节
C8 32 9B FD 0E 01 用户信息(TP-UD) “Hello!”
例2 接收:SMSC号码是+8613800250500,对方号码是13693092030,消息内容是“你好!”。手机接收到的PDU串可以是
08 91 68 31 08 20 05 05 F0 84 0D 91 68 31 96 03 29 30 F0 00 08 30 30 21 80 63 54 80 06 4F 60 59 7D 00 21对照规范,具体分析:
分段 含义 说明
08 地址信息的长度 个八位字节(包括91)
91 SMSC地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 08 20 05 05 F0 SMSC地址 8613800250500,补‘F’凑成偶数个
84 基本参数(TP-MTI/MMS/RP) 接收,无更多消息,有回复地址
0D 回复地址数字个数 共13个十进制数(不包括91和‘F’)
91 回复地址格式(TON/NPI) 用国际格式号码(在前面加‘+’)
68 31 96 03 29 30 F0 回复地址(TP-RA) 8613693092030,补‘F’凑成偶数个
00 协议标识(TP-PID) 是普通GSM类型,点到点方式
08 用户信息编码方式(TP-DCS) UCS2编码
30 30 21 80 63 54 80 时间戳(TP-SCTS) 2003-3-12 08:36:45 +8时区
06 用户信息长度(TP-UDL) 实际长度6个字节
4F 60 59 7D 00 21 用户信息(TP-UD) “你好!”
我的编码解码的代码已经经过测试,希望能够给大家带来帮助。稍后会再追加收发短信的demo。
1. /// <summary>
2. /// 用来编码解码
3. /// 引用http://my.ihome99.com/space.php?uid=165788&do=blog&id=17368
4. /// </summary>
5. public class PDUCodingHelper
6. {
7. #region ...变量...
8. /// <summary>
9. /// 国际通用格式‘+’-91
10. /// </summary>
11. private const string InternationalFormat = "91";
12. /// <summary>
13. /// TP-MTI格式
14. /// </summary>
15. private const string TP_MTIFormat = "11";
16. /// <summary>
17. /// TP-MR格式
18. /// </summary>
19. private const string TP_MRFormat = "00";
20. /// <summary>
21. /// 协议方式
22. /// 默认点到点方式00
23. /// </summary>
24. private const string TP_PIDFormat = "00";
25. /// <summary>
26. /// 编码方式
27. /// 默认Unicode:TP_DCS
28. /// </summary>
29. private const string TP_DCSFormat = "08";
30. /// <summary>
31. /// 有效期
32. /// 默认5分钟
33. /// </summary>
34. private const string TP_VPFormat = "00";
35. #endregion ...变量...
36. #region ...方法...
37. #region ...公有方法...
38. /// <summary>
39. /// 编码
40. /// </summary>
41. /// <param name="pduPara"></param>
42. /// <returns></returns>
43. public static bool Encoding(STPDUParam pduPara,out string encodingPdu)
44. {
45. string.Empty;
46. bool suc = true;
47. try
48. {
49. #region ...处理短信中心...
50. int length;
51. new StringBuilder();
52. string interSCA = BuildInvertNumber(pduPara.SCA.ToCharArray());
53. length = interSCA.Length / 2 + 1;
54. "D2"));
55. encodingStrb.Append(InternationalFormat);
56. encodingStrb.Append(interSCA);
57. #endregion ...处理短信中心...
58. #region ...处理标准格式...
59. encodingStrb.Append(TP_MTIFormat);
60. encodingStrb.Append(TP_MRFormat);
61. #endregion ...处理标准格式...
62. #region ...处理手机号码...
63. length = pduPara.TPA.Length;
64. "X2"));
65. encodingStrb.Append(InternationalFormat);
66. encodingStrb.Append(BuildInvertNumber(pduPara.TPA.ToCharArray()));
67. #endregion ...处理手机号码...
68. #region ...处理协议、编码方式与有效期...
69. encodingStrb.Append(TP_PIDFormat);
70. encodingStrb.Append(TP_DCSFormat);
71. encodingStrb.Append(TP_VPFormat);
72. #endregion ...处理协议、编码方式与有效期...
73. #region ...处理用户短信内容...
74. string userData = pduPara.TP_UD;
75. byte[] buffer = System.Text.Encoding.Unicode.GetBytes(userData);
76. length = buffer.Length;
77. "X2"));
78. for (int i = 0; i < buffer.Length; i += 2)
79. {
80. if (i + 1 < buffer.Length)
81. {
82. "X2"));
83. }
84. "X2"));
85. }
86. #endregion ...处理用户短信内容...
87. encodingPdu = encodingStrb.ToString();
88. }
89. catch(Exception e)
90. {
91. "SMSPDUCoding.PDUCodingHelper.Encoding Excepttion: " + e.Message);
92. false;
93. }
94. return suc;
95. }
96. /// <summary>
97. /// 解码
98. /// </summary>
99. /// <param name="pduBody"></param>
100. /// <param name="pduDecoding"></param>
101. /// <returns></returns>
102. public static bool Decoding(string pduBody, out STPDUParam pduDecoding)
103. {
104. bool suc = true;
105. new STPDUParam();
106. try
107. {
108. int currentIndex = 0;
109. int codingProtocol;
110. #region ...处理短信中心...
111. int scaLength = 0;
112. byte bytscaLength = 0;
113. if (TryParseString2Byte(pduBody.Substring(currentIndex, 2), out bytscaLength))
114. {
115. int)bytscaLength * 2;
116. // 由于短消息中心中包含91
117. // 所以索引多加2,长度减2
118. currentIndex += 4;
119. scaLength -= 2;
120. string scaSrc = pduBody.Substring(currentIndex, scaLength);
121. string realSCA = BuildUnInvertNumber(scaSrc.ToCharArray());
122. pduDecoding.SCA = realSCA;
123. currentIndex += scaLength;
124. }
125. else
126. {
127. return false;
128. }
129. #endregion ...处理短信中心...
130. #region ...处理TP-MTI/MMS/RP...
131. // 这个信息在字符串中占2个字符
132. currentIndex += 2;
133. #endregion ...处理TP-MTI/MMS/RP...
134. #region ...处理短消息号码...
135. int numberLength = 0;
136. byte bytNumberLength = 0;
137. if (TryParseString2Byte(pduBody.Substring(currentIndex, 2), out bytNumberLength))
138. {
139. currentIndex += 2;
140. int) bytNumberLength;
141. // 由于短信号码不包含‘91’因此加1
142. // 飞信发送的格式为‘A1’,短信发送格式为‘91’
143. currentIndex += 2;
144. if (numberLength%2 != 0)
145. {
146. numberLength++;
147. }
148. string numberSrc = pduBody.Substring(currentIndex, numberLength);
149. string realNUmber = BuildUnInvertNumber(numberSrc.ToCharArray());
150. pduDecoding.TPA = realNUmber;
151. currentIndex += numberLength;
152. }
153. else
154. {
155. return false;
156. }
157. #endregion ...处理短消息号码...
158. #region ...处理协议标识与编码协议...
159. // 协议标识占2个字符
160. currentIndex += 2;
161. // 取编码方式
162. byte bytCodingProtocol = 0;
163. if (TryParseString2Byte(pduBody.Substring(currentIndex, 2), out bytCodingProtocol))
164. {
165. int) bytCodingProtocol;
166. currentIndex += 2;
167. }
168. else
169. {
170. return false;
171. }
172. #endregion ...处理协议标识与编码协议...
173. #region ...处理时间戳...
174. int year, month, day, hour, minute, second;
175. string yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr, timeAreaStr;
176. string timeStamp = pduBody.Substring(currentIndex, 14);
177. timeStamp = ConvertCharArray2String(InvertCharArray(timeStamp.ToCharArray()));
178. int timeStampIndex = 0;
179. yearStr = timeStamp.Substring(timeStampIndex, 2);
180. timeStampIndex += 2;
181. monthStr = timeStamp.Substring(timeStampIndex, 2);
182. timeStampIndex += 2;
183. dayStr = timeStamp.Substring(timeStampIndex, 2);
184. timeStampIndex += 2;
185. hourStr = timeStamp.Substring(timeStampIndex, 2);
186. timeStampIndex += 2;
187. minuteStr = timeStamp.Substring(timeStampIndex, 2);
188. timeStampIndex += 2;
189. secondStr = timeStamp.Substring(timeStampIndex, 2);
190. timeStampIndex += 2;
191. timeAreaStr = timeStamp.Substring(timeStampIndex, 2);
192. timeStampIndex += 2;
193. if (int.TryParse(yearStr, out year))
194. {
195. int)year;
196. }
197. else
198. {
199. return false;
200. }
201. if (!int.TryParse(monthStr, out month))
202. {
203. return false;
204. }
205. if (!int.TryParse(dayStr, out day))
206. {
207. return false;
208. }
209. if (!int.TryParse(hourStr, out hour))
210. {
211. return false;
212. }
213. if (!int.TryParse(minuteStr, out minute))
214. {
215. return false;
216. }
217. if (!int.TryParse(secondStr, out second))
218. {
219. return false;
220. }
221. new DateTime(year, month, day, hour, minute, second);
222. currentIndex += 14;
223. #endregion ...处理时间戳...
224. #region ...处理用户数据...
225. byte bytUserDataLength = 0;
226. string realUserData = string.Empty;
227. int userDataLength;
228. if (TryParseString2Byte(pduBody.Substring(currentIndex, 2), out bytUserDataLength))
229. {
230. int)bytUserDataLength;
231. if (userDataLength % 2 != 0)
232. {
233. return false;
234. }
235. currentIndex += 2;
236. string userDataSrc = pduBody.Substring(currentIndex, userDataLength*2);
237. switch (codingProtocol)
238. {
239. case 0:
240. // 0标识7bit编码
241. realUserData = Get7BitUserData(userDataSrc);
242. break;
243. case 8:
244. // 8标识Unicode编码
245. realUserData = GetUnicodeUserData(userDataSrc);
246. break;
247. }
248. pduDecoding.TP_UD = realUserData;
249. }
250. else
251. {
252. return false;
253. }
254. #endregion ...处理用户数据...
255. }
256. catch(Exception e)
257. {
258. "SMSPDUCoding.PDUCodingHelper.Decoding Excepttion: " + e.Message);
259. false;
260. }
261. return suc;
262. }
263. #endregion ...公有方法...
264. #region ...私有方法...
265. /// <summary>
266. /// 将字符串数组反向
267. /// </summary>
268. /// <param name="MobileNum"></param>
269. /// <returns></returns>
270. private static char[] InvertCharArray(char[] src)
271. {
272. char[] dst = new char[src.Length];
273. for (int i = 0; i < src.Length; i += 2)
274. {
275. if (i + 1 < src.Length)
276. {
277. dst[i] = src[i + 1];
278. dst[i + 1] = src[i];
279. }
280. else
281. {
282. dst[i] = src[i];
283. }
284. }
285. return dst;
286. }
287. /// <summary>
288. /// 生成符合PDU标准的号码
289. /// 手机号与短信中心号码,编码用
290. /// </summary>
291. /// <param name="numberArray"></param>
292. /// <returns></returns>
293. private static string BuildInvertNumber(char[] numberArray)
294. {
295. new StringBuilder();
296. char[] tempArray = new char[2];
297. for (int i = 0; i < numberArray.Length; i += 2)
298. {
299. if (i + 1 < numberArray.Length)
300. {
301. tempArray[0] = numberArray[i];
302. tempArray[1] = numberArray[i + 1];
303. }
304. else
305. {
306. tempArray[0] = numberArray[i];
307. 'F';
308. }
309. invertNumberStrb.Append(InvertCharArray(tempArray));
310. }
311. return invertNumberStrb.ToString();
312. }
313. /// <summary>
314. /// 还原号码
315. /// 手机号与短信中心号码,解码用
316. /// </summary>
317. /// <param name="numberArray"></param>
318. /// <returns></returns>
319. private static string BuildUnInvertNumber(char[] numberArray)
320. {
321. // 编码后号码长度均为偶数,如果不是偶数说明错误
322. Debug.Assert(numberArray.Length%2 == 0);
323. new StringBuilder();
324. char[] tempArray = new char[2];
325. for (int i = 0; i < numberArray.Length; i += 2)
326. {
327. tempArray[0] = numberArray[i + 1];
328. tempArray[1] = numberArray[i];
329. if (tempArray[1] != 'F')
330. {
331. numberStrb.Append(tempArray);
332. }
333. else
334. {
335. numberStrb.Append(tempArray[0]);
336. }
337. }
338. return numberStrb.ToString();
339. }
340. /// <summary>
341. /// 将字符串转化为byte
342. /// </summary>
343. /// <param name="strInput"></param>
344. /// <param name="bytOutput"></param>
345. /// <returns></returns>
346. private static bool TryParseString2Byte(string strInput, out byte bytOutput)
347. {
348. bool suc = true;
349. bytOutput = 0;
350. try
351. {
352. bytOutput = Convert.ToByte(strInput, 16);
353. }
354. catch(Exception e)
355. {
356. "SMSPDUCoding.PDUCodingHelper.TryParseString2Byte Exception: " + e.Message);
357. false;
358. }
359. return suc;
360. }
361. /// <summary>
362. /// 将字符数组转化为字符串
363. /// </summary>
364. /// <param name="charArray"></param>
365. /// <returns></returns>
366. private static string ConvertCharArray2String(char[] charArray)
367. {
368. new StringBuilder();
369. strb.Append(charArray);
370. return strb.ToString();
371. }
372. /// <summary>
373. /// 获取Unicode编码数据
374. /// </summary>
375. /// <param name="userDataSrc"></param>
376. /// <returns></returns>
377. private static string GetUnicodeUserData(string userDataSrc)
378. {
379. string res = "";
380. int len = userDataSrc.Length;
381. byte test;
382. byte[] resByte = new byte[len / 2];
383. for (int i = 0, j = 0; i < len; i += 2, j++)
384. {
385. //resByte[j] = VarTypeConvert.Str_2toByte(usc.Substring(i, 2));
386. byte bytTmp = 0;
387. out bytTmp);
388. resByte[j] = bytTmp;
389. }
390. for (int p = 0; p < resByte.Length; p += 2)
391. {
392. test = resByte[p];
393. resByte[p] = resByte[p + 1];
394. resByte[p + 1] = test;
395. }
396. res = System.Text.UnicodeEncoding.Unicode.GetString(resByte);
397. return res;
398. }
399. /// <summary>
400. /// 7Bit解码
401. /// </summary>
402. /// <param name="userDataSrc"></param>
403. /// <returns></returns>
404. private static string Get7BitUserData(string userDataSrc)
405. {
406. int iByte = 0;//字节位置(个数)
407. int iLeft = 0;//缓冲多余的字节位数
408. new System.Text.StringBuilder();
409. for (int i = 0; i < userDataSrc.Length; i += 2)
410. {
411. byte bSrc = byte.Parse(userDataSrc.Substring(i, 2), System.Globalization.NumberStyles.AllowHexSpecifier);
412. // 将源字节右边部分与残余数据相加,去掉最高位,得到一个目标解码字节
413. sb.Append(Convert.ToChar((((bSrc << iByte) | iLeft) & 0x7f)));
414. // 将该字节剩下的左边部分,作为残余数据保存起来
415. iLeft = bSrc >> (7 - iByte);
416. // 修改字节计数值
417. iByte++;
418. // 到了一组的最后一个字节
419. if (iByte == 7)
420. {
421. // 额外得到一个目标解码字节
422. sb.Append(Convert.ToChar(iLeft));
423. // 组内字节序号和残余数据初始化
424. iByte = 0;
425. iLeft = 0;
426. }
427. }
428. return sb.ToString();
429. }
430. #endregion ...私有方法...
431. #endregion ...方法...
432. }
433. /// <summary>
434. /// 短消息PDU参数
435. /// </summary>
436. public struct STPDUParam
437. {
438. /// <summary>
439. /// 短消息中心号码
440. /// SMS center address
441. /// </summary>
442. public string SCA;
443. /// <summary>
444. /// 目标号码或回复号码
445. /// TP-DA或TP-RA
446. /// </summary>
447. public string TPA;
448. /// <summary>
449. /// 时间戳字符串
450. /// </summary>
451. public string TP_SCTS;
452. /// <summary>
453. /// 用户数据,短消息内容
454. /// 接收时使用
455. /// </summary>
456. public string TP_UD;
457. /// <summary>
458. /// 时间戳
459. /// </summary>
460. public DateTime TimeStamp;
461. }