1. 程式人生 > >C# 串列埠連線的讀取與傳送

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