17 August 2014

Simple TCP Library - ลากันที TCP ที่ยุ่งยาก

Updated on


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

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

TCP คืออะไร? UDP คืออะไร? ใช้ทำอะไร?

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

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

        ถึงแม้ว่าทั้งสองเครื่องจะต่อเข้า Router ด้วยวิธีที่ต่างกัน แต่ทว่าทั้งสองเครื่องก็ยังคงอยู่ในวงแลนเดียวกัน (เพราะอิงจาก Router เป็นศูนย์กลาง) ดังนั้นทั้งสองเครื่องก็จะสามารถส่งข้อมูลระหว่างกันได้ถึงแม้ว่า Router ตัวนั้นจะต่ออินเตอร์เน็ตหรือไม่ก็ตาม
        จากภาพตัวอย่างจะสมมติว่าคอมพิวเตอร์มี IP Address เป็น 192.168.1.10 และอุปกรณ์แอนดรอยด์เป็น 192.168.1.3 (ส่วน Router จะเป็น 192.168.1.1) ดังนั้นสมมติว่าอุปกรณ์แอนดรอยด์อยากจะส่งข้อมูลไปยังคอมพิวเตอร์ก็จะอิงจาก 192.168.1.10 และถ้าคอมพิวเตอร์อยากจะส่งข้อมูลไปยังอุปกรณ์แอนดรอยด์ก็จะอิง 192.168.1.10 ดังนั้นก็อย่าลืมเช็ค IP Address ของเคร่ืองด้วยล่ะ

        โดยการทำงานแบบนี้เป็นมาตรฐานทั่วไปของวงแลน ไม่ว่าจะเป็นคอมพิวเตอร์ด้วยกันหรือว่าอุปกรณ์แอนดรอยด์ด้วยกันก็ไม่เกี่ยว เพราะสนใจที่ IP Address เป็นหลัก และถ้าไม่มี Router ก็สามารถให้อุปกรณ์แอนดรอยด์หรือคอมพิวเตอร์ปล่อย WiFi Hotspot เพื่อทำวงแลนส่วนตัวก็ได้นะเออ

        หมายเหตุ - อธิบายสำหรับคนที่ไม่รู้เรื่องอะไรเลย เพราะมีผู้ที่หลงเข้ามาอ่านที่จะทำเรื่อง TCP แต่ทว่าไม่ได้ศึกษาข้อมูลมาก่อนเลย (มีจริงๆนะ) ก็เลยอธิบายคร่าวๆเผื่อไว้ให้

แล้ว TCP คืออะไร ใช่ TCP/IP หรือป่าว ?

        TCP ก็เป็นส่วนหนึ่งของคำว่า TCP/IP นั่นแหละ เพราะว่า IP หรือ Internet Protocol Suite มี TCP เป็นส่วนหนึ่งในนั้น บ่อยครั้งจึงเรียกกันว่า TCP/IP และเรียก TCP ก็ไม่ผิด โดย TCP มีชื่อเต็มๆว่า Transmission Control Protocol เป็นการทำงานหนึ่งที่อยู่ใน Transport Layer ของ Network ส่วน IP จะอยู่ใน Internet Layer (อยากรู้ก็ไปศึกษาต่อยอดเอานะ คีย์เวิร์ดคือ "OSI Model")

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

        ที่บอกว่าคอยจัดการเรื่องการส่งข้อมูลที่ว่า เจ้าของบล็อกไม่อยากให้ผู้ที่หลงเข้ามาอ่านมองข้อมูลว่าเป็นไฟล์ภาพ ไฟล์งานหรืออื่นๆ แต่จะให้มองลึกไปกว่านั้น นั่นก็คือข้อมูลระดับ byte เพราะในการทำงาน TCP จะแปลงข้อมูลให้เป็นระดับ Byte แล้วค่อยส่งนั่นเอง ดังนั้นไม่ว่าจะไฟล์ภาพหรือแม้แต่ String ASCII ก็จะถูกแปลงเป็น Byte แล้วค่อยส่งไปเครื่องปลายทาง

รู้ไปทำไมว่ามันส่งในระดับ Byte?

        มีคนเคยถามเจ้าของบล็อกแบบนี้นะเออ... ไหนๆก็พูดเรื่องนี้แล้วก็ขออธิบายทิ้งไว้เลยละกัน

        การที่รู้ว่าส่งข้อมูลระดับ Byte จะทำให้เห็นภาพและประยุกต์ได้ง่ายขึ้น เช่น อยากจะส่งข้อมูล String ง่ายๆอย่าง Hello World ก็จะถูกแปลงเป็น 0x48(H) 0x65(e) 0x6c(l) 0x6c(l) 0x6f(o) 0x20( ) 0x57(W) 0x6f(o) 0x72(r) 0x6c(l) 0x64(d) จะเห็นว่า Byte มีหลายตัวด้วยกัน (ขึ้นอยู่กับข้อมูล) ดังนั้นต่อไปจะเรียกว่า Byte Array

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

        "หัวใจสำคัญอยู่ที่ Byte Array"

        ซึ่งข้อมูลทุกตัวสามารถแปลงเป็น Byte Array ได้อยู่แล้ว

String message = "Hello World";
byte[] messageData = message.getBytes();

Bitmap bitmap = ...
ByteArrayOutputStream bos= new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.JPG, 100, bos);
byte[] byteArray = bos.toByteArray();

        แต่ถ้าไม่รู้เรื่องเลยก็จะได้แต่ถามว่า "ส่ง String ผ่าน TCP ยังไง?" พอทำได้แล้ว แต่อยากส่งเป็นไฟล์ภาพบ้าง ก็จะมาถามต่อว่า "ส่งภาพผ่าน TCP ยังไง?" แล้วคำถามต่อๆกันไปก็จะตามมา "ส่งไฟล์เพลงผ่าน TCP ยังไง?" "ส่งตัวเลขผ่าน TCP ยังไง"

        พอนึกภาพออกเนอะ...

แล้ว UDP ล่ะ?

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

        สำหรับ UDP ย่อมาจาก User Datagram Protocol ทำหน้าที่เหมือนกับ TCP นั่นแหละ อยู่ใน Transport Layer เหมือนกัน แต่ทว่า UDP จะเน้นไปที่ข้อมูลขนาดเล็ก ไม่ใหญ่มาก เน้นความไวเป็นหลัก แต่ข้อมูลมีโอกาสผิดเพี้ยนไปได้ เพราะไม่มีการเช็คข้อมูล และข้อมูลอาจจะสูญหายระหว่างทางก็ได้ (ถ้าสัญญาณไม่คงที่)

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

ปกติ TCP เอาไปใช้ทำอะไรกัน?

        พูดสั้นๆง่ายๆก็คือ "ส่งข้อมูล" นั่นแหละ แต่ทว่าส่งข้อมูลแล้วเอาไปทำอะไรต่อก็อีกเรื่องหนึ่ง อย่างเช่นที่นิยมกันก็พวก ควบคุมบางอย่างผ่าน WiFi ยกตัวอย่างเช่น เอาบอร์ด Arduino ติดมอเตอร์ทำเป็นหุ่นยนต์เคลื่อนที่ได้ เวลาสั่งก็สั่งผ่าน WiFi เป็นต้น โดยการทำงานในส่วน TCP ก็มีแค่ส่งข้อมูลเท่านั้น โดยนิยมเป็น String ง่ายๆ เช่น ส่ง F, B, R และ L เป็นต้น ที่ปลายทางก็คอยเช็คว่าข้อมูลเป็นอะไรแล้วทำงานตามที่กำหนดไว้นั่นเอง

เข้าสู่เนื้อหาหลักของบทความนี้ซะที

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

        ก่อนอื่นต้องเข้าใจว่า TCP ไม่ใช่ว่าอยู่ๆนึกจะส่งก็ส่งได้เลย เพราะปลายทางก็ต้องอยู่ในสถานะที่พร้อมรับข้อมูลด้วย โดยฝั่งต้นทางจะขอเรียกว่า Client และปลายทางเรียกว่า Server ละกัน
        ดังนั้นก่อนที่จะส่งข้อมูล ฝั่ง Server ก็จะต้องอยู่ในสถานะพร้อมรับข้อมูลด้วย (ต่อไปจะขอเรียกว่า "เปิด Server" ละกันนะ)  โดยเปิดทิ้งไว้เพื่อรอให้ฝั่ง Client ส่งข้อมูลมาได้ แต่ถ้าฝั่ง Server ปิดอยู่ก็จะทำให้ Client ส่งข้อมูลมาไม่ได้นั่นเอง

        เอ๊ะ นี่มัน One Way นี่นา? แล้วถ้าอยากให้ Server ส่งข้อมูลกลับมาที่ Client ล่ะ?

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

        แต่อย่างที่บอก ถ้าผู้ที่หลงเข้ามาอ่านเคยเขียนโค๊ดแบบนี้มาก่อนจะรู้ว่าการที่ต้องเขียนเปิด Server มันยุ่งยากมาก ซึ่งในไลบรารีของเจ้าของบล็อกก็ได้จัดการไว้ให้เรียบร้อยแล้ว ดังนั้นจึงบอกลาปัญหาวุ่นวายได้เลย

public final int TCP_PORT = 21111;

SimpleTCPServer server;

protected void onCreate(Bundle savedInstanceState) {
    ...
  
    server = new SimpleTCPServer(TCP_PORT); 

    ...
}
 
public void onResume() {
    super.onResume();
    server.start();
}
 
public void onStop() {
    super.onStop();
    server.stop();
}

        เท่านี้ TCP ก็พร้อมใช้งานและ Server ก็เปิดรอรับข้อมูลเรียบร้อยแล้ว ง่ายใช่มั้ยล่ะ! นี่ล่ะตัวอย่างการใช้งานใน Simple TCP Library


        กลับมาดูเรื่อง TCP กันต่อนะ โดยจะเจาะลึกไปในตอนที่ Server รับข้อมูล เพราะว่า "Client ส่งข้อมูลไปยัง Server" ดูเหมือนจะเข้าใจได้ง่าย แต่กลับไม่รู้ว่าจริงๆแล้วเกิดอะไรขึ้นในระหว่างนั้นกันแน่

        นอกจากการเปิด-ปิด Server แล้ว ยังมีเรื่องของการเชื่อมต่อระหว่าง Client กับ Server อยู่ด้วย เพราะก่อนที่จะส่งข้อมูลให้ Server นั้น ฝั่ง Client จะทำการเชื่อมต่อกับ Server เสียก่อน (ทำให้รู้ได้ว่าปลายทางมีอยู่จริงหรือไม่)  โดยเจ้าของบล็อกจะเรียกสถานะการเชื่อมต่อนี้เป็น Close และ Open

        เมื่อปลายทางมีอยู่จริงและสามารถเชื่อมต่อได้ สถานะการเชื่อมต่อของ Server จะเป็น Open

        เมื่อเชื่อมต่อได้แล้วก็จะเริ่มทำการส่งข้อมูลจนเสร็จ

        เมื่อส่งข้อมูลเสร็จแล้วฝั่ง Client ก็จะหยุดการเชื่อมต่อทันที ทำให้สถานะเป็น Close ดังเดิม

        นี่คือ Concept คร่าวๆที่ผู้ที่หลงเข้ามาอ่านส่วนใหญ่ใช้งานกัน ดังนั้นเมื่อส่งข้อมูลหนึ่งชุด การเชื่อมต่อที่ว่านี้ก็จะ Open แล้วก็ Close เมื่อเสร็จ ถ้าส่งข้อมูลหลายๆชุด ก็จะ Open และ Close สลับไปมาเรื่อยๆ แต่ทว่าเรื่องการเชื่อมต่อที่ว่านี้ ผู้ที่หลงเข้ามาอ่านสามารถควบคุมได้ด้วยนะเอ้อ

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

        แบบไหนดีกว่ากัน?

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


        เจ้าของบล็อกจึงทำให้ไลบรารีมีคลาสหลักๆสองแบบด้วยกัน สำหรับใช้งานคนละแบบ โดยที่การส่งแบบปกติจะใช้ชื่อว่า Simple TCP ซึ่งเป็นการส่งข้อมูลเสร็จก็หยุดเชื่อมต่อทันที และการเปิด Connection ค้างไว้แล้วส่งข้อมูลเรื่อยๆจะตั้งชื่อไว้ว่า Continuous TCP

        ทั้งสองคลาสหลักนี้มีการทำงานต่างกันเล็กน้อย แต่การใช้งานก็คล้ายๆกัน โดย Simple TCP จะมีคลาสย่อยอีกสองตัวคือ SimpleTcpClient กับ SimpleTcpServer เอาไว้กำหนดได้ว่าจะให้เครื่องนั้นๆทำงานเป็น Client หรือ Server หรือว่าทั้งคู่เลย

        ในบทตวามนี้จะยังไม่อธิบายการใช้งานนะ เพราะว่าเดี๋ยวจะยาวเกินไปเลยขอแบ่งบทความย่อยอีกสองส่วนเพื่ออธิบายการทำงานของ Simple TCP กับ Continuous TCP และสามารถเข้าไปดาวน์โหลด SimpleTCPLibrary กันได้ที่ Android-SimpleTCPLibrary [GitHub]

        และถ้าต้องการเขียนโปรแกรมบน Arduino เพื่อให้รองรับ TCP เจ้าของบล็อกมีไลบรารีไว้แล้ว แต่ว่าใช้กับโมดูลที่ชื่อว่า WiFly นะ สามารถดาวน์โหลดได้ที่ WiFlyTCP [GitHub]

        ส่วนบทความอธิบายคลาสทั้งสอง Coming Soon นะคร้าบบบบ