三年前, 我搞了一个短信猫玩, 所以就写了个控制短信猫的程序, 详见这里。
说是重构, 几乎就是除了界面部分都重写了一遍。
原来的设计, 使用了类似于同步的一种方式:
向串口发送命令, 然后读取并处理返回的信息, 就这么简单。
但是, 这个方式显然不能处理突发情况, 也就是短信猫主动发送数据的情况, 比如收到了新短信或者是有电话打进来。而是必须要等到下一次发送命令的时候, 才有可能处理得到。
“有可能”是因为, 我处理返回信息实际上就是判断里面有没有”OK”或者”ERROR”字样, 显然不会处理别的东西。
于是就这么用了几年。
现在的设计, 多开了个线程来读取串口发来的所有数据:
串口接收到的数据以行为单位, 一行就是一条数据。
使用了ManualResetEvent。当串口发来OK或者ERROR的时候, 保存这个OK或者ERROR的消息并设置这个event为非阻塞状态, 允许发送线程继续; 当串口发来其他数据, 比如收到了新短信或者是有电话打进来的时候, 就调用相应的处理程序。
串口不能同时读写, 并且读取操作是阻塞的, 于是在读取的时候就不能写入了。为了解决这个问题, 我设置了读取的超时时间, 并使用了同步锁。
同时, 顺便解决了接收短信有时候用TEXT模式不正常的问题, 使用了PDU格式。
下面是解析PDU的代码。
Imports System.Text Public Class PDUHelper Public Shared Function ToPDU(sms As ShortMessage) As String 'TODO: To PDU Throw New NotImplementedException() End Function Public Shared Function Parse(raw As String) As ShortMessage Dim offset = 0 Dim SMSCLength = HexToByte(raw.Substring(offset, 2)) offset += 2 Dim SMSC = DecodeAddress(raw.Substring(offset, SMSCLength * 2)) offset += SMSCLength * 2 Dim Unknown = HexToByte(raw.Substring(offset, 2)) offset += 2 Dim RemoteAddressLength = HexToByte(raw.Substring(offset, 2)) offset += 2 '转换成字节数 If RemoteAddressLength Mod 2 = 1 Then RemoteAddressLength += 1 End If RemoteAddressLength = RemoteAddressLength / 2 + 1 Dim RemoteAddress = DecodeAddress(raw.Substring(offset, RemoteAddressLength * 2)) offset += RemoteAddressLength * 2 Dim PID = HexToByte(raw.Substring(offset, 2)) offset += 2 Dim DCS = HexToByte(raw.Substring(offset, 2)) offset += 2 Dim SendTime = DecodeTime(raw.Substring(offset, 14)) offset += 14 Dim UDL = HexToByte(raw.Substring(offset, 2)) offset += 2 If DCS = 0 Then '7bit时, UDL存的是真实字符的个数, 要转换为字节数 UDL = Math.Ceiling(UDL * 7 / 8 ) End If Dim UD = HexToBytes(raw.Substring(offset, UDL * 2)) offset += UDL * 2 Dim UD_string = "" If DCS = 0 Then '7bit UD_string = Decode7bit(Math.Floor(UDL * 8 / 7), UD) ElseIf DCS = 8 Then 'UCS2 UD_string = DecodeUCS2(UD) Else UD_string = BytesToHex(UD) Utils.dbg("Warning: Unknown DCS, returning raw data") End If Dim sms As New ShortMessage sms.Message = UD_string sms.MessageCentre = SMSC sms.Time = SendTime sms.RemoteAddress = RemoteAddress Return sms End Function Public Shared Function DecodeTime(raw As String) As DateTime Dim year = Now.Year.ToString().Substring(0, 2) + raw(1) + raw(0) Dim month = raw(3) + raw(2) Dim day = raw(5) + raw(4) Dim hour = raw(7) + raw(6) Dim minute = raw(9) + raw(8) Dim second = raw(11) + raw(10) Dim offset = HexToByte(raw(13) + raw(12)) Return New DateTime(year, month, day, hour, minute, second) End Function Public Shared Function EncodeAddress(addr As String) As Byte() Dim _91 = addr(0) = "+" If _91 Then addr = addr.Substring(1) End If If addr.Length Mod 2 = 1 Then addr += "F" End If Dim len = addr.Length / 2 If _91 Then len += 1 End If Dim raw(len - 1) As Byte Dim offset = 0 If _91 Then raw(0) = &H91 offset = 1 End If For i = 0 To addr.Length - 1 Step 2 raw(offset + i / 2) = Convert.ToByte(addr(i), 16) + (Convert.ToByte(addr(i + 1), 16) << 4) Next Return raw End Function Public Shared Function DecodeAddress(raw As String) As String Dim sb As New StringBuilder() For i = 0 To raw.Length - 1 Step 2 Dim currblock = raw.Substring(i, 2) If i = 0 AndAlso currblock = "91" Then sb.Append("+") Else sb.Append(currblock(1)) If currblock(0).ToString().ToUpper() <> "F" Then sb.Append(currblock(0)) End If End If Next Return sb.ToString() End Function Public Shared Function DecodeAddress(raw As Byte()) As String Return DecodeAddress(BytesToHex(raw)) End Function Public Shared Function DecodeUCS2(raw As Byte()) As String Return Encoding.BigEndianUnicode.GetString(raw) End Function Public Shared Function EncodeUCS2(data As String) As Byte() Return Encoding.BigEndianUnicode.GetBytes(data) End Function Public Shared Function Encode7bit(data As String) As Byte() Dim raw = Encoding.Default.GetBytes(data) Dim rawlen = raw.Length Dim result(Math.Ceiling(rawlen * 7 / 8 )) As Byte Dim lastData As Byte = 0 '上一字节残余的数据 Dim i = 0, counter = 0 '8个字节一组 Do While i < rawlen Dim idInGroup = i Mod 8 '当前正在处理的组内字节的序号,范围是0-7 If idInGroup = 0 Then '组内第一个字节,只是保存起来,待处理下一个字节时使用 lastData = raw(i) Else '组内其它字节,将其右边部分与残余数据相加,得到一个目标编码字节 result(counter) = raw(i) << (8 - idInGroup) Or lastData lastData = raw(i) >> idInGroup counter += 1 End If i += 1 Loop If i Mod 8 > 0 Then result(counter) = lastData counter += 1 End If ReDim Preserve result(counter - 1) Return result End Function Public Shared Function Decode7bit(count As Integer, raw As Byte()) As String 'Dim raw = HexToByte(data) Dim rawlen = raw.Length Dim result(Math.Ceiling(rawlen * 8 / 7)) As Byte Dim lastData As Byte = 0 '上一字节残余的数据 Dim i = 0, counter = 0 '7个字节一组 Do While i < rawlen Dim idInGroup = i Mod 7 '当前正在处理的组内字节的序号,范围是0-6 result(counter) = (raw(i) << idInGroup Or lastData) And &H7F lastData = raw(i) >> (7 - idInGroup) counter += 1 i += 1 '到了一组的最后一个字节 If i Mod 7 = 0 Then '额外得到一个目标解码字节 result(counter) = lastData lastData = 0 counter += 1 End If Loop ReDim Preserve result(counter - 1) Return Encoding.Default.GetString(result) End Function Public Shared Function HexToBytes(data As String) As Byte() Dim bin(data.Length / 2 - 1) As Byte For i = 0 To data.Length - 1 Step 2 bin(i / 2) = Convert.ToByte(data.Substring(i, 2), 16) Next Return bin End Function Public Shared Function BytesToHex(data As Byte()) As String Dim sb As New StringBuilder() For i = 0 To data.Length - 1 sb.Append(data(i).ToString("X2")) Next Return sb.ToString() End Function Public Shared Function HexToByte(data As String) As Byte Return Convert.ToByte(data, 16) End Function Public Shared Function BytesToHex(data As Byte) As String Return data.ToString("X2") End Function End Class
重构后, 可维护性指数由80成功降为77!
1 条评论。