使用PDU模式UCS2编码发送波斯语SMS出现乱码的原因及解决
波斯语短信PDU模式发送乱码问题排查与解决
问题描述
使用GSM调制解调器通过PDU模式+UCS2编码发送波斯语短信(内容为“سلام”)至号码0777220XXX,接收方收到的是㌆䐆✆这类乱码字符。已执行AT+CMGF=0切换PDU模式、将消息转为UTF-16大端(UCS2)格式、号码转为半字节编码,仍未解决乱码问题。
原始代码
public void SendSms(string recipientNumber, string messageText) { try { SerialPort port = new SerialPort(); port.PortName = "COM9"; // Make sure this is correct port.BaudRate = 9600; port.Parity = Parity.None; port.DataBits = 8; port.StopBits = StopBits.One; port.Handshake = Handshake.None; port.ReadTimeout = 5000; port.WriteTimeout = 5000; port.Open(); // Set to UCS2 character set port.WriteLine("AT+CSCS=\"UCS2\"\r"); Thread.Sleep(1000); string response = port.ReadExisting(); MessageBox.Show("Modem response UCS2: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); // Set SMS mode to PDU (0 = PDU mode) port.WriteLine("AT+CMGF=0\r"); Thread.Sleep(1000); response = port.ReadExisting(); MessageBox.Show("Modem response Text Mode: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); // Convert Persian message to PDU format string pduMessage = ConvertToPduFormat(recipientNumber, messageText); // Get PDU length (without SCA part) int pduLength = (pduMessage.Length / 2) - 1; // Send the PDU command port.WriteLine($"AT+CMGS={pduLength}\r"); Thread.Sleep(1000); response = port.ReadExisting(); MessageBox.Show("Modem response send pdu: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); // Send the actual PDU data port.Write(pduMessage + "\r"); Thread.Sleep(1000); // Send Ctrl+Z (ASCII 26) to submit SMS port.Write(new byte[] { 26 }, 0, 1); Thread.Sleep(5000); // Read response response = port.ReadExisting(); MessageBox.Show("Modem response send message: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); if (response.Contains("ERROR")) { MessageBox.Show("Send Failed!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { MessageBox.Show("Message Sent Successfully!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); SaveMessageToDatabase(recipientNumber, messageText); } port.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } public static string ConvertToPduFormat(string recipientNumber, string message) { // Convert Persian text to UCS2 (UTF-16 Big Endian) byte[] ucs2Bytes = Encoding.BigEndianUnicode.GetBytes(message); // Use BigEndianUnicode for UCS2 string hexMessage = BitConverter.ToString(ucs2Bytes).Replace("-", ""); // Convert phone number to international format (remove leading 0) if (recipientNumber.StartsWith("0")) { recipientNumber = "+93" + recipientNumber.Substring(1); // Example for Afghanistan (+93) } // Convert phone number to semi-octet format string pduPhoneNumber = ConvertPhoneNumberToSemiOctets(recipientNumber); // PDU Header string pduHeader = "0001000B91" + pduPhoneNumber + "0008" + hexMessage; return pduHeader; } private static string ConvertPhoneNumberToSemiOctets(string phoneNumber) { string semiOctets = ""; if (phoneNumber.StartsWith("+")) { phoneNumber = phoneNumber.Substring(1); } for (int i = 0; i < phoneNumber.Length; i += 2) { if (i + 1 < phoneNumber.Length) { semiOctets += phoneNumber[i + 1].ToString() + phoneNumber[i].ToString(); } else { semiOctets += "F" + phoneNumber[i].ToString(); // Add "F" padding if needed } } return semiOctets; }
乱码原因分析
- PDU头部结构缺失关键字段:原始代码的PDU头缺少**TP-UDL(用户数据长度)**字段,接收方无法正确解析消息长度;同时TP-DA长度字段位置错误,导致地址解析异常。
- 冗余字符集设置干扰:PDU模式下modem直接接收十六进制编码数据,
AT+CSCS="UCS2"是文本模式的字符集命令,会导致modem对数据进行额外编码处理。 - PDU长度计算错误:
(pduMessage.Length / 2) - 1的计算方式不符合AT+CMGS参数要求,参数应为TPDU的总字节数(十六进制字符串长度/2)。 - PDU数据发送冗余换行:发送PDU数据时添加
\r会破坏十六进制数据结构。
修正方案
1. 修复PDU头部结构
在ConvertToPduFormat方法中补充TP-UDL字段,并调整PDU头部结构:
public static string ConvertToPduFormat(string recipientNumber, string message) { // 转换波斯语为UCS2字节数组 byte[] ucs2Bytes = Encoding.BigEndianUnicode.GetBytes(message); string hexMessage = BitConverter.ToString(ucs2Bytes).Replace("-", ""); // TP-UDL:UCS2消息的字节数,转为两位十六进制 string udLength = ucs2Bytes.Length.ToString("X2"); // 转换号码为国际格式 if (recipientNumber.StartsWith("0")) { recipientNumber = "+93" + recipientNumber.Substring(1); } // 号码转半字节编码 string pduPhoneNumber = ConvertPhoneNumberToSemiOctets(recipientNumber); // 正确PDU结构:TP-MTI(00) + TP-MR(01) + TP-DA长度(0B) + 地址类型(91) + 号码 + TP-PID(00) + TP-DCS(08) + TP-UDL + 消息 string pduHeader = "00010B91" + pduPhoneNumber + "0008" + udLength + hexMessage; return pduHeader; }
2. 调整SendSms方法
- 移除冗余的
AT+CSCS="UCS2"命令 - 修正PDU长度计算逻辑
- 移除PDU数据发送时的冗余换行
public void SendSms(string recipientNumber, string messageText) { try { SerialPort port = new SerialPort(); port.PortName = "COM9"; port.BaudRate = 9600; port.Parity = Parity.None; port.DataBits = 8; port.StopBits = StopBits.One; port.Handshake = Handshake.None; port.ReadTimeout = 5000; port.WriteTimeout = 5000; port.Open(); // 设置PDU模式 port.WriteLine("AT+CMGF=0\r"); Thread.Sleep(1000); string response = port.ReadExisting(); MessageBox.Show("Modem response PDU Mode: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); // 生成PDU消息 string pduMessage = ConvertToPduFormat(recipientNumber, messageText); // 计算TPDU总字节数(十六进制字符串长度/2) int pduLength = pduMessage.Length / 2; // 发送CMGS命令 port.WriteLine($"AT+CMGS={pduLength}\r"); Thread.Sleep(1000); response = port.ReadExisting(); MessageBox.Show("Modem response send pdu: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); // 发送PDU数据(无冗余换行) port.Write(pduMessage); Thread.Sleep(1000); // 发送Ctrl+Z提交 port.Write(new byte[] { 26 }, 0, 1); Thread.Sleep(5000); // 读取结果 response = port.ReadExisting(); MessageBox.Show("Modem response send message: " + response, "Response", MessageBoxButtons.OK, MessageBoxIcon.Information); if (response.Contains("ERROR")) { MessageBox.Show("Send Failed!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Error); } else { MessageBox.Show("Message Sent Successfully!", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information); SaveMessageToDatabase(recipientNumber, messageText); } port.Close(); } catch (Exception ex) { MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
3. 额外验证项
- 发送
AT+CSCS=?查询modem支持的字符集,确认包含"UCS2" - 测试接收方手机的波斯语显示支持性,确认区域语言设置正确
- 先用中文“测试”等已知UCS2字符验证编码逻辑是否正常
内容的提问来源于stack exchange,提问作者Mohammad Samim Safi
相关产品推荐
相关产品推荐

