C# 串列埠連線的讀取與傳送
一、串列埠連線的開啟與關閉
串列埠,即COM口,在.NET中使用 SerialPort 類進行操作。串列埠開啟與關閉,是涉及慢速硬體的IO操作,頻繁開啟或關閉會影響整體處理速度,甚至導致開啟或關閉串列埠失敗。非特殊情況,串列埠一次性開啟後,在退出程式時關閉串列埠即可。在開啟串列埠前,可以設定一些常用的引數。常用的引數如下:
(1)串列埠的接受/傳送超時時間:ReadTimeout/WriteTimeout。
(2) 串列埠的接受/傳送快取區大小:ReadBufferSize/WriteBufferSize。
具體程式碼如下:
1 // Open Com 2 _serialPort = new SerialPort(com, baud); 3 if (_serialPort.IsOpen) _serialPort.Close(); 4 5 // Set the read / write timeouts 6 _serialPort.ReadTimeout = 500; 7 _serialPort.WriteTimeout = 500; 8 9 // Set read / write buffer Size,the default of value is 1MB 10 _serialPort.ReadBufferSize = 1024 * 1024; 11 _serialPort.WriteBufferSize = 1024 * 1024; 12 13 _serialPort.Open(); 14 15 // Discard Buffer 16 _serialPort.DiscardInBuffer(); 17 _serialPort.DiscardOutBuffer();
需要注意的是超出緩衝區的部分會被直接丟棄。因此,如果需要使用串列埠傳送大檔案,那接收方和傳送方都需要將各自的緩衝區域設定的足夠大,以便能夠一次性儲存下大檔案的二進位制陣列。若條件限制,緩衝區域不能設定過大,那就需要在傳送大檔案的時候按照發送緩衝區大小分包去傳送,接收方按順序把該陣列組合起來形成接受檔案的二進位制陣列。
二、串列埠傳送
SerialPort 類傳送支援二進位制傳送與文字傳送,需要注意的是文字傳送時,需要知道轉換的規則,一般常用的是ASCII、UTF7、UTF-8、UNICODE、UTF32。具體程式碼如下:
1 #region Send 2 /// <summary> 3 /// 傳送訊息(byte陣列) 4 /// </summary> 5 /// <param name="buffer"></param> 6 /// <param name="offset"></param> 7 /// <param name="count"></param> 8 public void Send(byte[] buffer, int offset, int count) 9 { 10 lock (_mux) 11 { 12 _serialPort.Write(buffer, offset, count); 13 _sendCount += (count - offset); 14 } 15 } 16 17 /// <summary> 18 /// 傳送訊息(字串) 19 /// </summary> 20 /// <param name="encoding">字串編碼方式,具體方式見<see cref="Encoding"/></param> 21 /// <param name="message"></param> 22 public void Send(Encoding encoding , string message) 23 { 24 lock (_mux) 25 { 26 var buffer = encoding.GetBytes(message); 27 _serialPort.Write(buffer, 0, buffer.Length); 28 _sendCount += buffer.Length; 29 } 30 } 31 #endregion
三、串列埠接受
串列埠接受需要注意,訊息接受與訊息處理要程式碼分離。不能把流程處理的程式碼放入資訊接受處,因為訊息處理或多或少會有耗時,這會造成當傳送方傳送過快時,接受方的接受緩衝區會快取多條訊息。我們可以把接受到的訊息放入佇列中,然後在外部執行緒中,嘗試去拿出該條訊息進行消費。採用 “生產-消費”模式。具體程式碼如下:
1 #region Receive 2 private void PushMessage() 3 { 4 _serialPort.DataReceived += (sender, e) => 5 { 6 lock (_mux) 7 { 8 if (_serialPort.IsOpen == false) return; 9 int length = _serialPort.BytesToRead; 10 byte[] buffer = new byte[length]; 11 _serialPort.Read(buffer, 0, length); 12 _receiveCount += length; 13 _messageQueue.Enqueue(buffer); 14 _messageWaitHandle.Set(); 15 } 16 }; 17 } 18 19 /// <summary> 20 /// 獲取串列埠接受到的內容 21 /// </summary> 22 /// <param name="millisecondsToTimeout">取訊息的超時時間</param> 23 /// <returns>返回byte陣列</returns> 24 public byte[] TryMessage(int millisecondsToTimeout = -1) 25 { 26 if (_messageQueue.TryDequeue(out var message)) 27 { 28 return message; 29 } 30 31 if (_messageWaitHandle.WaitOne(millisecondsToTimeout)) 32 { 33 if (_messageQueue.TryDequeue(out message)) 34 { 35 return message; 36 } 37 } 38 return default; 39 } 40 #endregion
四、完整程式碼與測試結果
串列埠工具類的完整程式碼如下:
1 using System; 2 using System.Collections.Concurrent; 3 using System.Collections.Generic; 4 using System.IO.Ports; 5 using System.Linq; 6 using System.Runtime.Serialization; 7 using System.Text; 8 using System.Threading; 9 using System.Threading.Tasks; 10 11 namespace SerialportDemo 12 { 13 public class SSerialPort 14 { 15 private SerialPort _serialPort; 16 private readonly ConcurrentQueue<byte[]> _messageQueue; 17 private readonly EventWaitHandle _messageWaitHandle; 18 private int _receiveCount, _sendCount; 19 private readonly object _mux; 20 public int ReceiveCount 21 { 22 get => _receiveCount; 23 } 24 public int SendCount 25 { 26 get => _sendCount; 27 } 28 public SSerialPort(string com, int baud ) 29 { 30 // initialized 31 _mux=new object(); 32 _receiveCount = 0; 33 _sendCount = 0; 34 _messageQueue = new ConcurrentQueue<byte[]>(); 35 _messageWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset); 36 37 // Open Com 38 OpenCom(com.ToUpper(),baud); 39 40 // Receive byte 41 PushMessage(); 42 } 43 44 private void OpenCom(string com, int baud) 45 { 46 // Open Com 47 _serialPort = new SerialPort(com, baud); 48 if (_serialPort.IsOpen) _serialPort.Close(); 49 50 // Set the read / write timeouts 51 _serialPort.ReadTimeout = 500; 52 _serialPort.WriteTimeout = 500; 53 54 // Set read / write buffer Size,the default of value is 1MB 55 _serialPort.ReadBufferSize = 1024 * 1024; 56 _serialPort.WriteBufferSize = 1024 * 1024; 57 58 _serialPort.Open(); 59 60 // Discard Buffer 61 _serialPort.DiscardInBuffer(); 62 _serialPort.DiscardOutBuffer(); 63 } 64 65 66 #region Static 67 /// <summary> 68 /// 獲取當前計算機的串列埠名的陣列 69 /// </summary> 70 /// <returns></returns> 71 public static string[] GetPortNames() 72 { 73 return SerialPort.GetPortNames(); 74 } 75 #endregion 76 77 #region Receive 78 private void PushMessage() 79 { 80 _serialPort.DataReceived += (sender, e) => 81 { 82 lock (_mux) 83 { 84 if (_serialPort.IsOpen == false) return; 85 int length = _serialPort.BytesToRead; 86 byte[] buffer = new byte[length]; 87 _serialPort.Read(buffer, 0, length); 88 _receiveCount += length; 89 _messageQueue.Enqueue(buffer); 90 _messageWaitHandle.Set(); 91 } 92 }; 93 } 94 95 /// <summary> 96 /// 獲取串列埠接受到的內容 97 /// </summary> 98 /// <param name="millisecondsToTimeout">取訊息的超時時間</param> 99 /// <returns>返回byte陣列</returns> 100 public byte[] TryMessage(int millisecondsToTimeout = -1) 101 { 102 if (_messageQueue.TryDequeue(out var message)) 103 { 104 return message; 105 } 106 107 if (_messageWaitHandle.WaitOne(millisecondsToTimeout)) 108 { 109 if (_messageQueue.TryDequeue(out message)) 110 { 111 return message; 112 } 113 } 114 return default; 115 } 116 #endregion 117 118 119 #region Send 120 /// <summary> 121 /// 傳送訊息(byte陣列) 122 /// </summary> 123 /// <param name="buffer"></param> 124 /// <param name="offset"></param> 125 /// <param name="count"></param> 126 public void Send(byte[] buffer, int offset, int count) 127 { 128 lock (_mux) 129 { 130 _serialPort.Write(buffer, offset, count); 131 _sendCount += (count - offset); 132 } 133 } 134 135 /// <summary> 136 /// 傳送訊息(字串) 137 /// </summary> 138 /// <param name="encoding">字串編碼方式,具體方式見<see cref="Encoding"/></param> 139 /// <param name="message"></param> 140 public void Send(Encoding encoding , string message) 141 { 142 lock (_mux) 143 { 144 var buffer = encoding.GetBytes(message); 145 _serialPort.Write(buffer, 0, buffer.Length); 146 _sendCount += buffer.Length; 147 } 148 } 149 #endregion 150 151 /// <summary> 152 /// 清空接受/傳送總數統計 153 /// </summary> 154 public void ClearCount() 155 { 156 lock (_mux) 157 { 158 _sendCount = 0; 159 _receiveCount = 0; 160 } 161 } 162 163 /// <summary> 164 /// 關閉串列埠 165 /// </summary> 166 public void Close() 167 { 168 _serialPort.Close(); 169 } 170 } 171 }View Code
測試程式碼如下:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Console.WriteLine($"該計算機可使用的串列埠列表:{string.Join(",", SSerialPort.GetPortNames())}"); 6 7 Console.Write("請輸入需要開啟的串列埠:"); 8 string port = Console.ReadLine(); 9 SSerialPort com = new SSerialPort(port, 57600); 10 Console.WriteLine($"串列埠 {port} 開啟成功..."); 11 12 Console.Write("請輸入需要開啟的串列埠傳送的訊息:"); 13 string text = Console.ReadLine(); 14 15 while (true) 16 { 17 com.Send(Encoding.Default, text); 18 Console.WriteLine($"總共傳送 {com.SendCount}"); 19 var message = com.TryMessage(); 20 if (message != null) 21 { 22 Console.WriteLine($"{DateTime.Now.ToString("HH:mm:ss fff")} {Encoding.Default.GetString(message)}"); 23 24 //// TEST:從新增延時可以測試到,接受訊息和處理訊息必須分不同執行緒處理。因為對於訊息的處理或多或少都需要耗時,這樣容易造成訊息處理不及時。而新增到佇列後,我們可以隨時取出處理 25 //System.Threading.Thread.Sleep(100*1); 26 } 27 Console.WriteLine($"總共接受 {com.ReceiveCount}"); 28 } 29 30 31 Console.ReadKey(); 32 } 33 }View Code
使用串列埠工具測試如下,對於串列埠的接受如絲般順滑。當我們在訊息中增加測試延時後,就會發現當串列埠工具繼續快速傳送一段時間後關閉傳送,發現使用佇列後,依然沒有丟失一條來自發送方的訊息。
&n