I/. Client
Phần này chúng ta sẽ xem thử 1 ví dụ cách tạo kết nối đến một chương trình server sử dụng lớp Socket, và sau đó là cách máy khách gửi và nhận dữ liệu từ server thông qua socket. Ví dụ này chúng ta sẽ hiện thực 1 máy khách gọi là CDTHClient. Client này sẽ kết nối đến máy chủ CDTHServer. Máy chủ YinServer sẽ nhận sữ liệu và trả lời nó.
CDTHClient sẽ tạo 1 socket kết nối đến CDTH server, đọc dữ liệu do người dùng nhập vào và gửi đến CDTH server thông qua socket. Server sẽ trả lời thông điệp và gửi lại client thông qua socket.
Chương trình client sẽ đọc và hiển thị dữ liệu nhận lại được server:
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
public class YinClient {
public static void main(String[] args) throws IOException {
Socket inComing = null; //Đối tượng socket
PrintWriter out = null; //Đối tượng gửi dữ liệu qua socket
Scanner in = null; //Đối tượng nhận dữ liệu từ socket
try {
/*
* Tạo một socket mới kết nối đến máy có tên là “localhost” thông qua cổng 9999
* Ở đây localhost chính là tên địa chỉ cục bộ máy của bạn
* Nếu kết nối đến máy khác bạn hãy gõ đúng tên địa chỉ của máy đó
*/
inComing = new Socket(“localhost”,9999);
System.out.println(“Client: Đã kết nối máy chủ”);
// Tạo đối tượng PrintWriter, tham số truyền vào là luồng Output của socket
// Tham số true để PrintWrite tự động flush
out = new PrintWriter(inComing.getOutputStream(), true);
in = new Scanner(inComing.getInputStream());
// Tạo đối tượng Scanner đọc thông tin từ bàn phím
Scanner sc = new Scanner(System.in);
// Chuỗi người dùng nhập vào
String userInput=“”;
// Hiển thị lời chào của Server
System.out.println(“Server: “ + in.nextLine());
// Vòng lặp nhập xuất
while (!userInput.equals(“exit”)) {
System.out.print(“Client: “);
userInput = sc.nextLine();
out.println(userInput);
System.out.println(in.nextLine());
}
// Đóng các kết nối
out.close();
in.close();
inComing.close();
} catch (UnknownHostException e) {
System.err.println(“Không tìm thấy máy chủ.”);
System.exit(1);
} catch (IOException e) {
System.err.println(“Không thế kết nối máy chủ”);
System.exit(1);
}
}
}
Chú ý rằng CDTHClient sẽ thực hiện cả 2 tác vụ là gửi và nhận dữ liệu từ CDTHServer thông qua socket.
Chúng ta bắt đầu xem xét cách hoạt động của chương trình. Hãy chú ý đến 4 dòng lệnh đầu tiên trong khối try của phương thức main. Nhiệm vụ của chúng là tạo một socket kết nối giữa client và server, sau đó tạo PrintWriter và Scanner để đọc và ghi dữ liệu thông qua luồng của socket.
inComing = new Socket(“localhost”,9999);
System.out.println(“Client: Đã kết nối máy chủ”);
// Tạo đối tượng PrintWriter, tham số truyền vào là luồng Output của socket
// Tham số thứ 2 là true để PrintWrite tự động flush
out = new PrintWriter(inComing.getOutputStream(), true);
in = new Scanner(inComing.getInputStream());
Dòng lệnh đầu tiên sẽ tạo 1 Socket với tên là inComing. Phương thức khởi tạo của Socket yêu cầu tên của máy chủ và số hiệu cổng cần kết nối. Trong ví dụ này chúng ta đã sử dụng chính máy của mình để thực hiện nên tên mặc định sẽ là “localhost”.
Dòng lệnh thứ 2 sẽ lấy luồng xuất và tạo một PrintWriter trên nó. Cũng thế, bạn có thể đoán ra được công dụng của dòng lệnh thứ 3 là tạo một đối tượng Scanner trên luồng nhập của socket để đọc dữ liệu (Nếu bạn chưa quen với các đối tượng nhập xuất (I/O) của java, bạn có thể đọc thêm các tài liệu nói về vấn đề này, chúng khá phổ biến.).
// Hiển thị lời chào của Server
System.out.println(“Server: “ + in.nextLine());
Dòng lệnh trên sẽ hiển thị lời chào của Server và in ra màn hình xuất. Dĩ nhiên chúng ta sẽ làm điều này khi bắt tay vào làm Server. Server sẽ tự động xuất ra lời chào khi có máy khách kết nối đến nó.
Phần thú vị của chương trình nằm trong vòng lặp while phía sau. Vòng lặp này sẽ đọc 1 dòng dữ liệu 1 lần từ luồng nhập chuẩn từ người dùng, sau đó ngay lập tức gửi chúng đến server bằng PrintWriter thông qua socket. Tôi tạm gọi nó là vòng lặp nhập xuất:
// Vòng lặp nhập xuất
while (!userInput.equals(“exit”)) {
System.out.print(“Client: “);
userInput = sc.nextLine();
out.println(userInput);
System.out.println(in.nextLine());
}
Trong thân vòng lặp, chương trình sẽ đọc 1 dòng nhập từ bàn phím sau đó gửi đến server. Dòng cuối cùng của vòng lặp là in thông điệp trả lời của máy chủ. Chú ý rằng để ngừng vòng lặp, ta đã kiểm tra điều kiện cho vòng lặp là dòng thông điệp người dùng gõ vào phải khác “exit”. Nghĩa là việc gõ vào từ exit trong khi bạn nhập thông điệp sẽ đưa bạn thoát ra khỏi vòng lặp.
Một khi đã thoát khỏi vòng lặp, ta sẽ thực hiện đóng các kết nối lại thông qua các câu lệnh sau:
// Đóng các kết nối
out.close();
in.close();
inComing.close();
Hãy nhớ việc đóng các luồng này lại khi bạn thực hiện xong việc kết nối giữa các máy. Một chương trình tốt luôn cố gắng dọn dẹp những thứ nó đã tạo ra khi kết thúc. Những dòng lệnh trên sẽ đóng luồng đọc và ghi kết nối của socket, sau đó là đóng socket kết nối đến server. Thứ tự này là cần thiết. Bạn nên đóng các luồng kết nối đến socket trước khi đóng socket lại.
Chương trình này sẽ kết nối đơn giản thẳng đến server vì nó sử dụng một giao thức đơn giản. Client gửi text đến server, và server gửi trả lại thông điệp. Các bước tiến hành chung cho quá trình này có thể tóm lại như sau:
1. Mở socket
2. Mở luồng nhập và xuất đến socket.
3. Đọc và ghi thông tin qua luồng theo giao thức của server.
4. Đóng các luồng
5. Đóng socket.
II/. Server
Bạn đã tạo xong 1 client, bước tiếp theo chúng ta sẽ tạo 1 server để client có thể kết nối vào. Cách thức hoạt động của Server chúng ta sắp tạo ra không khác Client là mấy. Ngoại trừ việc thêm vào một đối tượng ServerSocket.
Đây là mã nguồn của CDTHServer:
Socket soc = server.accept();
PrintWriter out = new PrintWriter(soc.getOutputStream(), true);
Scanner in = new Scanner(soc.getInputStream());
String inputLine;
// Gửi lời chào đến Client
out.println(“Xin chào”);
// Vòng lặp nhập xuất
while (true){
if(!in.hasNextLine())
break;
inputLine=in.nextLine();
if(inputLine.equalsIgnoreCase(“exit”))
{
out.println(“Server: Ngắt kết nối máy khách”);
break;
}else
out.println(“Server: Đã nhận được thông điệp”);
}
out.close();
in.close();
server.close();
} catch (IOException e) {
System.out.println(“Không thể tạo kết nối”);
System.exit(1);
}
}
}
Lớp ServerSocket có đủ mọi thứ ta cần để viết các server bằng Java. Nó có các constructor để tạo các đối tượng ServerSocket mới, các phương thức để lắng nghe các liên kết trên một cổng xác định, và các phương thức trả về một Socket khi liên kết được thiết lập, vì vậy ta có thể gửi và nhận dữ liệu.
Vòng đời của một server
1. Một ServerSocket mới được tạo ra trên một cổng xác định bằng cách sử dụng một constructor ServerSocket.
2. ServerSocket lắng nghe liên kết đến trên cổng đó bằng cách sử dụng phương thức accept(). Phương thức accept() phong tỏa cho tới khi một client thực hiện một liên kết, phương thức accept() trả về một đối tượng Socket mà liên kết giữa client và server.
3. Tùy thuộc vào kiểu server, hoặc phương thức getInputStream(), getOutputStream() hoặc cả hai được gọi để nhận các luồng vào ra để truyền tin với client.
4. server và client tương tác theo một giao thức thỏa thuận sẵn cho tới khi ngắt liên kết.
5. Server, client hoặc cả hai ngắt liên kết
6. Server trở về bước hai và đợi liên kết tiếp theo.
Một đối tượng ServerSocket hoạt động trong một vòng lặp chấp nhận các liên kết. Mỗi lần lặp nó gọi phương thức accept(). Phương thức này trả về một đối tượng Socket biểu diễn liên kết giữa client và server. Tương tác giữa client và server được tiến hành thông qua socket này. Khi giao tác hoàn thành, server gọi phương thức close() của đối tượng socket. Nếu client ngắt liên kết trong khi server vẫn đang hoạt động, các luồng vào ra kết nối server với client sẽ đưa ra ngoại lệ InterruptedException trong lần lặp tiếp theo
· public Socket accept() throws IOException
Khi bước thiết lập liên kết hoàn thành, và ta sẵn sàng để chấp nhận liên kết, cần gọi phương thức accept() của lớp ServerSocket. Phương thức này sẽ phong tỏa, nó dừng quá trình xử lý và đợi cho tới khi client được kết nối. Khi client thực sự kết nối, phương thức accept() trả về đối tượng Socket. Ta sử dụng các phương thức getInputStream() và getOutputStream() để truyền tin với client.
Bây giờ bạn hãy thử chạy chương trình server này trước, sau đó chạy Client và thử gõ thông điệp trong Client xem sao. Nếu không dùng YinClient, bạn có thể dùng chương trình Telnet có sẵn trong Windows để thực hiện.
Trong telnet, bạn sử dụng lệnh sau để kết nối vào CDTHServer:
Để phát triển ứng dụng này, bạn có thể tạo 1 server có khả năng trò chuyện với client thông qua thông điệp người dùng nhập vào. Tương tự như các chương trình chat thông qua mạng nhưng ở một mức cơ bản nhất.