Hướng dẫn lập trình Java Socket
Công ty Vĩnh Cửu tuyển dụng lập trình viên Java

1- Socket là gì?

Tại phía server (server-side):
  • Thông thường, một chương trình server chạy trên một máy tính cụ thể, chương trình này có một ổ cắm (Server Socket), ổ cắm được ràng buộc bởi cổng (Port number) cụ thể. Các chương trình phục vụ (Server program) chỉ chờ đợi, lắng nghe tại ổ cắm (Server Socket) các Client để thực hiện một yêu cầu kết nối.

Tại phía client (client-side):
  • Các Client biết tên máy của máy tính mà trên đó chương trình chủ (server) đang chạy và số cổng mà chương trình chủ lắng nghe. Để thực hiện một yêu cầu kết nối, Client cố gắng tạo ra cuộc gặp với máy chủ trên máy tính của chương trình chủ và cổng. Các Client cũng cần phải tự định danh chính nó với server để gắn với một cổng địa phương cái sẽ được sử dụng trong suốt quá trình kết nối này, thông thường nó được gán bởi hệ điều hành.
Hình minh họa tổng quát:
Nếu mọi việc suôn sẻ, chương trình chủ chấp nhận kết nối của client. Khi chấp nhận, máy chủ được một socket mới bị ràng buộc vào cổng cùng một địa phương và cũng có thiết bị đầu cuối từ xa của nó thiết lập địa chỉ và cổng của client. Nó đã tạo ra một socket mới để chăm sóc Client vừa được chấp nhận kết nối, và tiếp tục lắng nghe tại ổ cắm gốc ban đầu (ServerSocket) cho các yêu cầu kết nối mới.
Về phía Client, nếu kết nối được chấp nhận, một ổ cắm được tạo thành công và Client có thể sử dụng ổ cắm để giao tiếp với chương trình chủ.

Các Client và Server có thể giao tiếp bằng cách ghi hay đọc từ ổ cắm (Socket) của chúng.

Dữ liệu ghi vào luồng đầu ra trên Socket của client sẽ nhận được trên luồng đầu vào của Socket tại Server. Và ngược lại dữ liệu ghi vào luồng đầu ra trên Socket của Server sẽ nhận được trên luồng đầu vào của Socket tại Client.

Định nghĩa: 

Một Socket là một điểm cuối của một giao tiếp 2 chiều giữa hai chương trình chạy trên mạng. Socket được giàng buộc với một cổng (con số cụ thể) để các tầng TCP (TCP Layer) có thể định danh ứng dụng mà dữ liệu sẽ được gửi tới.

2- Ví dụ đơn giản với Socket

SimpleServerProgram.java
package org.o7planning.tutorial.socket;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class SimpleServerProgram {

   public static void main(String args[]) {

       ServerSocket listener = null;
       String line;
       BufferedReader is;
       BufferedWriter os;
       Socket socketOfServer = null;

   

       // Mở một ServerSocket tại cổng 9999.
       // Chú ý bạn không thể chọn cổng nhỏ hơn 1023 nếu không là người dùng
       // đặc quyền (privileged users (root)).
       try {
           listener = new ServerSocket(9999);
       } catch (IOException e) {
           System.out.println(e);
           System.exit(1);
       }

       try {
           System.out.println("Server is waiting to accept user...");


           // Chấp nhận một yêu cầu kết nối từ phía Client.
           // Đồng thời nhận được một đối tượng Socket tại server.

           socketOfServer = listener.accept();
           System.out.println("Accept a client!");

     
           // Mở luồng vào ra trên Socket tại Server.
           is = new BufferedReader(new InputStreamReader(socketOfServer.getInputStream()));
           os = new BufferedWriter(new OutputStreamWriter(socketOfServer.getOutputStream()));

   
           // Nhận được dữ liệu từ người dùng và gửi lại trả lời.
           while (true) {
               // Đọc dữ liệu tới server (Do client gửi tới).
               line = is.readLine();

               // Ghi vào luồng đầu ra của Socket tại Server.
               // (Nghĩa là gửi tới Client).
               os.write(">> " + line);
               // Kết thúc dòng
               os.newLine();
               // Đẩy dữ liệu đi
               os.flush();  

               // Nếu người dùng gửi tới QUIT (Muốn kết thúc trò chuyện).
               if (line.equals("QUIT")) {
                   os.write(">> OK");
                   os.newLine();
                   os.flush();
                   break;
               }
           }

       } catch (IOException e) {
           System.out.println(e);
           e.printStackTrace();
       }
       System.out.println("Sever stopped!");
   }
}
SimpleClientDemo.java
package org.o7planning.tutorial.socket;

import java.io.*;
import java.net.*;

public class SimpleClientDemo {

   public static void main(String[] args) {

       // Địa chỉ máy chủ.
       final String serverHost = "localhost";

       Socket socketOfClient = null;
       BufferedWriter os = null;
       BufferedReader is = null;

       try {
           // Gửi yêu cầu kết nối tới Server đang lắng nghe
           // trên máy 'localhost' cổng 9999.
           socketOfClient = new Socket(serverHost, 9999);

           // Tạo luồng đầu ra tại client (Gửi dữ liệu tới server)
           os = new BufferedWriter(new OutputStreamWriter(socketOfClient.getOutputStream()));

           // Luồng đầu vào tại Client (Nhận dữ liệu từ server).
           is = new BufferedReader(new InputStreamReader(socketOfClient.getInputStream()));

       } catch (UnknownHostException e) {
           System.err.println("Don't know about host " + serverHost);
           return;
       } catch (IOException e) {
           System.err.println("Couldn't get I/O for the connection to " + serverHost);
           return;
       }

       try {
           // Ghi dữ liệu vào luồng đầu ra của Socket tại Client.
           os.write("HELO");
           os.newLine(); // kết thúc dòng
           os.flush();  // đẩy dữ liệu đi.
           os.write("I am Tom Cat");
           os.newLine();
           os.flush();
           os.write("QUIT");
           os.newLine();
           os.flush();

           // Đọc dữ liệu trả lời từ phía server
           // Bằng cách đọc luồng đầu vào của Socket tại Client.
           String responseLine;
           while ((responseLine = is.readLine()) != null) {
               System.out.println("Server: " + responseLine);
               if (responseLine.indexOf("OK") != -1) {
                   break;
               }
           }

           os.close();
           is.close();
           socketOfClient.close();
       } catch (UnknownHostException e) {
           System.err.println("Trying to connect to unknown host: " + e);
       } catch (IOException e) {
           System.err.println("IOException:  " + e);
       }
   }

}

Chạy ví dụ:

Trước hết bạn cần chạy class SimpleServerProgram:
Tiếp theo chạy class SimpleClientDemo.

3- Ví dụ Socket + Thread

Thông thường một kết nối giữa chương trình chủ và 1 Client được tạo ra, bạn nên để chúng nói chuyện với nhau trên một luồng (Thread), như vậy mỗi khi có một kết nối mới một luồng mới lại được tạo ra.
 
ServerProgram.java
package org.o7planning.tutorial.socketthread;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerProgram {

   public static void main(String args[]) throws IOException {

       ServerSocket listener = null;

       System.out.println("Server is waiting to accept user...");
       int clientNumber = 0;


       // Mở một ServerSocket tại cổng 7777.
       // Chú ý bạn không thể chọn cổng nhỏ hơn 1023 nếu không là người dùng
       // đặc quyền (privileged users (root)).
       try {
           listener = new ServerSocket(7777);
       } catch (IOException e) {
           System.out.println(e);
           System.exit(1);
       }

       try {
           while (true) {


               // Chấp nhận một yêu cầu kết nối từ phía Client.
               // Đồng thời nhận được một đối tượng Socket tại server.

               Socket socketOfServer = listener.accept();
               new ServiceThread(socketOfServer, clientNumber++).start();
           }
       } finally {
           listener.close();
       }

   }

   private static void log(String message) {
       System.out.println(message);
   }

   private static class ServiceThread extends Thread {

       private int clientNumber;
       private Socket socketOfServer;

       public ServiceThread(Socket socketOfServer, int clientNumber) {
           this.clientNumber = clientNumber;
           this.socketOfServer = socketOfServer;

           // Log
           log("New connection with client# " + this.clientNumber + " at " + socketOfServer);
       }

       @Override
       public void run() {

           try {

   
               // Mở luồng vào ra trên Socket tại Server.
               BufferedReader is = new BufferedReader(new InputStreamReader(socketOfServer.getInputStream()));
               BufferedWriter os = new BufferedWriter(new OutputStreamWriter(socketOfServer.getOutputStream()));

               while (true) {
                   // Đọc dữ liệu tới server (Do client gửi tới).
                   String line = is.readLine();

                   // Ghi vào luồng đầu ra của Socket tại Server.
                   // (Nghĩa là gửi tới Client).
                   os.write(">> " + line);
                   // Kết thúc dòng
                   os.newLine();
                   // Đẩy dữ liệu đi
                   os.flush();

                   // Nếu người dùng gửi tới QUIT (Muốn kết thúc trò chuyện).
                   if (line.equals("QUIT")) {
                       os.write(">> OK");
                       os.newLine();
                       os.flush();
                       break;
                   }
               }

           } catch (IOException e) {
               System.out.println(e);
               e.printStackTrace();
           }
       }
   }
}
ClientDemo.java
package org.o7planning.tutorial.socketthread;

import java.io.*;
import java.net.*;
import java.util.Date;

public class ClientDemo {

   public static void main(String[] args) {

       // Địa chỉ máy chủ.
       final String serverHost = "localhost";

       Socket socketOfClient = null;
       BufferedWriter os = null;
       BufferedReader is = null;

       try {
           // Gửi yêu cầu kết nối tới Server đang lắng nghe
           // trên máy 'localhost' cổng 7777.
           socketOfClient = new Socket(serverHost, 7777);

           // Tạo luồng đầu ra tại client (Gửi dữ liệu tới server)
           os = new BufferedWriter(new OutputStreamWriter(socketOfClient.getOutputStream()));

           // Luồng đầu vào tại Client (Nhận dữ liệu từ server).
           is = new BufferedReader(new InputStreamReader(socketOfClient.getInputStream()));

       } catch (UnknownHostException e) {
           System.err.println("Don't know about host " + serverHost);
           return;
       } catch (IOException e) {
           System.err.println("Couldn't get I/O for the connection to " + serverHost);
           return;
       }

       try {
           // Ghi dữ liệu vào luồng đầu ra của Socket tại Client.
           os.write("HELO! now is " + new Date());
           os.newLine(); // kết thúc dòng
           os.flush();  // đẩy dữ liệu đi.
           os.write("I am Tom Cat");
           os.newLine();
           os.flush();
           os.write("QUIT");
           os.newLine();
           os.flush();

           // Đọc dữ liệu trả lời từ phía server
           // Bằng cách đọc luồng đầu vào của Socket tại Client.
           String responseLine;
           while ((responseLine = is.readLine()) != null) {
               System.out.println("Server: " + responseLine);
               if (responseLine.indexOf("OK") != -1) {
                   break;
               }
           }

           os.close();
           is.close();
           socketOfClient.close();
       } catch (UnknownHostException e) {
           System.err.println("Trying to connect to unknown host: " + e);
       } catch (IOException e) {
           System.err.println("IOException:  " + e);
       }
   }

}

4- Java Socket API

4.1- ServerSocket class

Cấu tử:
TT Phương thức và mô tả
1 public ServerSocket(int port) throws IOException

Tạo một đối tượng Server Socket giàng buộc với một cổng cụ thể. Một ngoại lệ sẽ được ném ra nếu cổng đó đã bị giàng buộc (Sử dung) bởi một chương trình khác.

2 public ServerSocket(int port, int backlog) throws IOException

Tương tự như cấu tử trên, tham số backlog định rõ có bao nhiêu client đến để lưu trữ trên hàng đợi.

3 public ServerSocket(int port, int backlog, InetAddress address) throws IOException

Tương tự như cấu tử trên, tham số InetAddress chỉ rõ địa chỉ IP địa phương sẽ được nối (bind) tới. InetAddress được sử dụng cho các máy chủ có thể có nhiều địa chỉ IP, cho phép các máy chủ để xác định các địa chỉ IP của nó để chấp nhận yêu cầu của client.

4 public ServerSocket() throws IOException

Tạo một Server Socket chưa định rõ cổng, địa chỉ. Khi sử dụng cấu tử này, sử dụng phương thức bind(..) khi bạn sẵn sàng để nối với địa chỉ, cổng cụ thể.

Phương thức:
TT Phương thức và mô tả
1 public int getLocalPort()

Trả về cổng mà Server Socket đang lắng nghe. Phương thức này rất có ích, nếu bạn đưa vào giá trị cổng bằng 0 trong cấu tử, nghĩa là nói với server tự tìm một cổng phù hợp, sau đó sử dụng phương thức này để lấy ra giá trị thực.

2 public Socket accept() throws IOException

Đợi một yêu cầu kết nối từ client. Phương thức này sẽ khóa ứng dụng cho tới khi có một yêu cầu kết nối từ client đến trên cổng cụ thể hoặc hết thời gian chờ (Time-out), giả sử rằng thời gian time-out được thiết lập sử dụng phương thức setSoTimeout(). Ngược lại method sẽ bị khóa vô thời hạn.

3 public void setSoTimeout(int timeout)

Sét thời gian chờ tối đa (time-out) nghĩa là thời gian Server Socket sẽ chờ yêu cầu kết nối từ người dùng trong quá trình accept().

4 public void bind(SocketAddress host, int backlog)

Nối Server Socket tới một server cụ thể có cổng định sẵn trong đối tượng SocketAddress. Sử dụng phương thức này nếu bạn khởi tạo ServerSocket sử dụng cấu tử mặc định (Không tham số).

4.2- Socket class

Cấu tử:
TT Cấu tử và mô tả
1 public Socket(String host, int port) throws UnknownHostException, IOException.

Cấu tử này tạo Socket kết nối tới Server cụ thể tại cổng xác định trong tham số, Nếu nó không ném ra ngoại lệ, nghĩa là việc kết nối thành công, client đã kết nối với server.

2 public Socket(InetAddress host, int port) throws IOException

Cấu tử này giống hệt với các nhà xây dựng trước đó, ngoại trừ Server được biểu thị bằng một đối tượng InetAddress.

3 public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException.

Kết nối tới host, port cụ thể, tạo ra một Socket trên host địa phương, và cổng địa phương được chỉ định trong tham số.

4 public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException.

Cấu tử này giống hệt với cấu tử trên, ngoại trừ server được biểu thị bằng một đối tượng InetAddress thay vì một String

5 public Socket()

Tạo một Socket chưa kết nối. Sử dụng phương thức connect() để kết nối socket này tới server.

Phương thức:
TT Phương thức và mô tả
1 public void connect(SocketAddress host, int timeout) throws IOException

Phương thức này kết nối socket này tới host chỉ định. Phương thức này cần thiết chỉ khi bạn khởi tạo một đối tượng Socket thông qua cấu tử mặc định (Không tham số).

2 public InetAddress getInetAddress()

Phương thức này trả về địa chỉ của máy tính mà socket này kết nối tới.

3 public int getPort()

Trả về cổng giàng buộc tới máy tính từ xa.

4 public int getLocalPort()

Trả về cổng socket là ràng buộc để trên máy địa phương.

5 public SocketAddress getRemoteSocketAddress()

Trả về địa chỉ của socket từ xa (remote socket).

6 public InputStream getInputStream() throws IOException

Trả về luồng đầu vào của Socket. Luồng đầu vào này được kết nối với luồng đầu ra của Socket từ xa (remote socket).

7 public OutputStream getOutputStream() throws IOException

Trả về luồng đầu ra của Socket. Luồng đầu ra này kết nối với luồng đầu vào của Socket từ xa.

8 public void close() throws IOException

Đóng socket, nghĩa là làm cho Socket này không còn khả năng trò chuyện với máy kia.

4.3- InetAddress

Phương thức:
TT Phương thức và mô tả
1 static InetAddress getByAddress(byte[] addr)

Trả về đối tượng InetAddress cho bởi địa chỉ IP thô.

2 static InetAddress getByAddress(String host, byte[] addr)

Trả về đối tượng InetAddress dựa trên tên máy chủ được cung cấp và địa chỉ IP.

3 static InetAddress getByName(String host)

Xác định địa chỉ IP của host, cho bởi tên host.

4 String getHostAddress()

Trả về địa chỉ IP.

5 String getHostName()

Trả về host name

6 static InetAddress getLocalHost()

Trả về địa chỉ địa phương.

7 String toString()

Chuyển đối tượng thành String.