9.10. TCP -- สตรีมไบต์ที่เชื่อถือได้

TCP หรือ Transmission Control Protocol คือบริการเลเยอร์การส่งอีกประเภทหนึ่งที่ทำงานบน IP ถ้า UDP ถูกอธิบายได้ดีที่สุดว่า "ส่งแพ็กเก็ตแล้วหวังว่าจะถึง" TCP คือ "เปิดการเชื่อมต่อระหว่างสองจุดปลายทางแล้วถือว่าเป็นท่อไบต์สองทางที่อีกฝั่งได้รับแน่นอน เรียงลำดับ และได้รับเพียงครั้งเดียว" การสื่อสารทางอินเทอร์เน็ตส่วนใหญ่ใช้ TCP และงานเครือข่ายส่วนใหญ่ของ กล้อง ก็ใช้ TCP เช่นกัน

9.10.1. สิ่งที่ TCP เพิ่มเติมจาก IP

TCP ทำงานได้มากกว่า UDP มาก โดยเพิ่มสิ่งต่อไปนี้:

  • การเชื่อมต่อ ก่อนที่ข้อมูลใดจะไหลผ่าน สองจุดปลายทางจะแลกเปลี่ยนการจับมือกันสั้น ๆ เพื่อยืนยันว่ากำลังสื่อสารกัน การเชื่อมต่อมีสถานะ เช่น "เปิด" "ปิดครึ่งหนึ่ง" "ปิด" ซึ่งทั้งสองฝั่งติดตาม

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

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

  • การควบคุมการไหล ถ้าผู้รับช้า ผู้ส่งจะถูกบอกให้ลดความเร็วลง การเชื่อมต่อจะปรับตัวตามอัตราของฝั่งที่อ่อนแอกว่า

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

ทั้งหมดนี้เป็นอัตโนมัติ Python API ที่แอปพลิเคชันใช้คือเพียงแค่ send(bytes) และ recv(n) เท่านั้น TCP จัดการทุกอย่างที่เหลือภายใต้นั้น

9.10.2. การจับมือกัน

การเชื่อมต่อ TCP เปิดด้วยการแลกเปลี่ยนสามทางก่อนที่จะอนุญาตให้ข้อมูลใดผ่านได้:

A diagram with two columns labelled "client" and "server". An arrow from client to server labelled "SYN", then an arrow from server to client labelled "SYN-ACK", then an arrow from client to server labelled "ACK". Below that a thick arrow labelled "data flowing both ways".

การจับมือกันสามทาง เมื่อทั้งสองฝั่งยืนยันแล้ว การเชื่อมต่อจะเปิดและข้อมูลสามารถไหลได้

client ส่งแพ็กเก็ต SYN (synchronise) เพื่อขอเปิด server ตอบด้วย SYN-ACK (synchronise + acknowledge) เพื่อยอมรับ client ส่ง ACK สุดท้ายเพื่อยืนยัน หลังจากการส่งกลับมาครั้งนี้ ทั้งสองฝั่งเห็นด้วยว่าการเชื่อมต่อเปิดอยู่และได้ซิงโครไนซ์ตัวนับสำหรับติดตามไบต์ที่ส่งและรับแล้ว

การจับมือกันมีค่าใช้จ่ายหนึ่ง round-trip-time ของเวลาแฝงก่อนที่ไบต์แรกที่มีประโยชน์จะผ่าน สำหรับเครือข่ายท้องถิ่นนั้นคือหนึ่งมิลลิวินาที สำหรับการเชื่อมต่อข้ามทวีปนั้นประมาณ 100 ms นี่คือต้นทุนหลักของ TCP และเหตุผลที่ข้อความสั้นแบบครั้งเดียวบางครั้งทำงานได้ดีกว่าเมื่อใช้ UDP แทน

9.10.2.1. การจับมือกันเพื่อปิด

การเชื่อมต่อ TCP ยังปิดด้วยการแลกเปลี่ยน (โดย FIN จากแต่ละฝั่ง) ฝั่งใดก็ได้สามารถปิดครึ่งหนึ่งของการเชื่อมต่อได้อย่างอิสระ สถานะ half-closed ที่ฝั่งหนึ่งหยุดส่งแต่อีกฝั่งยังคงพูดอยู่นั้นถูกต้องตามกฎหมาย แม้จะไม่ค่อยพบเห็น แอปพลิเคชันปกติเพียงแค่เรียก close() และปล่อยให้โปรโตคอลจัดการลำดับการปิด

9.10.3. สิ่งที่ TCP ไม่ รับประกัน

สิ่งสองสามอย่างที่บางครั้งถูกสมมติว่า TCP ทำแต่ไม่ได้ทำ:

  • ขอบเขตของข้อความ แอปพลิเคชันส่ง สตรีมของไบต์ ไม่ใช่สตรีมของข้อความ การเรียก send(b"hello") สองครั้งอาจมาถึงเป็น recv() เดียวของ b"hellohello" หรือเป็น recv()สองครั้งที่มีขนาดต่างกัน ถ้าแอปพลิเคชันต้องการการกำหนดกรอบข้อความ ต้องเพิ่มการกำหนดกรอบเองเช่น newline prefix ความยาว หรืออะไรก็ได้ การส่งเอกสาร JSON ผ่าน TCP เช่น ต้องการแต่ละเอกสารที่คั่นด้วย newline หรือเครื่องหมายอื่น

  • การเข้ารหัส TCP ส่งไบต์ที่แอปพลิเคชันให้มาในรูปแบบธรรมดาตลอดทาง ถ้าเนื้อหาต้องเป็นความลับ แอปพลิเคชันต้องห่อการเชื่อมต่อด้วย TLS (ดู ซ็อกเก็ตที่เข้ารหัสและ TLS)

  • การพิสูจน์ตัวตน TCP ตรวจสอบว่าไบต์มาถึงครบถ้วน แต่ไม่ได้บอกว่า ใคร ส่งมา การพิสูจน์ตัวตนเป็นเรื่องของเลเยอร์ที่สูงกว่า

  • ความมีชีวิตบนการเชื่อมต่อที่เงียบ ถ้าไม่มีฝั่งใดส่งข้อมูลเป็นเวลานาน การเชื่อมต่อยังเปิดอยู่ในทางเทคนิคแต่ไม่สามารถตรวจจับได้ว่าปลายอีกด้านล่มหรือหายไปแล้ว โพรบ Keepalive สามารถเปิดใช้งานบน socket เพื่อแก้ไขปัญหานี้เมื่อจำเป็น

9.10.4. เมื่อใดควรใช้ TCP

TCP เป็นคำตอบที่ถูกต้องสำหรับการสนทนาเกือบทุกประเภทที่มีรูปแบบ "client เปิดการเชื่อมต่อกับ server พวกเขาแลกเปลี่ยนไบต์บางส่วน การเชื่อมต่อปิดเมื่อเสร็จสิ้น" คำขอ HTTP และ HTTPS session SSH คำสั่ง database การถ่ายโอนไฟล์ การอัปโหลดภาพ -- ทั้งหมดผ่าน TCP

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

เมื่อมี port, UDP และ TCP ทั้งหมดอยู่ในมือ เรื่องราวของเลเยอร์การส่งก็จบแล้ว Python API ที่เปิดเผยทั้งสองอยู่ในหน้าถัดไป: อ็อบเจกต์ Socket