รูปแบบและวิธีปฏิบัติที่ดีที่สุดของโปรโตคอลการสื่อสาร


19

ทุกครั้งที่ฉันออกแบบโพรโทคอลอนุกรมที่จะใช้ระหว่างสอง Arduino ฉันรู้สึกบิตเหมือนฉัน reinventing ล้อ ฉันสงสัยว่ามีวิธีปฏิบัติที่ดีที่สุดหรือรูปแบบที่ผู้คนติดตาม คำถามนี้น้อยเกี่ยวกับรหัสจริง แต่เพิ่มเติมเกี่ยวกับรูปแบบของข้อความ

ตัวอย่างเช่นถ้าฉันต้องการบอกให้อาร์ดิโนแฟลชมันเป็น LED ครั้งแรก 3 ครั้งฉันอาจส่ง:

^L1,F3\n
  • '^': เริ่มคำสั่งใหม่
  • 'L': กำหนดคำสั่ง (L: กำหนดเป้าหมายคำสั่งนี้เป็น LED)
  • '1': กำหนดเป้าหมาย LED แรก
  • ',': ตัวคั่นบรรทัดคำสั่งค่าใหม่ในข้อความนี้เพื่อติดตาม
  • 'F': คำสั่งย่อยของ Flash
  • '3': 3 ครั้ง (แฟลช LED สามครั้ง)
  • '\ n': จบคำสั่ง

คิด? คุณมักจะเขียนโปรโตคอลอนุกรมใหม่อย่างไร ถ้าฉันต้องการส่งแบบสอบถามจาก arduino 1 ถึง arduino 2 แล้วรับการตอบกลับ

คำตอบ:


13

มีหลายวิธีในการเขียนโปรโตคอลอนุกรมขึ้นอยู่กับฟังก์ชันการทำงานที่คุณอาจต้องการและการตรวจสอบข้อผิดพลาดที่คุณต้องการ

สิ่งทั่วไปบางอย่างที่คุณเห็นในโปรโตคอลจุดต่อจุดคือ:

ท้ายข้อความ

โพรโทคอล ASCII ที่ง่ายที่สุดเพียงแค่มีลำดับของอักขระข้อความสิ้นสุดบ่อยครั้ง\rหรือ\nอย่างนี้คือสิ่งที่ได้รับการพิมพ์เมื่อกดปุ่ม Enter โปรโตคอลไบนารี่อาจใช้0x03หรือบางไบต์ทั่วไปอื่น ๆ

เริ่มต้นของข้อความ

ปัญหาที่เกิดขึ้นเมื่อสิ้นสุดข้อความคือคุณไม่ทราบว่ามีไบต์อื่นที่ได้รับเมื่อคุณส่งข้อความ ไบต์เหล่านี้จะถูกนำหน้าไปยังข้อความและทำให้ถูกตีความผิด ตัวอย่างเช่นหาก Arduino เพิ่งตื่นจากการสลีปอาจมีขยะในบัฟเฟอร์อนุกรม ในการหลีกเลี่ยงปัญหานี้คุณจะต้องเริ่มต้นลำดับข้อความ ในตัวอย่างของคุณ^, ในโปรโตคอลเลขฐานสองมักจะ0x02

ตรวจสอบข้อผิดพลาด

หากข้อความอาจเสียหายเราจำเป็นต้องตรวจสอบข้อผิดพลาด นี่อาจเป็น checksum หรือข้อผิดพลาด CRC หรืออย่างอื่น

ตัวละครหนี

อาจเป็นได้ว่าการตรวจสอบเพิ่มไปยังตัวควบคุมเช่นไบต์ 'เริ่มต้นของข้อความ' หรือ 'สิ้นสุดข้อความ' หรือข้อความที่มีค่าเท่ากับตัวควบคุม ทางออกคือการแนะนำตัวละครหนี อักขระเลี่ยงถูกวางไว้ก่อนอักขระควบคุมที่แก้ไขเพื่อให้ไม่มีอักขระควบคุมจริง เช่นถ้าอักขระเริ่มต้นคือ 0x02 โดยใช้อักขระเลี่ยง 0x10 เราสามารถส่งค่า 0x02 ในข้อความเป็นคู่ไบต์ 0x10 0x12 (อักขระควบคุมไบต์ XOR)

หมายเลขแพ็คเก็ต

หากข้อความเกิดความเสียหายเราสามารถขอให้ส่งอีกครั้งด้วยข้อความที่ขาดหรือลองใหม่ แต่ถ้ามีหลายข้อความที่ถูกส่งไปแล้วข้อความล่าสุดเท่านั้นที่สามารถส่งได้อีก แต่จะได้รับหมายเลขที่ห่อหุ้มแทนข้อความจำนวนหนึ่ง ตัวอย่างเช่นหากหมายเลขนี้คือ 16 อุปกรณ์ส่งสัญญาณสามารถจัดเก็บข้อความ 16 ข้อความสุดท้ายที่ส่งและหากมีความเสียหายอุปกรณ์ที่รับสามารถร้องขอการส่งซ้ำโดยใช้หมายเลขแพ็คเก็ต

ความยาว

บ่อยครั้งในโปรโตคอลไบนารี่คุณจะเห็นไบต์ยาวที่บอกอุปกรณ์ที่รับว่ามีกี่ตัวอักษรในข้อความ นี่เป็นการเพิ่มการตรวจสอบข้อผิดพลาดอีกระดับราวกับว่าไม่ได้รับจำนวนไบต์ที่ถูกต้องแสดงว่ามีข้อผิดพลาด

Arduino เฉพาะ

เมื่อมากับโปรโตคอลสำหรับ Arduino การพิจารณาอันดับแรกคือความน่าเชื่อถือของช่องทางการสื่อสาร หากคุณกำลังส่งผ่านสื่อไร้สายส่วนใหญ่ XBee, WiFi และอื่น ๆ มีการตรวจสอบข้อผิดพลาดและลองใหม่แล้วจึงไม่มีจุดในการวางสิ่งเหล่านี้ในโปรโตคอลของคุณ หากคุณกำลังส่งผ่าน RS422 สองสามกิโลเมตรจะมีความจำเป็น สิ่งที่ฉันจะรวมคือจุดเริ่มต้นของข้อความและจุดสิ้นสุดของข้อความตามที่คุณมี การนำไปใช้โดยทั่วไปของฉันดูเหมือนว่า:

>messageType,data1,data2,…,dataN\n

การแบ่งส่วนข้อมูลด้วยเครื่องหมายจุลภาคช่วยให้การแยกวิเคราะห์ง่ายและข้อความจะถูกส่งโดยใช้ ASCII โปรโตคอล ASCII นั้นยอดเยี่ยมเพราะคุณสามารถพิมพ์ข้อความลงในมอนิเตอร์แบบอนุกรม

หากคุณต้องการโปรโตคอลไบนารีอาจจะทำให้ขนาดข้อความสั้นลงคุณจะต้องใช้การหลบหนีหากข้อมูลไบต์สามารถเหมือนกับไบต์ควบคุม อักขระควบคุมไบนารีจะดีกว่าสำหรับระบบที่ต้องการตรวจสอบข้อผิดพลาดและลองใหม่อย่างเต็มรูปแบบ เพย์โหลดยังสามารถเป็น ASCII ได้ถ้าต้องการ


เป็นไปได้ไหมที่ขยะที่อยู่ด้านหน้าของรหัสเริ่มต้นที่แท้จริงอาจมีจุดเริ่มต้นของรหัสควบคุมข้อความ? คุณจะจัดการกับสิ่งนี้อย่างไร
CMCDragonkai

@CMCDragonkai ใช่นี่เป็นไปได้โดยเฉพาะอย่างยิ่งสำหรับรหัสควบคุมไบต์เดียว อย่างไรก็ตามหากคุณพบรหัสควบคุมการเริ่มต้นครึ่งทางผ่านการแยกวิเคราะห์ข้อความข้อความจะถูกละทิ้งและเริ่มการแยกวิเคราะห์ใหม่
geometrikal

9

ฉันไม่ได้มีความเชี่ยวชาญอย่างเป็นทางการในโปรโตคอลอนุกรม แต่ฉันใช้พวกเขาค่อนข้างสองสามครั้งและมากขึ้นหรือน้อยลงในโครงการนี้:

(ส่วนหัวแพ็กเก็ต) (ID ไบต์) (ข้อมูล) (fletcher16 เช็คซัม) (ส่วนท้ายของแพ็คเก็ต)

ฉันมักจะทำให้ส่วนหัว 2 ไบต์และส่วนท้าย 1 ไบต์ ตัวแยกวิเคราะห์ของฉันจะถ่ายโอนข้อมูลทุกอย่างเมื่อเห็นส่วนหัวแพ็คเก็ตใหม่และพยายามแยกวิเคราะห์ข้อความหากเห็นส่วนท้าย หากการตรวจสอบล้มเหลวก็จะไม่ทิ้งข้อความ แต่ยังคงเพิ่มจนกว่าจะพบตัวละครที่ส่วนท้ายและการตรวจสอบที่ประสบความสำเร็จ ด้วยวิธีนี้ส่วนท้ายจะต้องเป็นหนึ่งไบต์เนื่องจากการชนไม่รบกวนข้อความ

ID มีข้อผิดพลาดบางครั้งความยาวของส่วนข้อมูลคือแทะด้านล่าง (4 บิต) สามารถใช้บิตความยาวที่สองได้ แต่โดยปกติฉันไม่ต้องกังวลเนื่องจากความยาวไม่จำเป็นต้องแยกวิเคราะห์อย่างถูกต้องดังนั้นการเห็นความยาวที่ถูกต้องสำหรับ ID ที่ระบุนั้นเป็นเพียงการยืนยันเพิ่มเติมว่าข้อความนั้นถูกต้อง

fletcher16 checksum เป็นเช็คซัม 2 ไบต์ที่มีคุณภาพใกล้เคียงกับ CRC แต่สามารถนำไปใช้ได้ง่ายกว่ามาก รายละเอียดบางอย่างที่นี่ รหัสสามารถทำได้ง่ายอย่างนี้:

for(int i=0; i < bufSize; i++ ){
   sum1 = (sum1 + buffer[i]) % 255;
   sum2 = (sum2 + sum1) % 255;
}
uint16_t checksum = (((uint16_t)sum1)<<8) | sum2;

ฉันได้ใช้ระบบ call and respone สำหรับข้อความสำคัญด้วยซึ่งพีซีจะส่งข้อความทุก ๆ 500ms หรือมากกว่านั้นจนกว่าจะได้รับข้อความ OK พร้อมการตรวจสอบข้อความต้นฉบับทั้งหมดเป็นข้อมูล (รวมถึงการตรวจสอบต้นฉบับ)

แน่นอนว่ารูปแบบนี้ไม่เหมาะสำหรับการพิมพ์ลงในเทอร์มินัลเหมือนตัวอย่างของคุณ โปรโตคอลของคุณค่อนข้างดีสำหรับการถูก จำกัด ที่ ASCII และฉันแน่ใจว่าง่ายกว่าสำหรับโครงการด่วนที่คุณต้องการให้สามารถอ่านและส่งข้อความได้โดยตรง สำหรับโครงการขนาดใหญ่มันเป็นเรื่องดีที่จะมีความหนาแน่นของโปรโตคอลไบนารีและความปลอดภัยของการตรวจสอบ


เนื่องจาก "parser" ของคุณจะถ่ายโอนข้อมูลทุกอย่างเมื่อเห็นส่วนหัวของแพ็กเก็ตใหม่ "ฉันสงสัยว่าสิ่งนี้จะไม่สร้างปัญหาหรือไม่หากมีโอกาสที่ส่วนหัวจะพบภายในข้อมูล
humanityANDpeace

@humanityANDpeace เหตุผลที่ทำให้มันลดลงก็คือเมื่อแพ็กเก็ตถูกตัดออกมันจะไม่แยกวิเคราะห์อย่างถูกต้องดังนั้นเมื่อคุณตัดสินใจทิ้งขยะและย้ายไป วิธีที่ง่ายที่สุดและจากประสบการณ์ของฉันดีพอวิธีแก้ปัญหาคือการวางแพ็กเก็ตที่ไม่ถูกต้องทันทีที่ส่วนหัวถัดไปเข้ามาฉันใช้ส่วนหัว 16 บิตโดยไม่มีปัญหา แต่คุณสามารถทำให้มันยาวขึ้นได้ แบนด์วิดธ์
BrettAM

ดังนั้นสิ่งที่คุณอ้างถึงในฐานะส่วนหัวนั้นเป็นส่วนหนึ่งของชุด Magic 16 บิต เช่น 010101001 10101010 จริงไหม? ฉันยอมรับว่ามันเป็นเพียงการเปลี่ยนเป็น 1/256 * 256 เท่านั้น แต่มันยังปิดการใช้งาน 16 บิตภายในข้อมูลของคุณไม่เช่นนั้นจะมีการตีความผิดเป็นส่วนหัวและคุณทิ้งข้อความใช่ไหม
humanityANDpeace

@ มนุษยชาติandpeaceฉันรู้ว่ามันเป็นอีกหนึ่งปีต่อมา แต่คุณต้องแนะนำลำดับการหลบหนี ก่อนที่จะส่งเซิร์ฟเวอร์จะตรวจสอบเพย์โหลดสำหรับไบต์พิเศษใด ๆ จากนั้นจะหนีออกไปด้วยไบต์พิเศษอื่น ฝั่งไคลเอ็นต์คุณต้องใส่ส่วนของข้อมูลต้นฉบับกลับมารวมกัน นี่หมายความว่าคุณไม่สามารถส่งแพ็กเก็ตที่มีความยาวคงที่และทำให้การใช้งานมีความซับซ้อน มีมาตรฐานโปรโตคอลอนุกรมมากมายให้เลือกจากที่อยู่ทั้งหมดนี้ นี่คือการอ่านที่ดีมากในหัวข้อ
RubberDuck

1

หากคุณใช้มาตรฐานคุณอาจดูการเข้ารหัส ASN.1 / BER TLV ASN.1 เป็นภาษาที่ใช้อธิบายโครงสร้างข้อมูลซึ่งสร้างขึ้นเพื่อการสื่อสารโดยเฉพาะ BER เป็นวิธี TLV ในการเข้ารหัสโครงสร้างข้อมูลโดยใช้ ASN.1 ปัญหาคือการเข้ารหัส ASN.1 อาจเป็นเรื่องที่ดีที่สุด สร้างเต็มรูปแบบคอมไพเลอร์ ASN.1 เปี่ยมเป็นโครงการที่อยู่ในตัวเอง (และหนึ่งหากินโดยเฉพาะอย่างยิ่งที่ว่าคิดว่าเดือน )


น่าจะดีกว่าที่จะเก็บเฉพาะโครงสร้าง TLV ไว้ TLV โดยทั่วไปประกอบด้วยสามองค์ประกอบ: แท็กความยาวและเขตข้อมูลค่า แท็กกำหนดชนิดของข้อมูล (สตริงข้อความสตริง octet จำนวนเต็ม ฯลฯ ) และความยาวความยาวของค่า

ใน BER ค่า T ยังระบุว่าค่าเป็นชุดของโครงสร้าง TLV เอง (โหนดที่สร้างขึ้น) หรือค่าโดยตรง (โหนดดั้งเดิม) ด้วยวิธีนี้คุณสามารถสร้างแผนภูมิในรูปแบบไบนารีได้เหมือนกับ XML (แต่ไม่มีค่าใช้จ่าย XML)

ตัวอย่าง:

TT LL VV
02 01 FF

เป็นจำนวนเต็ม (แท็ก02) ที่มีความยาวค่า 1 (ความยาว01) และค่า -1 (ค่าFF) ใน ASN.1 / BER จำนวนเต็มจะลงนามหมายเลข endian ใหญ่ แต่แน่นอนคุณสามารถใช้รูปแบบของคุณเอง

TT LL (TT LL VV, TT LL VV VV)
30 07  02 01 FF  02 02 00 FF

เป็นลำดับ (รายการ) ที่มีความยาว 7 มีจำนวนเต็มสองจำนวนหนึ่งค่าหนึ่งมีค่า -1 และอีกค่าหนึ่งมีค่า 255 การเข้ารหัสจำนวนเต็มสองค่ารวมกันเป็นค่าลำดับ

คุณสามารถโยนมันลงในตัวถอดรหัสออนไลน์ได้เช่นกันใช่ไหม?


คุณสามารถใช้ความยาวไม่ จำกัด ใน BER ซึ่งจะช่วยให้คุณสามารถสตรีมข้อมูลได้ ในกรณีนี้คุณต้องแยกวิเคราะห์ต้นไม้อย่างถูกต้อง ฉันคิดว่ามันเป็นหัวข้อขั้นสูงคุณต้องรู้เกี่ยวกับความกว้างก่อนและการแจงส่วนแรกก่อน


การใช้โครงร่าง TLV ช่วยให้คุณคิดโครงสร้างข้อมูลชนิดใดก็ได้และเข้ารหัสมัน ASN.1 ก้าวไปไกลกว่านั้นโดยมอบตัวระบุที่ไม่ซ้ำใคร (OID's) ตัวเลือก (จำนวนมากเช่น C-unions) รวมถึงโครงสร้าง ASN.1 อื่น ๆ เป็นต้น แต่อาจทำให้โครงการของคุณมากเกินไป อาจเป็นโครงสร้างที่กำหนดโดย ASN.1 ที่รู้จักกันดีในปัจจุบันคือใบรับรองที่เบราว์เซอร์ของคุณใช้


0

ถ้าไม่คุณมีพื้นฐานครอบคลุม คำสั่งของคุณสามารถสร้างและอ่านโดยมนุษย์และเครื่องจักรซึ่งเป็นข้อดีอย่างมาก คุณอาจเพิ่มการตรวจสอบเพื่อตรวจสอบคำสั่งที่มีรูปแบบไม่ถูกต้องหรือสิ่งที่เสียหายระหว่างการขนส่งโดยเฉพาะอย่างยิ่งหากช่องของคุณมีสายยาวหรือลิงก์วิทยุ

หากคุณต้องการความแข็งแกร่งทางอุตสาหกรรม (อุปกรณ์ของคุณต้องไม่ก่อให้เกิดหรืออนุญาตให้ใครบางคนได้รับบาดเจ็บหรือตายคุณต้องการอัตราการส่งข้อมูลที่สูงการกู้คืนความผิดพลาดการตรวจจับแพ็คเก็ตหายไป ฯลฯ ) จากนั้น

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.