วันอังคารที่ 24 ธันวาคม พ.ศ. 2556

การเขียนโปรแกรมเครือข่ายด้วย Socket ตอนที่ 6 : TCP Concurrent Server

จากโปรแกรมเซิร์ฟเวอร์ในตอนที่ 3 จัดเป็นเซิร์ฟเวอร์แบบ iterative นั่นคือให้บริการไคลเอนต์ได้ครั้งละหนึ่งตัว ถ้ามีมากกว่าหนึ่งตัวตัวที่เข้ามาทีหลังจะต้องเข้าคิวรอจนกว่าจะถึงคิวตัวเอง ซึ่งเซิร์ฟเวอร์แบบนี้ไม่เหมาะสมกับงานที่ไคลเอนต์และเซิร์ฟเวอร์ต้องติดต่อกันเป็นเวลานาน ลองนึกภาพว่าถ้า Gmail ให้บริการแบบนี้ดูนะครับว่ามันจะเป็นยังไง ดังนั้นงานแบบนี้จะต้องเขียนเซิร์ฟเวอร์ที่ทำงานแบบพร้อมกัน (Concurrent) ครับ

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

  1. import java.net.*;
  2. import java.io.*;
  3. public class EchoConcurrentServer
  4. {
  5.   public static final int PORT = 50000;
  6.   public static void main(String[] args)
  7.   {
  8.     ServerSocket serverSocket = null;
  9.     Socket serviceSocket = null;
  10.     int port = PORT;
  11.     try
  12.     { // create socket
  13.       if (args.length == 1) // user specify port number
  14.             port = Integer.parseInt(args[0]);
  15.         else if (args.length > 1) { // error 
  16.             System.out.println("Usage: Java EchoServer [port number]");
  17.             System.exit(1);
  18.         }
  19.         if (port <= 1023 || port > 65535)
  20.             throw new NumberFormatException();  
  21.       serverSocket = new ServerSocket(PORT);
  22.     }
  23.     catch (IOException e)
  24.     {
  25.       System.err.println("Error Creating Socket");
  26.       System.exit(1);
  27.     }
  28.     catch (NumberFormatException e) {
  29.         System.out.println("Port number must be integer between 1024 and 65535");
  30.         System.exit(3);
  31.     }

  32.     while (true)
  33.     {
  34.       try
  35.       {

  36.         // wait for connection then create streams
  37.         System.out.println("Waiting for client connection at port number " + port);
  38.         serviceSocket = serverSocket.accept();
  39.         EchoThread echoThread = new EchoThread(serviceSocket, port);
  40.         echoThread.start();
  41.         
  42. } catch (IOException e)
  43.        {
  44.               System.err.println("Closing Socket connection");
  45.               System.exit(1);
  46.        }
  47.     }
  48.   }
  49. }
  50. class EchoThread extends Thread {
  51.     private Socket serviceSocket;
  52.     private int port;
  53.     public EchoThread(Socket serviceSocket, int port) {
  54.         this.serviceSocket = serviceSocket;
  55.         this.port = port;
  56.     }
  57.     public void run() {
  58.         OutputStreamWriter output = null;
  59.         BufferedReader input = null;
  60.         try {
  61.             output = new OutputStreamWriter(serviceSocket.getOutputStream());
  62.             input = new BufferedReader(new InputStreamReader(serviceSocket.getInputStream()));
  63.             while(true) {
  64. String inputWord = input.readLine();
  65. if (!inputWord.equals(""))
  66. {
  67. output.write(inputWord.toUpperCase() + "\r\n");
  68. output.flush();
  69. }
  70. else {
  71. output.close();
  72. input.close();
  73. serviceSocket.close();
  74. break;
  75.                 }
  76.             }
  77.         }
  78.         catch (IOException e) {
  79.             System.err.println("Closing Socket connection");
  80.             
  81.         }
  82.         finally {
  83.             try {
  84.                 if (input != null)
  85.                     input.close();
  86.                 if (output != null)
  87.                     output.close();
  88.                 if (serviceSocket != null)
  89.                     serviceSocket.close();
  90.             }
  91.                 catch (IOException e) {
  92.                     e.printStackTrace();
  93.                 }
  94.         }
  95.     }
  96. }

คลาส EchoThread ในบรรทัดที่ 52-99 เป็นคลาสที่เขียนขึ้นเพื่อเป็นส่วนของการให้บริการไคลเอนต์ บรรทัดที่ 41 และ 42 สร้างออบเจกต์ของ EchoThread และสั่งให้ Thread เริ่มทำงาน หลังจากทำส่วนนี้เสร็จโปรแกรมเซิร์ฟเวอร์ก็สามารถกลับไปรับการติดต่อจากไคลเอนต์ตัวถัดไป และถ้ามีการติดต่อเข้ามาก็ทำแบบเดียวกันคือสร้าง Thread อีกตัวหนึ่งไปให้บริการไคลเอนต์

ส่วนโปรแกรมไคลเอนต์แสดงได้ดังต่อไปนี้

  1. import java.net.*;
  2. import java.io.*;
  3. import java.util.*;
  4. public class EchoClient
  5. {
  6.   public static final int PORT = 50000;  // default port
  7.   public static void main(String[] args)
  8.   {
  9. Socket clientSocket = null;
  10.         int port = PORT;
  11.         String hostName = null;
  12.         if (args.length < 1) {
  13.             System.out.println("Usage: java EchoClient <host name> [Port Number]");
  14.             System.exit(1);
  15.         }
  16.     try
  17.     { // create socket
  18.         if (args.length == 2) {
  19.             port = Integer.parseInt(args[1]);
  20.             if (port <= 1023 || port > 65535) 
  21.                 throw new NumberFormatException();
  22.         }
  23.       clientSocket = new Socket(args[0], port);
  24.     }
  25.     catch (IOException e)
  26.     {
  27.       System.err.println("Error Creating Socket");
  28.       System.exit(2);
  29.     }
  30.     catch (NumberFormatException e) {
  31.         System.out.println("Port number must be integer between 1024 and 65535");
  32.         System.exit(3);
  33.     }
  34.     OutputStreamWriter output = null;
  35.     Scanner input = null;
  36.     try
  37.       {
  38.             output = new OutputStreamWriter(clientSocket.getOutputStream());
  39.             input = new Scanner(clientSocket.getInputStream());
  40.    Scanner inputData = new Scanner(System.in);     
  41.    while(true) {
  42. System.out.println("Enter your word: ");
  43. String inputWord = inputData.nextLine();
  44.                         output.write(inputWord + "\r\n");
  45.                         output.flush();
  46. if (inputWord.equals(""))
  47. {
  48.                                 break;
  49. }
  50. String outputWord = input.nextLine();
  51. System.out.println(outputWord);
  52. }
  53.                 input.close();
  54. output.close();
  55. clientSocket.close();
  56. } catch (IOException e)
  57.        {
  58.               System.err.println("Closing Socket connection");
  59.        }
  60.         finally {
  61.             try {
  62.                 if (input != null)
  63.                     input.close();
  64.                 if (output != null)
  65.                     output.close();
  66.                 if (clientSocket != null)
  67.                     clientSocket.close();
  68.            } 
  69.             catch (IOException e) {
  70.                 e.printStackTrace(); 
  71.             }
  72.         }
  73.     }
  74. }

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

java EchoClient localhost

และขอแนะนำให้ลองรันไคลเอนต์หลาย ๆ ตัวติดต่อกับเซิร์ฟเวอร์พร้อม ๆ กันด้วย สำหรับใครที่ไม่อยากพิมพ์โค้ดเองสามารถดาวน์โหลดได้จากลิงก์นี้ครับ EchoClient.java EchoConcurrentServer.java

สุดท้ายสำหรับวันนี้ก็ขอสุขสันต์วันคริสต์มาสมายังทุกคนครับ...

ไม่มีความคิดเห็น:

แสดงความคิดเห็น