กลับมาอีกครั้งหลังจากหยุดไปนาน จากบทความที่ผ่านมาผมได้แนะนำการเขียนโปรแกรมเครือข่ายโดยใช้ซ็อกเก็ตซึ่งทำงานกับโปรโตคอลทีซีพี (TCP) แต่จริง ๆ แล้วโปรโตคอลบนระดับชั้นทรานสปอร์ต (transport layer) ยังมีโปรโตคอลอีกตัวหนึ่งคือยูดีพี (UDP) ซึ่งเราก็สามารถเขียนโปรแกรมซ็อกเก็ตที่ทำงานกับโปรโตคอลยูดีพีได้ด้วย แต่ก่อนจะไปเขียนโปรแกรมกันในบทความนี้จะขอพูดถึงความแตกต่างระหว่างโปรโตคอลทั้งสองตัวอย่างคร่าว ๆให้ฟังกันก่อนนะครับ
เริ่มจากโปรโตคอลทีซีพีก่อนแล้วกันนะครับ โปรโตคอลนี้เป็นโปรโตคอลที่เรียกว่าเป็นแบบต้องมีการเชื่อมต่อ (connection-oriented) นั่นคือก่อนที่โปรแกรมจะพูดคุยกันได้ โปรแกรมที่จะพูดคุยกันต้องสร้างการสื่อสารระหว่างกันให้ได้เสียก่อน เปรียบเหมือนการพูดคุยกันผ่านทางโทรศัพท์ ซึ่งต้องมีการโทรหากันให้ติดและอีกฝ่ายรับโทรศัพท์ก่อนจึงจะพูดคุยกันได้ อีกประการหนึ่งคือโปรโตคอลทีซีพีเป็นโปรโตคอลที่มีความน่าเชื่อถือในการสื่อสาร กล่าวโดยคร่าว ๆ คือมันจะช่วยดูแลให้การรับส่งข้อมูลระหว่างโปรแกรมที่กำลังติดต่อกันเป็นได้อย่างถูกต้อง ซึ่งรายละเอียดว่ามันทำอย่างไรนั้นก็ขอให้ไปอ่านเพิ่มเติมเอานะครับในเรื่องการสื่อสารข้อมูลและระบบเครือข่าย
ส่วนยูดีพีนั้นเป็นโปรโตคอลแบบที่ไม่ต้องมีการเชื่อมต่อก่อน (conectionless) คือก่อนที่โปรแกรมทั้งสองฝ่ายจะพูดคุยกันไม่จำเป็นต้องสร้างการเชื่อมต่อกันให้ได้ก่อนเหมือนกับที่ทีซีพีต้องทำ ถ้าจะเปรียบการสื่อสารประเภทนี้กับชีวิตประจำวันของเราก็คือการส่งจดหมายทางไปรษณีย์ครับ ถ้าคุณจะส่งจดหมายถึงเพื่อนคุณ คุณก็ลงมือเขียนได้เลยและเอาไปหยอดตู้ไปรษณีย์โดยคุณไม่ต้องติดต่อบอกเพื่อนคุณก่อนว่าคุณจะส่งจดหมายไปหา โปรโตคอลยูดีพีจัดเป็นโปรโตคอลที่ไม่น่าเชื่อถือเมื่อเทียบกับทีซีพี กล่าวคือมันไม่รับประกันความถูกต้องในการรับส่งข้อมูล อาจมีข้อมูลหายได้ในระหว่างการรับส่ง หรือแม้แต่ข้อมูลอาจถูกส่งมาในลำดับที่ไม่ถูกต้อง
ถ้าจะเปรียบเทียบให้เห็นขัดเจนก็ขอให้นึกถึงว่าการพูดคุยผ่านโทรศัพท์มีความน่าเชื่อถือกว่าการส่งจดหมาย เพราะมันรับประกันว่าผู้ที่พูดคุยกับเราจะได้รับข้อความจากเราแน่ หรือในกรณีที่เราได้ยินสิ่งที่เขาพูดไม่ชัดเราสามารถบอกให้เขาพูดใหม่ได้ แต่การส่งจดหมายมีโอกาสที่จดหมายจะส่งไปไม่ถึงผู้รีบ หรือผู้รับอาจเข้าใจผิดจากข้อความที่เราเขียนได้
พูดมาถึงตรงนี้หลายคนอาจบอกว่างั้นไม่ต้องสนใจไม่ต้องใช้ยูดีพีแล้วกัน มันจะมีไปทำไมเนี่ย คำตอบก็คือโปรโตคอลยูดีพีทำงานเร็วกว่าทีซีพีครับ เพราะมันไม่ต้องตรวจสอบอะไรมากมายเหมือนทีซีพี ดังนั้นยูดีพีจะเหมาะกับงานที่ไม่ต้องส่งข้อมูลมากนัก เพราะข้อมูลที่มีขนาดไม่ใหญ่โอกาสที่จะผิดพลาดก็มีน้อย หรือแม้แต่งานที่ต้องส่งข้อมูลขนาดใหญ่แต่ต้องการความรวดเร็วอย่างวีดีโอสตรีมมิง ก็ใช้โปรโตคอลยูดีพีนี้ในการรับส่งข้อมูลเช่นกัน เพราะมันต้องการความรวดเร็วซึ่งทีซีพีไม่สามารถทำได้ แต่เรื่องความน่าเชื่อถือนั้นจะมีการใช้โปรโตคอลตัวอื่นมาทำงานร่วมกับยูดีพี
โอเคครับ ถึงตรงนี้ก็คงเข้าใจความแตกต่างระหว่างทีซีพีและยูดีพีอย่างคร่าว กันแล้วนะครับ บทความต่อไปจะได้พูดถึงการเขียนโปรแกรมกันนะครับ
แสดงบทความที่มีป้ายกำกับ Data Communication แสดงบทความทั้งหมด
แสดงบทความที่มีป้ายกำกับ Data Communication แสดงบทความทั้งหมด
วันศุกร์ที่ 1 กุมภาพันธ์ พ.ศ. 2562
วันอังคารที่ 24 ธันวาคม พ.ศ. 2556
การเขียนโปรแกรมเครือข่ายด้วย Socket ตอนที่ 6 : TCP Concurrent Server
จากโปรแกรมเซิร์ฟเวอร์ในตอนที่ 3 จัดเป็นเซิร์ฟเวอร์แบบ iterative นั่นคือให้บริการไคลเอนต์ได้ครั้งละหนึ่งตัว ถ้ามีมากกว่าหนึ่งตัวตัวที่เข้ามาทีหลังจะต้องเข้าคิวรอจนกว่าจะถึงคิวตัวเอง ซึ่งเซิร์ฟเวอร์แบบนี้ไม่เหมาะสมกับงานที่ไคลเอนต์และเซิร์ฟเวอร์ต้องติดต่อกันเป็นเวลานาน ลองนึกภาพว่าถ้า Gmail ให้บริการแบบนี้ดูนะครับว่ามันจะเป็นยังไง ดังนั้นงานแบบนี้จะต้องเขียนเซิร์ฟเวอร์ที่ทำงานแบบพร้อมกัน (Concurrent) ครับ
ซึ่งการเขียนก็ไม่ได้ยากอะไร แนวคิดที่ผ่านมาในตอนต่าง ๆ ยังใช้ได้หมด แต่สิ่งที่เราจะเพิ่มเข้ามาคือเราจะนำเธรด (Thread) มาใช้ครับ หลักการก็คือหลังจากที่เซิร์ฟเวอร์รับการติดต่อจากไคลเอนต์เข้ามาแล้วแทนที่จะไปให้บริการไคลเอนต์เองเหมือนที่ผ่านมา ก็จะสร้างเธรด ขึ้นมาให้บริการไคลเอนต์แต่ละตัว ไปลองดูโค้ดกันครับ
สิ่งที่แตกต่างจากโปรแกรม iterative server ก็คือ คลาส EchoThread ในบรรทัดที่ 30-68 ซึ่งเป็นคลาสที่เขียนขึ้นเพื่อเป็นส่วนของการให้บริการไคลเอนต์ในการแปลงตัวอักษรตัวเล็กเป็นตัวใหญ่ จะเห็นว่างานบริการไคลเอนต์ที่เคยอยู่ในตัวโปรแกรม TCPServer จะถูกนำมาเขียนในคลาสนี้ บรรทัดที่ 21 และ 22 ในคลาส TCPConcurrent จะสร้างออบเจกต์ของ EchoThread และสั่งให้เทรดเริ่มทำงาน ให้สังเกตว่ามีการส่งซ็อกเก็ตเชื่อมต่อที่สร้างขึ้นมาให้ออบเจกต์ของคลาส EchoThread ผ่านทางคอนสตรักเตอร์ หลังจากสร้างออบเจกต์ และสั่งให้เธรดเริ่มทำงานในการบริการไคลเอนต์แล้ว โปรแกรมเซิร์ฟเวอร์ก็สามารถกลับไปรับการติดต่อจากไคลเอนต์ตัวถัดไปได้ โดยตอนนี้การบริการไคลเอนต์จะเป็นหน้าที่ของเธรดที่ถูกสร้างขึ้น และถ้ามีการติดต่อเข้ามาก็ทำแบบเดียวกันคือสร้างเธรดอีกตัวหนึ่งไปให้บริการไคลเอนต์
ในส่วนของโปรแกรมไคลเอนต์ เราสามารถใช้โปรแกรมเดิมได้โดยไม่ต้องแก้ไข ในการรันโปรแกรมเพื่อทดสอบก็ทำแบบเดียวกับการรันโปรแกรมทดสอบที่อธิบายไปแล้วในตอนที่แล้วนะครับ ซึ่งถ้าทุกอย่างถูกต้องโปรแกรมเซิร์ฟเวอร์จะตอบสนองกับไคลเอนต์ทุกตัวที่ติดต่อเข้ามาโดยไม่ต้องรอการเรียง
ลำดับ
สำหรับใครที่ไม่อยากพิมพ์โค้ดเองสามารถดาวน์โหลดได้จาก github ครับ
สำหรับใครที่ยังไม่เข้าใจว่าจะรันโปรแกรมยังไงดูได้จากวีดีโอนี้ครับ
สุดท้ายสำหรับวันนี้ก็ขอสุขสันต์วันคริสต์มาสมายังทุกคนครับ...
ซึ่งการเขียนก็ไม่ได้ยากอะไร แนวคิดที่ผ่านมาในตอนต่าง ๆ ยังใช้ได้หมด แต่สิ่งที่เราจะเพิ่มเข้ามาคือเราจะนำเธรด (Thread) มาใช้ครับ หลักการก็คือหลังจากที่เซิร์ฟเวอร์รับการติดต่อจากไคลเอนต์เข้ามาแล้วแทนที่จะไปให้บริการไคลเอนต์เองเหมือนที่ผ่านมา ก็จะสร้างเธรด ขึ้นมาให้บริการไคลเอนต์แต่ละตัว ไปลองดูโค้ดกันครับ
- //TCPConcurrentServer.java
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class TCPConcurrentServer {
- public static void main(String argv[]) {
- String clientSentence;
- String capitalizedSentence;
- ServerSocket welcomeSocket = null;
- try {
- welcomeSocket = new ServerSocket(6789);
- }
- catch (IOException e) {
- System.out.println("Cannot create a welcome socket");
- System.exit(1);
- }
- while(true) {
- try {
- System.out.println("The server is waiting ");
- Socket connectionSocket = welcomeSocket.accept();
- EchoThread echoThread = new EchoThread(connectionSocket);
- echoThread.start();
- }
- catch (IOException e) {
- System.out.println("Cannot create this connection");
- }
- }
- }
- }
- //EchoThread.java
- import java.io.*;
- import java.net.*;
- import java.util.*;
- public class EchoThread extends Thread {
- private Socket connectionSocket;
- public EchoThread(Socket connectionSocket) {
- this.connectionSocket = connectionSocket;
- }
- public void run() {
- Scanner inFromClient = null;
- DataOutputStream outToClient = null;
- try {
- inFromClient = new Scanner(connectionSocket.getInputStream());
- outToClient =
- new DataOutputStream(connectionSocket.getOutputStream());
- String clientSentence = inFromClient.nextLine();
- String capitalizedSentence = clientSentence.toUpperCase() + '\n';
- outToClient.writeBytes(capitalizedSentence);
- }
- catch (IOException e) {
- System.err.println("Closing Socket connection");
- }
- finally {
- try {
- if (inFromClient != null)
- inFromClient.close();
- if (outToClient != null)
- outToClient.close();
- if (connectionSocket != null)
- connectionSocket.close();
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
สิ่งที่แตกต่างจากโปรแกรม iterative server ก็คือ คลาส EchoThread ในบรรทัดที่ 30-68 ซึ่งเป็นคลาสที่เขียนขึ้นเพื่อเป็นส่วนของการให้บริการไคลเอนต์ในการแปลงตัวอักษรตัวเล็กเป็นตัวใหญ่ จะเห็นว่างานบริการไคลเอนต์ที่เคยอยู่ในตัวโปรแกรม TCPServer จะถูกนำมาเขียนในคลาสนี้ บรรทัดที่ 21 และ 22 ในคลาส TCPConcurrent จะสร้างออบเจกต์ของ EchoThread และสั่งให้เทรดเริ่มทำงาน ให้สังเกตว่ามีการส่งซ็อกเก็ตเชื่อมต่อที่สร้างขึ้นมาให้ออบเจกต์ของคลาส EchoThread ผ่านทางคอนสตรักเตอร์ หลังจากสร้างออบเจกต์ และสั่งให้เธรดเริ่มทำงานในการบริการไคลเอนต์แล้ว โปรแกรมเซิร์ฟเวอร์ก็สามารถกลับไปรับการติดต่อจากไคลเอนต์ตัวถัดไปได้ โดยตอนนี้การบริการไคลเอนต์จะเป็นหน้าที่ของเธรดที่ถูกสร้างขึ้น และถ้ามีการติดต่อเข้ามาก็ทำแบบเดียวกันคือสร้างเธรดอีกตัวหนึ่งไปให้บริการไคลเอนต์
ในส่วนของโปรแกรมไคลเอนต์ เราสามารถใช้โปรแกรมเดิมได้โดยไม่ต้องแก้ไข ในการรันโปรแกรมเพื่อทดสอบก็ทำแบบเดียวกับการรันโปรแกรมทดสอบที่อธิบายไปแล้วในตอนที่แล้วนะครับ ซึ่งถ้าทุกอย่างถูกต้องโปรแกรมเซิร์ฟเวอร์จะตอบสนองกับไคลเอนต์ทุกตัวที่ติดต่อเข้ามาโดยไม่ต้องรอการเรียง
ลำดับ
สำหรับใครที่ไม่อยากพิมพ์โค้ดเองสามารถดาวน์โหลดได้จาก github ครับ
สำหรับใครที่ยังไม่เข้าใจว่าจะรันโปรแกรมยังไงดูได้จากวีดีโอนี้ครับ
สุดท้ายสำหรับวันนี้ก็ขอสุขสันต์วันคริสต์มาสมายังทุกคนครับ...
วันเสาร์ที่ 7 ธันวาคม พ.ศ. 2556
เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 5: การรันโปรแกรมเพื่อทดสอบการทำงาน
หลังจากที่เราเขียนโปรแกรมเซิร์ฟเวอร์และไคลเอนต์เรียบร้อยแล้ว ในตอนนี้เราจะมาลองรันโปรแกรมเพื่อทดสอบการทำงานกันดู โดยจะขอยกตัวอย่างการรันโปรแกรมบนระบบปฏิบัติการไมโครซอฟท์วินโดวส์ นะครับ โดยเราจะรันทั้งสองโปรแกรมบนเครื่องเดียวกัน หลังจากคอมไฟล์ทั้งโปรแกรมเซิร์ฟเวอร์และไคลเอนต์แล้ว เราจะเริ่มจากรันโปรแกรมเซิร์ฟเวอร์ดังนี้ start java TCPServer
เราใช้คำสั่ง start นำหน้าคำสั่งที่ใช้รันโปรแกรมเซิร์ฟเวอร์เพราะเราต้องการจะเปิดหน้าจอ command prompt ชึ้นมาใหม่เพื่อใช้รันโปรแกรมเซิร์ฟเวอร์ โดนหน้าจอ command prompt เดิมจะได้ใช้สำหรับรันโปรแกรมไคลเอนต์ต่อไป
การรันไคลเอนต์ใช้คำสั่งดังนี้ java TCPClient
หลังจากนั้นเราสามารถป้อนข้อความอะไรก็ได้ จากนั้นโปรแกรมฝั่งเซิร์ฟเวอร์จะส่งข้อความที่เราป้อนเข้าไปกลับมาเป็นตัวอักษรภาษาอังกฤษตัวใหญ่ หลังจากรันโปรแกรมแล้วโปรแกรมฝั่งไคลเอนต์จะจบการทำงาน ส่วนโปรแกรมฝั่งเซิร์ฟเวอร์จะรอรับการติดต่อจากไคลเอนต์ต่อไป นั่นคือเราสามารถรันโปรแกรมฝั่งไคลเอนต์เพื่อติดต่อเข้าไปอีกกี่รอบก็ได้ ถ้าต้องการจบการทำงานของโปรแกรมฝั่งเซิร์ฟเวอร์ให้ไปที่หน้าต่างของโปรแกรมเซิร์ฟเวอร์และกด Ctrl-C
อีกหนึ่งจุดที่อยากให้ทดลองคือ เซิร์ฟเวอร์ที่เราเขียนขึ้นจากตัวอย่างที่แล้วเป็น iterative server คือจะให้บริการไคลเอนต์ได้ทีละหนึ่งตัว ตัวที่ติดต่อเข้ามาภายหลังจะต้องเข้าคิวรอไว้ ดังนั้นเพื่อให้เห็นภาพให้ลองรันไคลเอนต์สักสองตัว โดยเปิดหน้าต่าง command prompt ขึ้นมาสองหน้าต่าง แต่ละหน้าต่างก็รันโปรแกรมไคลเอนต์ จากนั้นให้ลองดูว่าถ้าป้อนสตริงจากไคลเอนต์ที่รันเป็นตัวที่สองก่อนที่จะป้อนสตริงจากไคลเอนต์ตัวที่หนึ่งจะเกิดอะไรขึ้น
สำหรับใครที่อ่านบทความแล้วยังไม่เข้าใจว่าจะรันโปรแกรมยังไง สามารถดูได้จากวีดีโอต่อไปนี้ครับ
หมายเหตุ:
1. ในกรณีที่รันโปรแกรมเซิร์ฟเวอร์ไม่ได้ส่วนใหญ่ปัญหาจะเกิดจากการที่ใช้หมายเลขพอร์ตซ้ำกับโปรแกรมอื่นที่รันอยู่ ให้แก้หมายเลขพอร์ตในโค้ดโปรแกรมของทั้งเซิร์ฟเวอร์และไคลเอนต์ จากนั้นคอมไพล์โปรแกรมและรันใหม่
เราใช้คำสั่ง start นำหน้าคำสั่งที่ใช้รันโปรแกรมเซิร์ฟเวอร์เพราะเราต้องการจะเปิดหน้าจอ command prompt ชึ้นมาใหม่เพื่อใช้รันโปรแกรมเซิร์ฟเวอร์ โดนหน้าจอ command prompt เดิมจะได้ใช้สำหรับรันโปรแกรมไคลเอนต์ต่อไป
การรันไคลเอนต์ใช้คำสั่งดังนี้ java TCPClient
อีกหนึ่งจุดที่อยากให้ทดลองคือ เซิร์ฟเวอร์ที่เราเขียนขึ้นจากตัวอย่างที่แล้วเป็น iterative server คือจะให้บริการไคลเอนต์ได้ทีละหนึ่งตัว ตัวที่ติดต่อเข้ามาภายหลังจะต้องเข้าคิวรอไว้ ดังนั้นเพื่อให้เห็นภาพให้ลองรันไคลเอนต์สักสองตัว โดยเปิดหน้าต่าง command prompt ขึ้นมาสองหน้าต่าง แต่ละหน้าต่างก็รันโปรแกรมไคลเอนต์ จากนั้นให้ลองดูว่าถ้าป้อนสตริงจากไคลเอนต์ที่รันเป็นตัวที่สองก่อนที่จะป้อนสตริงจากไคลเอนต์ตัวที่หนึ่งจะเกิดอะไรขึ้น
สำหรับใครที่อ่านบทความแล้วยังไม่เข้าใจว่าจะรันโปรแกรมยังไง สามารถดูได้จากวีดีโอต่อไปนี้ครับ
หมายเหตุ:
1. ในกรณีที่รันโปรแกรมเซิร์ฟเวอร์ไม่ได้ส่วนใหญ่ปัญหาจะเกิดจากการที่ใช้หมายเลขพอร์ตซ้ำกับโปรแกรมอื่นที่รันอยู่ ให้แก้หมายเลขพอร์ตในโค้ดโปรแกรมของทั้งเซิร์ฟเวอร์และไคลเอนต์ จากนั้นคอมไพล์โปรแกรมและรันใหม่
เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 4: TCP Client ด้วยภาษาจาวา (Java)
หลังจากเขียนโปรแกรมฝั่งเซิร์ฟเวอร์ไปเมื่อตอนที่แล้วคราวนี้มาดูโปรแกรมฝั่งไคลเอนต์บ้าง สำหรับโปรแกรมก็เป็นดังนี้ครับ
บรรทัดที่ 15 เป็นการสร้างซ็อกเก็ตเชื่อมต่อ (Connection Socket) ให้สังเกตว่าเราใช้ชื่อโฮสต์คือ localhost เพราะโปรแกรมตัวอย่างของเราเราจะรันโปรแกรมเซิร์ฟเวอร์และไคลเอนต์บนเครื่องเดียวกัน ในกรณีที่รันโปรแกรมเซิร์ฟเวอร์บนเครื่องอื่น ให้ระบุชื่อหรือหมายเลขไอพีของเครื่องดังกล่าว หมายเลขพอร์ตใช้หมายเลขที่โปรแกรมเซิร์ฟเวอร์รอรับการติดต่ออยู่ ในที่นี้ใช้ 6789 เพราะโปรแกรมเซิร์ฟเวอร์ที่เราเขียนขึ้นในบทความที่แล้วใช้หมายเลขพอร์ตนี้ บรรทัดที่ 16-17 สร้างสตรีมผลลัพธ์สำหรับเขียนข้อมูลลงซ็อกเก็ต บรรทัดที่ 18 สร้างสตรีมเพื่อรับข้อมูลจากซ็อกเก็ต บรรทัดที่ 21 เขียนข้อมูลลงซ็อกเก็ตเพื่อส่งให้เซิร์ฟเวอร์ บรรทัดที่ 22 รอรับข้อมูลที่เซิร์ฟเวอร์จะส่งกลับมา finally clause บรรทัดที่ 28-35 ปิดสตรีมทุกตัว และซ็อกเก็ตเชื่อมต่อ
สำหรับโปรแกรมต้นฉบับ (source code) สามารถโหลดได้จากลิงก์นี้ครับ ถ้าใครโหลดจาก gitlab ไปเมื่อบทความที่แล้ว ก็จะได้โค้ดโปรแกรมไปแล้วนะครับ ไม่ต้องโหลดใหม่ครับ
- import java.io.*;
- import java.net.*;
- import java.util.*;
- class TCPClient {
- public static void main(String argv[]) throws Exception
- {
- String sentence;
- String modifiedSentence;
- Scanner inFromUser = null;
- Socket clientSocket = null;
- DataOutputStream outToServer = null;
- Scanner inFromServer = null;
- try {
- inFromUser = new Scanner(System.in);
- clientSocket = new Socket("localhost", 6789);
- outToServer =
- new DataOutputStream(clientSocket.getOutputStream());
- inFromServer = new Scanner(clientSocket.getInputStream());
- System.out.print("Please enter words: ");
- sentence = inFromUser.nextLine();
- outToServer.writeBytes(sentence + '\n');
- modifiedSentence = inFromServer.nextLine();
- System.out.println("FROM SERVER: " + modifiedSentence);
- }
- catch (IOException e) {
- System.out.println("Error occurred: Closing the connection");
- }
- finally {
- try {
- if (inFromServer != null)
- inFromServer.close();
- if (outToServer != null)
- outToServer.close();
- if (clientSocket != null)
- clientSocket.close();
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
บรรทัดที่ 15 เป็นการสร้างซ็อกเก็ตเชื่อมต่อ (Connection Socket) ให้สังเกตว่าเราใช้ชื่อโฮสต์คือ localhost เพราะโปรแกรมตัวอย่างของเราเราจะรันโปรแกรมเซิร์ฟเวอร์และไคลเอนต์บนเครื่องเดียวกัน ในกรณีที่รันโปรแกรมเซิร์ฟเวอร์บนเครื่องอื่น ให้ระบุชื่อหรือหมายเลขไอพีของเครื่องดังกล่าว หมายเลขพอร์ตใช้หมายเลขที่โปรแกรมเซิร์ฟเวอร์รอรับการติดต่ออยู่ ในที่นี้ใช้ 6789 เพราะโปรแกรมเซิร์ฟเวอร์ที่เราเขียนขึ้นในบทความที่แล้วใช้หมายเลขพอร์ตนี้ บรรทัดที่ 16-17 สร้างสตรีมผลลัพธ์สำหรับเขียนข้อมูลลงซ็อกเก็ต บรรทัดที่ 18 สร้างสตรีมเพื่อรับข้อมูลจากซ็อกเก็ต บรรทัดที่ 21 เขียนข้อมูลลงซ็อกเก็ตเพื่อส่งให้เซิร์ฟเวอร์ บรรทัดที่ 22 รอรับข้อมูลที่เซิร์ฟเวอร์จะส่งกลับมา finally clause บรรทัดที่ 28-35 ปิดสตรีมทุกตัว และซ็อกเก็ตเชื่อมต่อ
สำหรับโปรแกรมต้นฉบับ (source code) สามารถโหลดได้จากลิงก์นี้ครับ ถ้าใครโหลดจาก gitlab ไปเมื่อบทความที่แล้ว ก็จะได้โค้ดโปรแกรมไปแล้วนะครับ ไม่ต้องโหลดใหม่ครับ
วันอังคารที่ 3 ธันวาคม พ.ศ. 2556
เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 3: TCP Server ด้วยภาษาจาวา (Java)
หลังจากเกริ่นนำมาแล้วสองตอน (ตอนที่ 1 และ ตอนที่ 2) และก็ทิ้งช่วงเสียนานเชียวก็ขอกลับมาเขียนต่อตอนที่สามนะครับ ในตอนนี้เราจะมาเริ่มเขียนโปรแกรมกันแล้ว โดยจะเขียนโปรแกรมในฝั่งเซิร์ฟเวอร์กันก่อน อย่างที่ทราบจากตอนที่ผ่านมาว่าโปรแกรมที่จะสื่อสารกันได้จะต้องสร้างซ็อกเก็ต (Socket) ก่อน ในฝั่งของเซิร์ฟเวอร์นั้นจะต้องสร้างซ็อกเก็ตสองตัว ตัวแรกจะขอเรียกว่าเซิฟร์เวอร์ซ็อกเก็ต (Server Socket) ซึ่งมีหน้าที่รอรับการติดต่อจากไคลเอนต์ ส่วนตัวที่สองจะขอเรียกว่าซ็อกเก็ตเชื่อมต่อ ( Connection Socket) ซึ่งจะใช้ในการแลกเปลี่ยนข้อมูลกับไคลเอนต์ ที่ต้องมีซ็อกเก็ตสองตัวก็เพื่อที่จะเตรียมการให้เซิร์ฟเวอร์สามารถรองรับไคลเอนต์ได้หลาย ๆ ตัว พร้อม ๆ กัน (แต่ต้องเขียนโปรแกรมเพิ่มเติมซึ่งจะกล่าวถึงในตอนต่อ ๆ ไปครับ)
สำหรับแนวคิดของการอ่านและเขียนข้อมูลกับซ็อกเก็ตนั้นจะใช้แนวคิดของสตรีม (Stream) ซึ่งก็คือแนวคิดที่จาวา (Java) ใช้ในการรับส่งข้อมูลระหว่างโปรแกรมที่เขียนด้วยภาษาจาวา กับอุปกรณ์รอบข้าง หรือกับไฟล์ ตัวอย่างเช่น เวลาจะพิมพ์ข้อมูลออกมาทางจอภาพเราจะใช้คำสั่ง System.out.println("string"); ประโยคนี้ out คือสตรีมผลลัพธ์ (output stream) ซึ่งอ้างถึงตัวแสดงผลลัพธ์มาตรฐาน ซึ่งก็คือจอภาพนั่นเอง การเขียนโปรแกรมซ็อกเก็ตของภาษาจาวา (Java) ก็จะใช้แนวคิดนี้ เพียงแต่สตรีมที่เราสร้างขึ้น จะเชื่อมโยงกับซ็อกเก็ตแทนที่จะเป็นอุปกรณ์หรือไฟล์
คลาสในภาษาจาวาที่ใช้สร้างเซิฟร์เวอร์ซ็อกเก็ตก็คือ ServerSocket ส่วนคลาสที่ใช้ในการสร้างซ็อกเก็ตเชื่อมต่อก็คือ ConnectionSocket นั่นเอง เอาล่ะครับเพื่อให้เข้าใจการทำงานมาดูตัวอย่างกันดีกว่า สำหรับโปรแกรมเซิร์ฟเวอร์ที่จะเขียนขึ้นมาก็คือเซิร์ฟเวอร์ที่ใช้ในการแปลงข้อความภาษาอังกฤษที่รับเข้ามาจากไคลเอนต์ ให้เป็นตัวอักษรตัวใหญ่ทั้งหมดแล้วส่งกลับไปให้ไคลเอนต์ สำหรับโค้คภาษาจาวา ของโปรแกรมแสดงได้ดังนี้ครับ
สำหรับโปรแกรมนี้ก็มีส่วนที่จะอธิบายดังนี้ครับ บรรทัดที่ 13 เป็นการสร้างเซิร์ฟเวอร์ซ็อกเก็ตโดยใช้หมายเลขพอร์ต 6789 ในการรอรับการติดต่อกับไคลเอนต์ บรรทัดที่ 22 จะเห็นว่ามีการเรียกใช้เมท็อด accept() เมท็อดนี้จะมีผลทำให้โปรแกรมเซิร์ฟเวอร์หยุดรอรับการติดต่อจากไคลเอนต์ เมื่อไคลเอนต์ติดต่อเข้ามาก็จะสร้างซ็อกเก็ตเชื่อมต่อเพื่อใช้ในการแลกเปลี่ยนข้อมูล บรรทัดที่ 23 สร้างสตรีมข้อมูลเข้า (input stream) เพื่ออ่านข้อมูลจากซ็อกเก็ต บรรทัดที่ 24-25 สร้างสตรีมผลลัพธ์ (output stream) เพื่อเขียนข้อมูลลงซ็อกเก็ต บรรทัดที่ 26 หยุดเพื่อรออ่านข้อมูลที่ไคลเอนต์จะส่งผ่านซ็อกเก็ตมา บรรทัดที่ 27 แปลงสตริงที่รับมาให้เป็นตัวอักษรตัวใหญ่ และบรรทัดที่ 28 เขียนข้อมูลผ่านซ็อกเก็ตกลับไปให้ไคลเอนต์ ใน finally clause จะปิด stream ที่สร้างขึ้นมาทั้งหมด และซ็อกเก็ตเชื่อมต่อตัวนี้ เนื่องจากในตัวอย่างนี้เซิฟร์เวอร์จะแปลงสตริงที่ไคลเอนต์ส่งเข้ามา ส่งสตริงที่แปลงกลับไป และตัดการเชื่อมต่อกับไคลเอนต์ทันที และข้อสังเกตที่น่าสนใจก็คือจะเห็นว่าเราให้โปรแกรมนี้ทำงานอยู่ในลูปไม่รู้จบ ซึ่งเป็นธรรมชาติของโปรแกรมเซิร์ฟเวอร์ที่จะทำงานไปเรื่อย ๆ
โปรแกรมเซิร์ฟเวอร์ตัวนี้ให้บริการไคลเอนต์ครั้งละหนึ่งตัว เมื่อให้บริการไคลเอนต์ตัวหนึ่งเสร็จจึงจะไปให้บริการไคลเอนต์ตัวอื่นที่ติดต่อเข้ามาได้ ลักษณะของโปรแกรมเซิร์ฟเวอร์แบบนี้เราจะเรียกว่า iterative server ซึ่งก็คือเซิร์ฟเวอร์ที่ให้บริการไคลเอนต์ได้ครั้งละหนึ่งตัว ถ้ามีไคลเอนต์ตัวอื่นติดต่อเข้ามาขณะที่เซิร์ฟเวอร์กำลังให้บริการไคลเอนต์ตัวอื่นอยู่ ไคลเอนต์ที่ติดต่อเข้ามาก็จะถูกเข้าคิวไว้ เมื่อเซิร์ฟเวอร์ให้บริการไคลเอนต์ตัวนี้เสร็จก็จะกลับไปให้บริการไคลเอนต์ในคิว สำหรับเซิร์เวอร์ที่ให้บริการไคลเอนต์ได้ทีละหลายตัวพร้อมกันที่เรียกว่า concurrent server นั้น ทำได้โดยใช้เธรด (thread) ซึ่งจะกล่าวถึงในตอนต่อ ๆ ไป
สำหรับโปรแกรมต้นฉบับ (source code) สามารถโหลดได้จากลิงก์นี้ครับ ซึ่งจะได้โปรแกรมทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์เลย แต่บล็อกเกี่ยวกับไคลเอนต์โปรแกรมจะอยู่ในตอนหน้านะครับ
เมื่อรันโปรแกรมนี้ โปรแกรมจะแสดงสถานะว่าโปรแกรมเซิร์ฟเวอร์กำลังทำงานอยู่ ซึ่งในโปรแกรมเซิร์ฟเวอร์จริง ๆ ไม่จำเป็นต้องมีการทำงานส่วนนี้นะครับ
สำหรับแนวคิดของการอ่านและเขียนข้อมูลกับซ็อกเก็ตนั้นจะใช้แนวคิดของสตรีม (Stream) ซึ่งก็คือแนวคิดที่จาวา (Java) ใช้ในการรับส่งข้อมูลระหว่างโปรแกรมที่เขียนด้วยภาษาจาวา กับอุปกรณ์รอบข้าง หรือกับไฟล์ ตัวอย่างเช่น เวลาจะพิมพ์ข้อมูลออกมาทางจอภาพเราจะใช้คำสั่ง System.out.println("string"); ประโยคนี้ out คือสตรีมผลลัพธ์ (output stream) ซึ่งอ้างถึงตัวแสดงผลลัพธ์มาตรฐาน ซึ่งก็คือจอภาพนั่นเอง การเขียนโปรแกรมซ็อกเก็ตของภาษาจาวา (Java) ก็จะใช้แนวคิดนี้ เพียงแต่สตรีมที่เราสร้างขึ้น จะเชื่อมโยงกับซ็อกเก็ตแทนที่จะเป็นอุปกรณ์หรือไฟล์
คลาสในภาษาจาวาที่ใช้สร้างเซิฟร์เวอร์ซ็อกเก็ตก็คือ ServerSocket ส่วนคลาสที่ใช้ในการสร้างซ็อกเก็ตเชื่อมต่อก็คือ ConnectionSocket นั่นเอง เอาล่ะครับเพื่อให้เข้าใจการทำงานมาดูตัวอย่างกันดีกว่า สำหรับโปรแกรมเซิร์ฟเวอร์ที่จะเขียนขึ้นมาก็คือเซิร์ฟเวอร์ที่ใช้ในการแปลงข้อความภาษาอังกฤษที่รับเข้ามาจากไคลเอนต์ ให้เป็นตัวอักษรตัวใหญ่ทั้งหมดแล้วส่งกลับไปให้ไคลเอนต์ สำหรับโค้คภาษาจาวา ของโปรแกรมแสดงได้ดังนี้ครับ
- import java.io.*;
- import java.net.*;
- import java.util.*;
- class TCPServer {
- public static void main(String argv[]) {
- String clientSentence;
- String capitalizedSentence;
- ServerSocket welcomeSocket = null;
- Socket connectionSocket = null;
- Scanner inFromClient = null;
- DataOutputStream outToClient = null;
- try {
- welcomeSocket = new ServerSocket(6789);
- }
- catch (IOException e) {
- System.out.println("Cannot create a welcome socket");
- System.exit(1);
- }
- while(true) {
- try {
- System.out.println("The server is waiting ");
- connectionSocket = welcomeSocket.accept();
- inFromClient = new Scanner(connectionSocket.getInputStream());
- outToClient =
- new DataOutputStream(connectionSocket.getOutputStream());
- clientSentence = inFromClient.nextLine();
- capitalizedSentence = clientSentence.toUpperCase() + '\n';
- outToClient.writeBytes(capitalizedSentence);
- }
- catch (IOException e) {
- System.out.println("Error cannot create this connection");
- }
- finally {
- try {
- if (inFromClient != null)
- inFromClient.close();
- if (outToClient != null)
- outToClient.close();
- if (connectionSocket != null)
- connectionSocket.close();
- }
- catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- }
- }
สำหรับโปรแกรมนี้ก็มีส่วนที่จะอธิบายดังนี้ครับ บรรทัดที่ 13 เป็นการสร้างเซิร์ฟเวอร์ซ็อกเก็ตโดยใช้หมายเลขพอร์ต 6789 ในการรอรับการติดต่อกับไคลเอนต์ บรรทัดที่ 22 จะเห็นว่ามีการเรียกใช้เมท็อด accept() เมท็อดนี้จะมีผลทำให้โปรแกรมเซิร์ฟเวอร์หยุดรอรับการติดต่อจากไคลเอนต์ เมื่อไคลเอนต์ติดต่อเข้ามาก็จะสร้างซ็อกเก็ตเชื่อมต่อเพื่อใช้ในการแลกเปลี่ยนข้อมูล บรรทัดที่ 23 สร้างสตรีมข้อมูลเข้า (input stream) เพื่ออ่านข้อมูลจากซ็อกเก็ต บรรทัดที่ 24-25 สร้างสตรีมผลลัพธ์ (output stream) เพื่อเขียนข้อมูลลงซ็อกเก็ต บรรทัดที่ 26 หยุดเพื่อรออ่านข้อมูลที่ไคลเอนต์จะส่งผ่านซ็อกเก็ตมา บรรทัดที่ 27 แปลงสตริงที่รับมาให้เป็นตัวอักษรตัวใหญ่ และบรรทัดที่ 28 เขียนข้อมูลผ่านซ็อกเก็ตกลับไปให้ไคลเอนต์ ใน finally clause จะปิด stream ที่สร้างขึ้นมาทั้งหมด และซ็อกเก็ตเชื่อมต่อตัวนี้ เนื่องจากในตัวอย่างนี้เซิฟร์เวอร์จะแปลงสตริงที่ไคลเอนต์ส่งเข้ามา ส่งสตริงที่แปลงกลับไป และตัดการเชื่อมต่อกับไคลเอนต์ทันที และข้อสังเกตที่น่าสนใจก็คือจะเห็นว่าเราให้โปรแกรมนี้ทำงานอยู่ในลูปไม่รู้จบ ซึ่งเป็นธรรมชาติของโปรแกรมเซิร์ฟเวอร์ที่จะทำงานไปเรื่อย ๆ
โปรแกรมเซิร์ฟเวอร์ตัวนี้ให้บริการไคลเอนต์ครั้งละหนึ่งตัว เมื่อให้บริการไคลเอนต์ตัวหนึ่งเสร็จจึงจะไปให้บริการไคลเอนต์ตัวอื่นที่ติดต่อเข้ามาได้ ลักษณะของโปรแกรมเซิร์ฟเวอร์แบบนี้เราจะเรียกว่า iterative server ซึ่งก็คือเซิร์ฟเวอร์ที่ให้บริการไคลเอนต์ได้ครั้งละหนึ่งตัว ถ้ามีไคลเอนต์ตัวอื่นติดต่อเข้ามาขณะที่เซิร์ฟเวอร์กำลังให้บริการไคลเอนต์ตัวอื่นอยู่ ไคลเอนต์ที่ติดต่อเข้ามาก็จะถูกเข้าคิวไว้ เมื่อเซิร์ฟเวอร์ให้บริการไคลเอนต์ตัวนี้เสร็จก็จะกลับไปให้บริการไคลเอนต์ในคิว สำหรับเซิร์เวอร์ที่ให้บริการไคลเอนต์ได้ทีละหลายตัวพร้อมกันที่เรียกว่า concurrent server นั้น ทำได้โดยใช้เธรด (thread) ซึ่งจะกล่าวถึงในตอนต่อ ๆ ไป
สำหรับโปรแกรมต้นฉบับ (source code) สามารถโหลดได้จากลิงก์นี้ครับ ซึ่งจะได้โปรแกรมทั้งฝั่งเซิร์ฟเวอร์และไคลเอนต์เลย แต่บล็อกเกี่ยวกับไคลเอนต์โปรแกรมจะอยู่ในตอนหน้านะครับ
เมื่อรันโปรแกรมนี้ โปรแกรมจะแสดงสถานะว่าโปรแกรมเซิร์ฟเวอร์กำลังทำงานอยู่ ซึ่งในโปรแกรมเซิร์ฟเวอร์จริง ๆ ไม่จำเป็นต้องมีการทำงานส่วนนี้นะครับ
วันพฤหัสบดีที่ 14 พฤศจิกายน พ.ศ. 2556
เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 2: รู้จักกับหมายเลขพอร์ต
จากตอนที่ 1 คงได้เข้าใจกันแล้วนะครับว่าซ็อกเก็ต (Socket) คืออะไร สำหรับในตอนที่ 2 นี้จะมาพูดถึงเรื่องของหมายเลขพอร์ตให้เข้าใจกันมากขึ้น อย่างที่กล่าวไปแล้วในตอนที่ 1 ว่า ที่อยู่ซ็อกเก็ต (socket address) ประกอบไปด้วย ที่อยู่ไอพีและหมายเลขพอร์ต โดยในส่วนหัวของแพ็กเก็ตข้อมูลที่มีการรับส่งกันจะมีการระบุที่อยู่ซ็อคเก็ตของทั้งฝั่งรับและฝั่งส่ง เปรียบได้กับเวลาเราจ่าหน้าซองจดหมายเราก็จะระบุทั้งที่อยู่ของผู้รับและผู้ส่ง ที่อยู่ของฝั่งส่งจะทำให้ผู้รับถ้าต้องการตอบกลับจะได้รู้ว่าจะต้องส่งข้อมูลกลับไปที่ใคร
ในส่วนของหมายเลขพอร์ตนั้นโปรแกรมเซิร์ฟเวอร์จะมีหมายเลขพอร์ตที่แน่นอนเพื่อที่ให้โปรแกรมไคลเอนต์ติดต่อเข้ามาได้ ส่วนโปรแกรมไคลเอนต์นั้นระบบปฏิบัติการจะเลือกหมายเลขพอร์ตที่ว่าง ณ ตอนนั้นมาให้ นั่นหมายความว่าโปรแกรมไคลเอนต์โปรแกรมเดียวกัน ในการทำงานแต่ละครั้งอาจมีหมายเลขพอร์ตไม่เหมือนกัน เช่นถ้าเราใช้เว็บเบราว์เซอร์เช่น Chrome ติดต่อเข้าไปยังเว็บเซิร์ฟเวอร์หมายเลขพอร์ตที่เว็บเซิร์ฟเวอร์จะมีค่าเดิมทุกครั้งเช่นหมายเลข 80 แต่โปรแกรม Chrome ในการทำงานครั้งแรกระบบปฏิบัติการอาจให้หมายเลข 1234 หลังจากนั้นถ้าปิดไปแล้วเปิดโปรแกรมขึ้นมาใหม่อาจจะได้หมายเลข 2000 ก็ได้
ดังนั้นในการเขียนโปรแกรมฝั่งเซิร์ฟเวอร์นักเขียนโปรแกรมจะต้องระบุหมายเลขพอร์ตที่แน่นอนให้กับโปรแกรม ในขณะที่โปรแกรมฝั่งไคลเอนต์ไม่จำเป็นต้องระบุ สิ่งที่ควรรู้ก็คือโปรแกรมสองโปรแกรมที่ทำงานบนเครื่องเดียวกันในขณะเดียวกัน และใช้โปรโตคอลในชั้นทรานสปอร์ตตัวเดียวกัน เช่นใช้ ทีซีพี (TCP) เหมือนกัน ไม่สามารถใช้หมายเลขพอร์ตเดียวกันได้ เพราะถ้ายอมให้ซ้ำกันได้จะทำให้ไม่รู้ว่าจะต้องส่งข้อมูลไปยังโปรแกรมปลายทางตัวใด ถ้าไม่เข้าใจให้นึกถึงว่าถ้าในคอนโดมิเนียมแห่งหนึ่งมีห้องสองห้องซึ่งมีหมายเลขห้องเดียวกัน ถ้ามีจดหมายมาถึงคนที่มีหน้าที่ส่งจดหมายก็จะไม่รู้ว่าควรจะส่งจดหมายไปที่ห้องใด
ดังนั้นในการเลือกหมายเลขพอร์ตให้กับโปรแกรมเซิร์ฟเวอร์ นักเขียนโปรแกรมควรจะมีความเข้าใจในเรื่องการจัดการหมายเลขพอร์ต โดยหน่วยงานที่มีหน้าที่ในการจัดการหมายเลขพอร์ตก็คือ Internet Assigned Numbers Authority (IANA) ซึ่งเป็นแผนกหนึ่งของ Internet Corporation for Assigned Names and Numbers (ICANN)
การจัดการหมายเลขพอร์ตจะมีการแบ่งหมายเลขพอร์ตออกเป็นช่วง ช่วงแรกคืออ 0-1,023 จะไม่นำมานำมาใช้กับโปรแกรมเรา และระบบปฎิบัติการหลายตัวก็จะไม่ให้เราลงทะเบียนโปรแกรมเราโดยใช้พอร์ตในช่วงนี้นี้ เพราะหมายเลขพอร์ตในช่วงนี้จะเป็นส่วนที่โปรแกรมเซิร์ฟเวอร์ที่เป็นมาตรฐานใช้ เช่นเว็บเซิร์ฟเวอร์ใช้หมายเลข 80 อีเมลเซิร์ฟเวอร์ใช้พอร์ตหมายเลข 25 เป็นต้น
หมายเลข 1,024-49,151 เป็นช่วงหมายเลขพอร์ตที่ IANA กำหนดให้เป็นพอร์ตจดทะเบียน (registered port) คือถ้าหน่วยงานใดที่ต้องการจะจดทะเบียนหมายเลขพอร์ตให้กับโปรแกรมของตัวเอง IANA ก็สามารถนำหมายเลขพอร์ตในช่วงนี้มาจดทะเบียนให้ได้ โปรแกรมเซิร์เวอร์ที่เราเขียนอาจจะใช้หมายเลขพอร์ตในช่วงนี้ก็ได้ แต่ก็ไม่ได้มีการบังคับหรือควบคุมใด ๆ ถ้าเราอยากเขียนโปรแกรมโดยใช้พอร์ตในช่วงนี้
ส่วนช่วงที่เหลือคือ 49,152 ถึง 65,535 เรียกว่าเป็นไดนามิกพอร์ต (dynamic port) เป็นหมายเลขพอร์ตที่ IANA ไม่รับจดทะเบียน และไม่ได้มีการควบคุมใด ๆ คือตั้งใจให้หมายเลขในช่วงนี้ใช้เป็นหมายเลขพอร์ตชั่วคราว หรือใช้สำหรับโปรแกรมหรือบริการที่ใช้ภายในเครื่อง หรือเป็นช่วงหมายเลขพอร์ตที่สุ่มให้โปรแกรมฝั่งไคลเอนต์ได้ใช้
ข้อมูลเพิ่มเติมของพอร์ตต่าง ๆ ที่มีการใช้งานจริงสามารถดูเพิ่มเติมได้จากลิงก์นี้ http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
ก็หวังว่าจะเข้าใจเรื่องหมายเลขพอร์ตมากขึ้นนะครับ แต่ไม่ต้องซีเรียสมาก เอาเป็นว่าโปรแกรมเซิร์ฟเวอร์ที่เราจะเขียนขึ้นต่อไปจะใช้พอร์ตตั้งแต่หมายเลข 1,024 ขึ้นไป ที่ว่างในเครื่องเราแล้วกันนะครับ โดยผมจะเลือกหมายเลขพอร์ตในช่วงของพอร์ตจดทะเบียน (1,024 - 49,151) เป็นหลักนะครับ พบกันต่อไปบทความหน้าครับ
ในส่วนของหมายเลขพอร์ตนั้นโปรแกรมเซิร์ฟเวอร์จะมีหมายเลขพอร์ตที่แน่นอนเพื่อที่ให้โปรแกรมไคลเอนต์ติดต่อเข้ามาได้ ส่วนโปรแกรมไคลเอนต์นั้นระบบปฏิบัติการจะเลือกหมายเลขพอร์ตที่ว่าง ณ ตอนนั้นมาให้ นั่นหมายความว่าโปรแกรมไคลเอนต์โปรแกรมเดียวกัน ในการทำงานแต่ละครั้งอาจมีหมายเลขพอร์ตไม่เหมือนกัน เช่นถ้าเราใช้เว็บเบราว์เซอร์เช่น Chrome ติดต่อเข้าไปยังเว็บเซิร์ฟเวอร์หมายเลขพอร์ตที่เว็บเซิร์ฟเวอร์จะมีค่าเดิมทุกครั้งเช่นหมายเลข 80 แต่โปรแกรม Chrome ในการทำงานครั้งแรกระบบปฏิบัติการอาจให้หมายเลข 1234 หลังจากนั้นถ้าปิดไปแล้วเปิดโปรแกรมขึ้นมาใหม่อาจจะได้หมายเลข 2000 ก็ได้
ดังนั้นในการเขียนโปรแกรมฝั่งเซิร์ฟเวอร์นักเขียนโปรแกรมจะต้องระบุหมายเลขพอร์ตที่แน่นอนให้กับโปรแกรม ในขณะที่โปรแกรมฝั่งไคลเอนต์ไม่จำเป็นต้องระบุ สิ่งที่ควรรู้ก็คือโปรแกรมสองโปรแกรมที่ทำงานบนเครื่องเดียวกันในขณะเดียวกัน และใช้โปรโตคอลในชั้นทรานสปอร์ตตัวเดียวกัน เช่นใช้ ทีซีพี (TCP) เหมือนกัน ไม่สามารถใช้หมายเลขพอร์ตเดียวกันได้ เพราะถ้ายอมให้ซ้ำกันได้จะทำให้ไม่รู้ว่าจะต้องส่งข้อมูลไปยังโปรแกรมปลายทางตัวใด ถ้าไม่เข้าใจให้นึกถึงว่าถ้าในคอนโดมิเนียมแห่งหนึ่งมีห้องสองห้องซึ่งมีหมายเลขห้องเดียวกัน ถ้ามีจดหมายมาถึงคนที่มีหน้าที่ส่งจดหมายก็จะไม่รู้ว่าควรจะส่งจดหมายไปที่ห้องใด
ดังนั้นในการเลือกหมายเลขพอร์ตให้กับโปรแกรมเซิร์ฟเวอร์ นักเขียนโปรแกรมควรจะมีความเข้าใจในเรื่องการจัดการหมายเลขพอร์ต โดยหน่วยงานที่มีหน้าที่ในการจัดการหมายเลขพอร์ตก็คือ Internet Assigned Numbers Authority (IANA) ซึ่งเป็นแผนกหนึ่งของ Internet Corporation for Assigned Names and Numbers (ICANN)
การจัดการหมายเลขพอร์ตจะมีการแบ่งหมายเลขพอร์ตออกเป็นช่วง ช่วงแรกคืออ 0-1,023 จะไม่นำมานำมาใช้กับโปรแกรมเรา และระบบปฎิบัติการหลายตัวก็จะไม่ให้เราลงทะเบียนโปรแกรมเราโดยใช้พอร์ตในช่วงนี้นี้ เพราะหมายเลขพอร์ตในช่วงนี้จะเป็นส่วนที่โปรแกรมเซิร์ฟเวอร์ที่เป็นมาตรฐานใช้ เช่นเว็บเซิร์ฟเวอร์ใช้หมายเลข 80 อีเมลเซิร์ฟเวอร์ใช้พอร์ตหมายเลข 25 เป็นต้น
หมายเลข 1,024-49,151 เป็นช่วงหมายเลขพอร์ตที่ IANA กำหนดให้เป็นพอร์ตจดทะเบียน (registered port) คือถ้าหน่วยงานใดที่ต้องการจะจดทะเบียนหมายเลขพอร์ตให้กับโปรแกรมของตัวเอง IANA ก็สามารถนำหมายเลขพอร์ตในช่วงนี้มาจดทะเบียนให้ได้ โปรแกรมเซิร์เวอร์ที่เราเขียนอาจจะใช้หมายเลขพอร์ตในช่วงนี้ก็ได้ แต่ก็ไม่ได้มีการบังคับหรือควบคุมใด ๆ ถ้าเราอยากเขียนโปรแกรมโดยใช้พอร์ตในช่วงนี้
ส่วนช่วงที่เหลือคือ 49,152 ถึง 65,535 เรียกว่าเป็นไดนามิกพอร์ต (dynamic port) เป็นหมายเลขพอร์ตที่ IANA ไม่รับจดทะเบียน และไม่ได้มีการควบคุมใด ๆ คือตั้งใจให้หมายเลขในช่วงนี้ใช้เป็นหมายเลขพอร์ตชั่วคราว หรือใช้สำหรับโปรแกรมหรือบริการที่ใช้ภายในเครื่อง หรือเป็นช่วงหมายเลขพอร์ตที่สุ่มให้โปรแกรมฝั่งไคลเอนต์ได้ใช้
ข้อมูลเพิ่มเติมของพอร์ตต่าง ๆ ที่มีการใช้งานจริงสามารถดูเพิ่มเติมได้จากลิงก์นี้ http://en.wikipedia.org/wiki/List_of_TCP_and_UDP_port_numbers
ก็หวังว่าจะเข้าใจเรื่องหมายเลขพอร์ตมากขึ้นนะครับ แต่ไม่ต้องซีเรียสมาก เอาเป็นว่าโปรแกรมเซิร์ฟเวอร์ที่เราจะเขียนขึ้นต่อไปจะใช้พอร์ตตั้งแต่หมายเลข 1,024 ขึ้นไป ที่ว่างในเครื่องเราแล้วกันนะครับ โดยผมจะเลือกหมายเลขพอร์ตในช่วงของพอร์ตจดทะเบียน (1,024 - 49,151) เป็นหลักนะครับ พบกันต่อไปบทความหน้าครับ
วันเสาร์ที่ 9 พฤศจิกายน พ.ศ. 2556
เขียนโปรแกรมเครือข่ายด้วยซ็อกเก็ต (Socket) ตอนที่ 1 รู้จักกับซ็อกเก็ต
แม้ว่าในปัจจุบันการพัฒนาโปรแกรมบนเครือข่ายเราจะพัฒนาในลักษณะที่เป็นเว็บแอพลิเคชัน หรือใช้มิดเดิลแวร์อย่างจาวาอีอี (Java EE) หรือ ไมโครซอฟท์ดอตเน็ต (Microsoft .Net) เป็นหลัก แต่พื้นฐานของโปรแกรมที่ทำงานบนเครือข่ายจะอยู่บนแนวคิดของซ็อกเก็ต (Socket) ยิ่งไปกว่านั้นแอพพลิเคชันหลัก ๆ บนอินเทอร์เน็ตที่เราใช้กันอยู่ในปัจจุบันนี้เช่นการติดต่อเพื่อขอหน้าเว็บระหว่างเว็บเบราว์เซอร์หรือเว็บเซิร์ฟเวอร์ หรือการรับส่งไฟล์โดยใช้โปรโตคอลเอฟทีพี (FTP) ก็ใช้เพียงแค่แนวคิดของซ็อกเก็ต ในการทำงาน ดังนั้นการรู้จักและเข้าใจการเขียนโปรแกรมเครือข่ายโดยใช้ซ็อคเก็ต จึงยังจัดว่ามีประโยชน์อยู่ ในบทความตอนแรกนี้ผมจะกล่าวถึงว่า ซ็อกเก็ตคืออะไร และในตอนต่อ ๆ ไป จะแสดงตัวอย่างของการเขียนโปรแกรมในภาษาต่าง ๆ อย่างเช่นภาษาจาวา (Java) ภาษาไพธอน (python) และอาจมีภาษาอื่น ๆ ด้วย ตามความขยันของผม แต่จริง ๆ แล้วถ้าเข้าใจแนวคิดในบทความนี้ผมคิดว่าก็สามารถที่จะนำไปประยุกต์กับภาษาอะไรก็ได้ครับ
ซ็อกเก็ตถ้าให้ความหมายในภาษาไทยอย่างง่าย ๆ ก็คือ เต้ารับที่เราสามารถเอาอุปกรณ์มาเสียบลงไปได้ (ฮั่นแน่ อย่าคิดลึกนะ) ตัวอย่างเช่นเต้ารับของปลั๊กไฟ การที่ใช้ชื่อซ็อกเก็ตนี้กับการสื่อสารระหว่างโปรแกรมบนเครือข่ายก็เพื่อแสดงให้เห็นภาพว่าโปรแกรมที่ต้องการคุยกันระหว่างเครื่อข่ายแต่ละตัว จะต้องเตรียมเต้ารับ (Socket) ของตัวเองไว้ เมื่อต้องการจะคุยกันก็เอาสายเคเบิลมาเสียบเข้ากับเต้ารับของทั้งสองฝั่ง ก็จะทำให้โปรแกรมจากทั้งสองฝั่งสามารถส่งข้อความกันได้ผ่านทางสายเคเบิลนี้
![]() |
| Photo by Greg Rosenke on Unsplash |
ในปัจจุบันระบบปฏิบัติการแทบทุกตัวจะรองรับแนวคิดของการติดต่อสื่อสารผ่านเครือข่ายโดยใช้ซ็อกเก็ต และภาษาเขียนโปรแกรมแทบทุกภาษาก็จะมี ซ็อกเก็ตเอพีไอ (Socket API) ให้นักเขียนโปรแกรมเรียกใช้ ข้อดีของการใช้ซ็อกเก็ตเอพีไอ ก็คือนักเขียนโปรแกรมไม่จำเป็นต้องลงไปรู้รายละเอียดของโปรโตคอลในระดับชั้นทรานส์ปอร์ตอย่างเช่น ทีซีพี (TCP) ) และระดับชั้นเน็ตเวิร์คอย่างเช่นไอพี ( IP)
สิ่งที่ต้องเข้าใจก่อนจะลงมือเขียนโปรแกรมโดยใช้ซ็อกเก็ตเอพีไอก็คือเราจะระบุถึงโปรแกรมที่อยู่กันบนคนละเครื่องในเครือข่ายอินเทอร์เน็ตได้อย่างไร คำตอบก็คือเราจะต้องระบุโดยใช้เลขที่อยู่สองตัวคือเลขที่อยู่ของเครื่องที่โปรแกรมนั้นทำงานอยู่ และเลขที่อยู่ของโปรแกรมนั้น ที่ต้องระบุเลขที่อยู่ของโปรแกรมก็เพราะว่าบนเครื่องคอมพิวเตอร์เครื่องหนึ่งอาจมีโปรแกรมที่ทำงานผ่านเครือข่ายทำงานอยู่พร้อม ๆ กันหลายโปรแกรม ถ้าไม่เข้าใจจะขอยกตัวอย่างว่าถ้าเราต้องการเขียนจดหมายถึงเพื่อนของเราที่อยู่คอนโดมิเนียม เราจะต้องจ่าหน้าซองโดยระบุทีอยู่ของคอนโด และหมายเลขห้องของเพื่อนเรา (คอนโดหนึ่งมีหลายห้อง) ที่อยู่ของคอนโดเปรียบได้กับเลขที่อยู่ของเครื่องคอมพิวเตอร์ ส่วนเลขที่ห้องก็คือที่อยู่ของโปรแกรมนั่นเอง
สำหรับบนอินเทอร์เน็ตเลขที่อยู่ของเครื่องคอมพิวเตอร์เราจะระบุโดยใช้หมายเลขไอพี (IP Address) ซึ่งพวกเราอาจเคยเห็นผ่านตามาบ้างในรูปแบบเช่น 192.168.1.2 อันนี้เป็นหมายเลขไอพีในเวอร์ชัน 4 นะครับ ปัจจุบันในเครือข่ายอินเทอร์เน็ตเราใช้ทั้งเวอร์ชัน 4 และเวอร์ชัน 6 ซึ่งจะเป็นเลขฐานสิบหก (อันนี้ขอละไว้ให้ค้นคว้าเพิ่มเติมเอานะครับว่าเวอร์ชัน 4 กับ 6 ต่างกันยังไง และทำไมเราใช้อยู่สองเวอร์ชัน)
ส่วนหมายเลขที่อยู่ของโปรแกรมเราจะใช้หมายเลขพอร์ต (port number) ครับ ซึ่งหมายเลขพอร์ตนี้ก็คือเลขจำนวนเต็มธรรมดานี่เองครับ โดยจะเป็นเลขจำนวนเต็มที่เริ่มจาก 0 ถึง 65,535 ซึ่งก็เท่ากับจำนวน 65,536 หรือพูดง่าย ๆ ก็คือ ตามทฤษฎีแล้ว คอมพิวเตอร์หนึ่งเครื่องสามารถมีโปรแกรมเครือข่ายทำงานอยู่ได้พร้อมกัน 65,536 โปรแกรม ถ้าจะเทียบกับตัวอย่างคอนโด ก็คือคอนโดมีจำนวนห้อง 65,536 ห้อง
ช่วงของตัวเลขที่ได้นี้ก็มาจากจำนวนบิตในฟิลด์ที่ใช้เก็บหมายเลขพอร์ตในโปรโตคอลระดับชั้นทรานส์ปอร์ตอย่าง ทีซีพี (TCP) หรือ ยูดีพี (UDP) ที่มีจำนวน 16 บิตนั่นเองครับ 2 ยกกำลัง 16 มีค่าเท่ากับ 65,536 นั่นเอง (ไม่รู้เรื่องก็ข้ามไปก่อนครับ ไปทำความเข้าใจเพิ่มเอาทีหลัง) รายละเอียดมากกว่านี้ของหมายเลขพอร์ตจะพูดถึงในตอนต่อไปครับ เอาเป็นว่าจะขอยกตัวอย่างหมายเลขพอร์ตตัวหนึ่งที่เป็นมาตรฐานสำหรับโปรแกรมเว็บเซิร์ฟเวอร์ก็แล้วกันนะครับนั่นก็คือพอร์ตหมายเลข 80 ดังนั้นสมมติว่าถ้าเว็บเบราว์เซอร์ต้องการติดต่อกับโปรแกรมเว็บเซิร์ฟเวอร์ที่ทำงานอยู่บนเครื่องที่มีหมายเลข IP 161.246.123.123 ก็ต้องระบุทั้งหมายเลขไอพีดังกล่าว และหมายเลขพอร์ตซึ่งคือ 80
![]() |
| Photo by Kevin Horvat on Unsplash |
ถึงตอนนี้หลายคนอาจบอกว่าไม่เคยเห็นต้องใส่เลขไอพีและเลขพอร์ตเลยเวลาเข้าถึงเว็บไซต์ เหตุผลก็คือในอินเทอร์เน็ตจะใช้ DNS (Doamin Name Server) เป็นกลไกในการแปลง URL ของเว็บไซต์ที่เราป้อนเป็นหมายเลขไอพีของเครื่องเซิร์ฟเวอร์ให้เราครับ ส่วนพอร์ต 80 เป็นพอร์ตมาตรฐาน ซึ่งถ้าเว็บเซิร์ฟเวอร์ใช้พอร์ตนี้อยู่แล้วก็ไม่จำเป็นต้องระบุ
สำหรับการใช้งานซ็อกเก็ตเอพีไอนั้นแต่ละโปรแกรมก็ต้องระบุที่อยู่ทั้งสองตัวเพื่อสร้างเต้ารับของตัวเอง ซึ่งในการใช้งานซ็อกเก็ตเอพีไอจะเรียกเลขที่อยู่ทั้งสองว่าที่อยู่ซ็อกเก็ต (Socket Address) หรือจะแปลว่าที่อยู่เต้ารับดี :) พูดง่าย ๆ ก็คือ
Socket Address = IP Address + Port Number นั่นเองครับ
เอาล่ะครับก็คิดว่าคงเพียงพอสำหรับตอนแรกของบทความนี้ก่อนนะครับ เพราะถ้ายาวกว่านี้ก็คงมีคนคลิกหนีไปที่อื่นแน่ ก็หวังว่าคงพอจะเข้าใจพื้นฐานนะครับว่าซ็อกเก็ตคืออะไร แล้วพบกันในบทความต่อไปครับ
สมัครสมาชิก:
ความคิดเห็น (Atom)



