iT邦幫忙

1

C# 讀卡機傳送與接收封包不完全問題

  • 分享至 

  • xImage

各位大哥大姊好:

想請問用程式輪詢卡機,用送指令跟接收卡機資訊,但有時候卡機訊息會不完全(黏包?)的問題

已經用執行敘暫停 但還是會有問題,還是接收訊息的緩衝區要改小呢
https://ithelp.ithome.com.tw/upload/images/20230221/20097057yyveqZeK1d.png
謝謝

Byte[] sendmessage = new byte[] { 0x7e, 0x04, 0x01, 0x25, 0xdb, 0x01 }; //send message
Byte[] delsendmessage = new byte[] { 0x7e, 0x04, 0x01, 0x37, 0xc9, 0x01 }; //del指令
Byte[] opensendmessage = new byte[] { 0x7e, 0x05, 0x01, 0x21, 0x84, 0x5b, 0x01 }; //open door 指令 7E 05 01 21 84 5B 01
Byte[] showlcdmessage= new byte[] { 0x7e, 0x11, 0x01, 0x27, 0x20, 0x08, 0x77, 0x65, 0x6c, 0x63, 0x6f, 0x6d, 0x65, 0x21, 0x01, 0xf4, 0x03, 0x5c, 0xb1}; //show lcd

 while (true) //一直迴圈執行
   {
                   
     Thread tt1 = new Thread(new ParameterizedThreadStart(cb.SendMsg));
     tt1.Start(sendmessage); //boxing
     Console.WriteLine("TXS接收指令:" + staticClass.ToHexString(sendmessage, sendmessage.Length)); //靜態類別不用new
     string[] array = cb.ReceiveMsg();
     Console.WriteLine("RX:" +array[0]);//卡機回覆的資訊,如果沒回覆會卡住
     Console.WriteLine("RX驗證碼1:" + array[2]);
     Console.WriteLine("RX decision:" +array[1]);
     
     if (array[1] == "Y") //ReceiveMsg 接收到的指令 長度31 and  第4個byte為0B
     {
          cb.SendMsg(showlcdmessage);
          cb.SendMsg(opensendmessage);
          cb.SendMsg(delsendmessage);
     }
          Thread.Sleep(100); //Delay 1秒
    }

public void SendMsg(object data)  送指令
 {
            byte[] msg = (byte[]) data;
            NetworkStream ns = tmpTcpClient.GetStream();
            //得到從客戶端傳來的網路流
            if (ns.CanWrite) //如果資料流支援寫入
            {
               
                ns.Write(msg, 0, msg.Length);
                Thread.Sleep(100);

            }   
 }

public string[] ReceiveMsg() //封包接收TcpClient tmpTcpClient
  {

     NetworkStream ns = tmpTcpClient.GetStream();
     byte[] receiveBytes = new byte[tmpTcpClient.ReceiveBufferSize];  
     int numberOfBytesRead = 0;
     int length = 0;
     string receiveMsg = string.Empty;
     string validbyte = string.Empty;
     string cardinfo = string.Empty;
     string decision = "O";
     if (ns.CanRead) //裡面是區塊變數
     {
          do
           {
            numberOfBytesRead = ns.Read(receiveBytes, 0, tmpTcpClient.ReceiveBufferSize); //從 NetworkStream 讀取的位元組數 length
                   /*字串的轉換省略*/
                    if (!ns.DataAvailable) Thread.Sleep(100);
            }
                while (ns.DataAvailable);
      }
           
            return new string[] { receiveMsg, decision, validbyte };    
   }
ntustzeus iT邦新手 3 級 ‧ 2023-02-23 09:47:44 檢舉
考慮一下使用現成的netcoreserver, supersocket之類的套件吧,自己用thread做要考慮很多狀態管理的流程…
不然就是收到資料時要判斷開頭和結尾,收到完整就丟去parse,逾時就把不完整的資料丟掉,免得影響判斷
mayyola iT邦研究生 2 級 ‧ 2023-02-24 09:28:34 檢舉
n大您好: 我再找看看 謝謝你~~
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

3
JamesDoge
iT邦高手 1 級 ‧ 2023-02-22 04:35:00
最佳解答

在 ReceiveMsg 方法裡加入了緩衝區的處理,以防止黏包(TCP Sticky Packets)的問題。

public class CardReader
{
    private TcpClient tcpClient;

    public CardReader(string ipAddress, int port)
    {
        tcpClient = new TcpClient(ipAddress, port);
    }

    public void SendCommand(byte[] command)
    {
        NetworkStream stream = tcpClient.GetStream();
        if (stream.CanWrite)
        {
            stream.Write(command, 0, command.Length);
        }
    }

    public string[] ReceiveResponse()
    {
        NetworkStream stream = tcpClient.GetStream();
        byte[] buffer = new byte[tcpClient.ReceiveBufferSize];
        string response = string.Empty;
        string decision = "O";
        string validByte = string.Empty;

        int bytesRead = 0;
        int totalBytesRead = 0;
        do
        {
            bytesRead = stream.Read(buffer, totalBytesRead, tcpClient.ReceiveBufferSize - totalBytesRead);
            totalBytesRead += bytesRead;
        }
        while (stream.DataAvailable);

        if (totalBytesRead > 0)
        {
            byte[] responseBytes = new byte[totalBytesRead];
            Array.Copy(buffer, responseBytes, totalBytesRead);

            response = BitConverter.ToString(responseBytes).Replace("-", " ");
            decision = responseBytes[4] == 0x0B ? "Y" : "N";
            validByte = responseBytes[5].ToString("X2");
        }

        return new string[] { response, decision, validByte };
    }

    public void Close()
    {
        tcpClient.Close();
    }
}

你可以使用以上修改後的程式碼來取代原本的 SendMsg 和 ReceiveMsg 方法。在迴圈裡面的部分也可以改成如下的寫法:

while (true)
{
    cb.SendCommand(sendmessage);
    Console.WriteLine("TXS接收指令:" + BitConverter.ToString(sendmessage).Replace("-", " "));
    string[] response = cb.ReceiveResponse();
    Console.WriteLine("RX:" + response[0]);
    Console.WriteLine("RX驗證碼1:" + response[2]);
    Console.WriteLine("RX decision:" + response[1]);

    if (response[1] == "Y")
    {
        cb.SendCommand(showlcdmessage);
        cb.SendCommand(opensendmessage);
        cb.SendCommand(delsendmessage);
    }

    Thread.Sleep(1000);
}

mayyola iT邦研究生 2 級 ‧ 2023-02-24 09:28:12 檢舉

謝謝
想請問知道封包最長長度的話,tcpClient.ReceiveBufferSize是否可以設小一點? 我看說明緩衝區預設是8192bytes,若封包長度31bytes 是否緩衝區設定的大小大於31bytes就可以了呢?
不知道是否可以提升效率 減少黏包的情況?

我要發表回答

立即登入回答