Bài giảng Lập trình mạng với ngôn ngữ Java (Phần 2)
1. Mô hình client/server
Mô hình được phổ biến nhất và được chấp nhận rộng rãi trong các hệ thống phân tán
là mô hình client/server. Trong mô hình này sẽ có một tập các tiến trình mà mỗi tiến trình
đóng vai trò như là một trình quản lý tài nguyên cho một tập hợp các tài nguyên cho trước và
một tập hợp các tiến trình client trong đó mỗi tiến trình thực hiện một tác vụ nào đó cần truy
xuất tới tài nguyên phần cứng hoặc phần mềm dùng chung. Bản thân các trình quản lý tài
nguyên cần phải truy xuất tới các tài nguyên dùng chung được quản lý bởi một tiến trình
khác, vì vậy một số tiến trình vừa là tiến trình client vừa là tiến trình server. Các tiến trình
phát ra các yêu cầu tới các server bất kỳ khi nào chúng cần truy xuất tới một trong các tài
nguyên của các server. Nếu yêu cầu là đúng đắn thì server sẽ thực hiện hành động được
yêu cầu và gửi một đáp ứng trả lời tới tiến trình client.
Tóm tắt nội dung tài liệu: Bài giảng Lập trình mạng với ngôn ngữ Java (Phần 2)
Sưu tầm bởi: www.daihoc.com.vn 119 Chương 6 Lập trình Socket cho giao thức TCP 1. Mô hình client/server Mô hình được phổ biến nhất và được chấp nhận rộng rãi trong các hệ thống phân tán là mô hình client/server. Trong mô hình này sẽ có một tập các tiến trình mà mỗi tiến trình đóng vai trò như là một trình quản lý tài nguyên cho một tập hợp các tài nguyên cho trước và một tập hợp các tiến trình client trong đó mỗi tiến trình thực hiện một tác vụ nào đó cần truy xuất tới tài nguyên phần cứng hoặc phần mềm dùng chung. Bản thân các trình quản lý tài nguyên cần phải truy xuất tới các tài nguyên dùng chung được quản lý bởi một tiến trình khác, vì vậy một số tiến trình vừa là tiến trình client vừa là tiến trình server. Các tiến trình phát ra các yêu cầu tới các server bất kỳ khi nào chúng cần truy xuất tới một trong các tài nguyên của các server. Nếu yêu cầu là đúng đắn thì server sẽ thực hiện hành động được yêu cầu và gửi một đáp ứng trả lời tới tiến trình client. Mô hình client/server cung cấp một cách tiếp cận tổng quát để chia sẻ tài nguyên trong các hệ thống phân tán. Mô hình này có thể được cài đặt bằng rất nhiều môi trường phần cứng và phần mềm khác nhau. Các máy tính được sử dụng để chạy các tiến trình client/server có nhiều kiểu khác nhau và không cần thiết phải phân biệt giữa chúng; cả tiến trình client và tiến trình server đều có thể chạy trên cùng một máy tính. Một tiến trình server có thể sử dụng dịch vụ của một server khác. Mô hình truyền tin client/server hướng tới việc cung cấp dịch vụ. Quá trình trao đổi dữ liệu bao gồm: 1. Truyền một yêu cầu từ tiến trình client tới tiến trình server 2. Yêu cầu được server xử lý 3. Truyền đáp ứng cho client Mô hình truyền tin này liên quan đến việc truyền hai thông điệp và một dạng đồng bộ hóa cụ thể giữa client và server. Tiến trình server phải nhận thức được thông điệp được yêu cầu ở bước một ngay khi nó đến và hành động phát ra yêu cầu trong client phải được tạm dừng (bị phong tỏa) và buộc tiến trình client ở trạng thái chờ cho tớ khi nó nhận được đáp ứng do server gửi về ở bước ba. Mô hình client/server thường được cài đặt dựa trên các thao tác cơ bản là gửi (send) và nhận (receive). Sưu tầm bởi: www.daihoc.com.vn 120 Hình 4.1 Quá trình giao tiếp client và server có thể diễn ra theo một t rong hai chế độ: bị phong tỏa (blocked) và không bị phong tỏa (non-blocked). Chế độ bị phong tỏa (blocked): Trong chế độ bị phong tỏa, khi tiến trình client hoặc server phát ra lệnh gửi dữ liệu (send), việc thực thi của tiến trình sẽ bị tạm ngừng cho tới khi tiến trình nhận phát ra lệnh nhận dữ liệu (receive). Tương tự đối với tiến trình nhận dữ liệu, nếu tiến trình nào đó (client hoặc server) phát ra lệnh nhận dữ liệu, mà tại thời điểm đó chưa có dữ liệu gửi tới thì việc thực thi của tiến trình cũng sẽ bị tạm ngừng cho tới khi có dữ liệu gửi tới. Chế độ không bị phong tỏa (non-blocked) Trong chế độ này, khi tiến trình client hay server phát ra lệnh gửi dữ liệu thực sự, việc thực thi của tiến trình vẫn được tiến hành mà không quan tâm đến việc có tiến trình nào phát ra lệnh nhận dữ liệu đó hay không. Tương tự cho trường hợp nhận dữ liệu, khi tiến trình phát ra lệnh nhận dữ liệu, nó sẽ nhận dữ liệu hiện có, việc thực thi của tiến trình vẫn được tiến hành mà không quan tâm đến việc có tiến trình nào phát ra lệnh gửi dữ liệu tiếp theo hay không. 2. Các kiến trúc Client/Server 2.1. Client/Server hai tầng (two-tier client/server) Kiến trúc client/server đơn giản nhất là kiến trúc hai tầng. Trong thực tế hầu hết các kiến trúc client/server là kiến trúc hai tầng. Một ứng dụng hai tầng cung cấp nhiều trạm làm việc với một tầng trình diễn thống nhất, tầng này truyền tin với tầng lưu trữ dữ liệu tập trung. Tầng trình diễn thông thường là client, và tầng lưu trữ dữ liệu là server. Hầu hết các ứng dụng Internet như là email, telnet, ftp thậm chí là cả Web là các ứng dụng hai tầng. Phần lớn các lập trình viên trình ứng dụng viết các ứng dụng client/server có xu thế sử dụng kiến trúc này. Tiến trình đang phong tỏa Tiến trình đang xử lý Request message Request message Reply Execution Wait Server Client Sưu tầm bởi: www.daihoc.com.vn 121 Trong ứng dụng hai tầng truyền thống, khối lượng công việc xử lý được dành cho phía client trong khi server chỉ đơn giản đóng vai trò như là chương trình kiểm soát luồng vào ra giữa ứng dụng và dữ liệu. Kết quả là không chỉ hiệu năng của ứng dụng bị giảm đi do tài nguyên hạn chế của PC, mà khối lượng dữ liệu truyền đi trên mạng cũng tăng theo. Khi toàn bộ ứng dụng được xử lý trên một PC, ứng dụng bắt buộc phải yêu cầu nhiều dữ liệu trước khi đưa ra bất kỳ kết quả xử lý nào cho người dùng. Nhiều yêu cầu dữ liệu cũng làm giảm hiệu năng của mạng. Một vấn đề thường gặp khác đối với ứng dụng hai tầng là vấn đề bảo trì. Chỉ cần một thay đổi nhỏ đối với ứng dụng cũng cần phải thay đổi lại toàn bộ ứng dụng client và server. Hình 4.2 2.2. Client/Server ba tầng Ta có thể tránh được các vấn đề của kiến trúc client/server hai tầng bằng cách mở rộng kiến trúc thành ba tầng. Một kiến trúc ba tầng có thêm một tầng mới tác biệt việc xử lý dữ liệu ở vị trí trung tâm. Hình 4.3 Sưu tầm bởi: www.daihoc.com.vn 122 Theo kiến trúc ba tầng, một ứng dụng được chia thành ba tầng tách biệt nhau về mặt logic. Tầng đầu tiên là tầng trình diễn thường bao gồm các giao diện đồ họa. Tầng thứ hai, còn được gọi là tầng trung gian hay tầng tác nghiệp. Tầng thứ ba chứa dữ liệu cần cho ứng dụng. Tầng thứ ba về cơ bản là chương trình thực hiện các lời gọi hàm để tìm kiếm dữ liệu cần thiết. Tầng trình diễn nhận dữ liệu và định dạng nó để hiển thị. Sự tách biệt giữa chức năng xử lý với giao diện đã tạo nên sự linh hoạt cho việc thiết kế ứng dụng. Nhiều giao diện người dùng được xây dựng và triển khai mà không làm thay đổi logic ứng dụng. Tầng thứ ba chứa dữ liệu cần thiết cho ứng dụng. Dữ liệu này có thể bao gồm bất kỳ nguồn thông tin nào, bao gồm cơ sở dữ liệu như Oracale, SQL Server hoặc tài liệu XML. 2.3. Kiến trúc n-tầng Kiến trúc n-tầng được chia thành các tầng như sau: Tầng giao diện người dùng: quản lý tương tác của người dùng với ứng dụng Tầng logic trình diễn: Xác định cách thức hiển thị giao diện người dùng và các yêu cầu của người dùng được quản lý như thế nào. Tầng logic tác nghiệp: Mô hình hóa các quy tắc tác nghiệp, Tầng các dịch vụ hạ tầng: Cung cấp một chức năng bổ trợ cần thiết cho ứng dụng như các thành phần (truyền thông điệp, hỗ trợ giao tác). 3. Mô hình truyền tin socket Hình 4.4 6 Socket() Bind() Listen() Accept() Các chức năng gửi và nhận Close() Socket() Bind() Connect() Các chức năng gửi và nhận Close() 1 3 4 5 7 2 Server Client Sưu tầm bởi: www.daihoc.com.vn 123 Khi lập trình, ta cần quan tâm đến chế độ bị phong tỏa, vì nó có thể dẫn đến tình huống một tiến trình nào đó sẽ rơi vào vòng lặp vô hạn của quá trình gửi hoặc nhận. Trong chương 1 chúng ta đã biết hai giao thức TCP và UDP là các giao thức tầng giao vận để truyền dữ liệu. Mỗi giao thức có những ưu và nhược điểm riêng. Chẳng hạn, giao thức TCP có độ tin cậy truyền tin cao, nhưng tốc độ truyền tin bị hạn chế do phải có giai đoạn thiết lập và giải phóng liên kết khi truyền tin, khi gói tin có lỗi hay bị thất lạc thì giao thức TCP phải có trách nhiệm truyền lại,Ngược lại, giao thức UDP có tốc độ truyền tin rất nhanh vì nó chỉ có một cơ chế truyền tin rất đơn giản: không cần phải thiết lập và giải phóng liên kết. Khi lập trình cho TCP ta sử dụng các socket luồng, còn đối với giao thức UDP ta sẽ sử dụng lớp DatagramSocket và DatagramPacket. Truyền tin hướng liên kết nghĩa là cần có giai đoạn thiết lập liên kết và giải phóng liên kết trước khi truyền tin. Dữ liệu được truyền trên mạng Internet dưới dạng các gói (packet) có kích thước hữu hạn được gọi là datagram. Mỗi datagram chứa một header và một payload. Header chứa địa chỉ và cổng cần truyền gói tin đến, cũng như địa chỉ và cổng xuất phát của gói tin, và các thông tin khác được sử dụng để đảm bảo độ tin cậy truyền tin, payload chứa dữ liệu. Tuy nhiên do các datagram có chiều dài hữu hạn nên thường phải phân chia dữ liệu thành nhiều gói và khôi phục lại dữ liệu ban đầu từ các gói ở nơi nhận. Trong quá trình truyền tin có thể có thể có một hay nhiều gói bị mất hay bị hỏng và cần phải truyền lại hoặc các gói tin đến không theo đúng trình tự. Để tránh những điều này, việc phân chia dữ liệu thành các gói, tạo các header, phân tích header của các gói đến, quản lý danh sách các gói đã nhận được và các gói chưa nhận được, ... rất nhiều công việc cần phải thực hiện, và đòi hỏi rất nhiều phần mềm phức tạp. Thật may mắn, ta không cần phải tự thực hiện công việc này. Socket là một cuộc cách mạng của Berkeley UNIX. Chúng cho phép người lập trình xem một liên kết mạng như là một luồng mà có thể đọc dữ liệu ra hay ghi dữ liệu vào từ luồng này. Về mặt lịch sử Socket là một sự mở rộng của một trong những ý tưởng quan trọng nhất của UNIX: tất cả các thao tác vào/ra giống như vào ra tệp tin đối với người lập trình, cho dù ta đang làm việc với bàn phím, màn hình đồ họa, một file thông thường, hay một liên kết mạng. Các Socket che dấu người lập trình khỏi các chi tiết mức thấp của mạng như môi kiểu đường truyền, các kích thước gói, yêu cầu truyền lại gói, các địa chỉ mạng... Một socket có thể thực hiện bảy thao tác cơ bản: Kết nối với một máy ở xa (ví dụ, chuẩn bị để gửi và nhận dữ liệu) Gửi dữ liệu Nhận dữ liệu Ngắt liên kêt Gán cổng Nghe dữ liệu đến Chấp nhận liên kết từ các máy ở xa trên cổng đã được gán Lớp Socket của Java được sử dụng bởi cả client và server, có các phương thức tương ứng với bốn thao tác đầu tiên. Ba thao tác cuối chỉ cần cho server để chờ các client liên kết với chúng. Các thao tác này được cài đặt bởi lớp ServerSocket. Các socket cho client thường được sử dụng theo mô hình sau: Một socket mới được tạo ra bằng cách sử dụng hàm Socket(). Socket cố gắng liên kết với một host ở xa. Mỗi khi liên kết được thiết lập, các host ở xa nhận các luồng vào và luồng ra từ socket, và sử dụng các luồng này để gửi dữ liệu cho nhau. Kiểu liên kết này được gọi Sưu tầm bởi: www.daihoc.com.vn 124 là song công (full-duplex)-các host có thể nhận và gửi dữ liệu đồng thời. Ý nghĩa của dữ liệu phụ thuộc vào giao thức. Khi việc truyền dữ liệu hoàn thành, một hoặc cả hai phía ngắt liên kết. Một số giao thức, như HTTP, đòi hỏi mỗi liên kết phải bị đóng sau mỗi khi yêu cầu được phục vụ. Các giao thức khác, chẳng hạn FTP, cho phép nhiều yêu cầu được xử lý trong một liên kết đơn. 4. Socket cho Client 4.1. Các constructor public Socket(String host, int port) throws UnknownHostException, IOException Hàm này tạo một socket TCP với host và cổng xác định, và thực hiện liên kết với host ở xa. Ví dụ: try{ Socket s = new Socket( “www.vnn.vn”,80); } catch(UnknownHostException e){ System.err.println(e); } catch(IOException e){ System.err.println(e); } Trong hàm này tham số host là hostname kiểu String, nếu host không xác định hoặc máy chủ tên miền không hoạt động thì constructor đưa ra ngoại lệ UnknownHostException. Vì một lý do nào đó mà không thể mở được socket thì constructor sẽ đưa ra ngoại lệ IOException. Có nhiều nguyên nhân khiến cho một liên kết thất bại: host mà ta đang cố gắng kết nối tới không chấp nhận liên kết, kết nối Internet có thể bị ngắt, hoặc vấn đề định tuyến có thể ngăn ngừa các gói tin của ta tới đích. Ví dụ: Viết chương trình để kiểm tra trên 1024 cổng đầu tiên những cổng nào đang có server hoạt động import java.net.*; import java.io.*; class PortScanner { public static void main(String[] args) { String host="localhost"; if(args.length>0){ host=args[0]; } for(int i=0;i<1024;i++){ try{ Socket s=new Socket(host,i); System.out.println("Co mot server dang hoat dong tren cong:"+i); Sưu tầm bởi: www.daihoc.com.vn 125 } catch(UnknownHostException e){ System.err.println(e); } catch(IOException e){ System.err.println(e); } } } } public Socket(InetAddress host, int port)throws IOException Tương tự như constructor trước, constructor này tạo một socket TCP với thông tin là địa chỉ của một host được xác định bởi một đối tượng InetAddres và số hiệu cổng port, sau đó nó thực hiện kết nối tới host. Nó đưa ra ngoại lệ IOException nhưng không đưa ra ngoại lệ UnknownHostException. Constructor đưa ra ngoại lệ trong trường hợp không kết nối được tới host. public Socket (String host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException Constructor này tạo ra một socket với thông tin là địa chỉ IP được biểu diễn bởi một đối tượng String và một số hiệu cổng và thực hiện kết nối tới host đó. Socket kết nối tới host ở xa thông qua một giao tiếp mạng và số hiệu cổng cục bộ được xác định bởi hai tham số sau. Nếu localPort bằng 0 thì Java sẽ lựa chọn một cổng ngẫu nhiên có sẵn nằm trong khoảng từ 1024 đến 65535. public Socket (InetAddress host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException Constructor chỉ khác constructor trên ở chỗ địa chỉ của host lúc này được biểu diễn bởi một đối tượng InetAddress. 4.2. Nhận các thông tin về Socket Đối tượng Socket có một số trường thông tin riêng mà ta có thể truy nhập tới chúng thông qua các phương thức trả về các thông tin này. public InetAddress getInetAddress() Cho trước một đối tượng Socket, phương thức getInetAddress() cho ta biết host ở xa mà Socket kết nối tới, hoặc liên kết đã bị ngắt thì nó cho biết host ở xa mà Socket đã kết nối tới public int getPort() Phương thức này cho biết số hiệu cổng mà Socket kết nối tới trên host ở xa. public int getLocalPort() Thông thường một liên kết thường có hai đầu: host ở xa và host cục bộ. Để tìm ra số hiệu cổng ở phía host cục bộ ta gọi phương thức getLocalPort(). public InetAddress getLocalAddress() Phương thức này cho ta biết giao tiếp mạng nào mà một socket gắn kết với nó. public InputStream getInputStream() throws IOException Sưu tầm bởi: www.daihoc.com.vn 126 Phương thức geInputStream() trả về một luồng nhập để đọc dữ liệu từ một socket vào chương trình. Thông thường ta có thể gắn kết luồng nhập thô InputStream tới một luồng lọc hoặc một luồng ký tự nhằm đưa các chức năng tiện ích (chẳng hạn như các luồng InputStream, hoặc InputStreamReader). Để tâng cao hiệu năng, ta có thể đệm dữ liệu bằng cách gắn kết nó với luồng lọc BufferedInputStream hoặc BufferedReader. public OutputStream getOutputStream() throws IOException Phương thức getOutputStream() trả về một luồng xuất thô để ghi dữ liệu từ ứng dụng ra đầu cuối của một socket. Thông thường, ta sẽ gắn kết luồng này với một luồng tiện lợi hơn như lớp DataOuputStream hoặc OutputStreamWriter trước khi sử dụn ... www.daihoc.com.vn Hình 4 Giả sử đối tượng obj là một đối tượng khả tuần tự. Bản thân đối tượng này có thể đã là khả tuần tự hoặc do người lập trình định nghĩa nên thuộc tính khả tuần tự cho nó. Cơ chế ghi đối tượng được tiến hành rất đơn giản: Trước tiên ta tạo ra một tệp để ghi thông tin, thực chất là tạo ra đối tượng FileOuputStream, sau đó ta tạo ra một luồng ghi đối tượng ObjectOuputStream gắn với luồng ghi tệp và gắn kết hai luồng với nhau. Việc ghi đối tượng được thực hiện bởi phương thức writeObject(). FileOuputStream fos=new FileOuputStream("date.out"); ObjectOuputStream oos=new ObjectOuputStream(fos); Date d=new Date(); oos.writeObject(d); Quá trình trên được gọi là quá trình tuần tự hóa. Chúng ta nhận thấy rằng để phục hồi lại trạng thái của một đối tượng ta phải mở một tệp để đọc dữ liệu. Nhưng ta không thể đọc được trực tiếp mà phải thông qua luồng nhập đối tượng ObjectInputStream gắn với luồng nhập tệp tin FileInputStream. Việc đọc lại trạng thái đối tượng được tiến hành nhờ phương thức readObject() FileInputStream fis=new FileInputStream("date.out"); ObjectInputStream ois=new ObjectInputStream(fis); Date d=(Date)ois.readObject(); Quá trình trên còn được gọi là giải tuần tự hóa Công việc đọc và ghi trạng thái của đối tượng khả tuần tự do người lập trình định nghĩa được tiến hành hoàn toàn tương tự như trên. 2. Truyền các đối tượng thông qua Socket Chúng ta đã biết cách ghi và đọc các đối tượng từ các luồng vào ra trong một tiến trình đơn, bây giờ chúng ta sẽ xem xét cách truyền đối tượng thông qua Socket. Mô hình lập trình Socket cho giao thức TCP là mô hình rất phổ biến trong lập trình mạng. Để lập chương trình client/server trong Java ta cần hai lớp Socket và ServerSocket. 2.1. Lớp Socket Lớp Socket của Java được sử dụng bởi cả client và server, nó có các phương thức tương ứng với bốn thao tác đầu tiên. Ba thao tác cuối chỉ cần cho server để chờ các client liên kết với chúng. Các thao tác này được cài đặt bởi lớp ServerSocket. Các Socket cho client thường được sử dụng theo mô hình sau: Sưu tầm bởi: www.daihoc.com.vn 1. Một Socket mới được tạo ra bằng cách sử dụng hàm dựng Socket(). 2. Socket cố gắng liên kết với một host ở xa. 3. Mỗi khi liên kết được thiết lập, các host ở xa nhận các luồng vào và luồng ra từ Socket, và sử dụng các luồng này để gửi dữ liệu cho nhau. Kiểu liên kết này được gọi là song công (full-duplex), các host có thể nhận và gửi dữ liệu đồng thời. Ý nghĩa của dữ liệu phụ thuộc vào từng giao thức. 4. Khi việc truyền dữ liệu hoàn thành, một hoặc cả hai phía ngắt liên kết. Một số giao thức, như HTTP, đòi hỏi mỗi liên kết phải bị đóng sau mỗi khi yêu cầu được phục vụ. Các giao thức khác, chẳng hạn như FTP, cho phép nhiều yêu cầu được xử lý trong một liên kết đơn. 2.2. Lớp ServerSocket 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 biểu diễn 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(), getOuputStream() hoặc cả hai được gọi để nhận các luồng vào ra phục vụ cho việc 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 Server trở về bước hai và đợi liên kết tiếp theo. 2.3. Truyền và nhận dữ liệu trong mô hình lập trình Socket Việc truyền và nhận dữ liệu thực chất là các thao tác đọc và ghi dữ trên Socket. Ta có thể thấy điều này qua sơ đồ dưới đây: Hình 5 Giả sử s là một đối tượng Socket. Nếu chương trình nhận dữ liệu thì ta sẽ lấy dữ liệu từ luồng nhập đến từ Socket: Program Socket InputStream ObjectOuput Sưu tầm bởi: www.daihoc.com.vn InputStream is=s.getInputStream() Để phục hồi trạng thái đối tượng ta gắn kết luồng nhập thô lấy được từ Socket với luồng đọc đối tượng ObjectInputStream: ObjectInputStream ois=new ObjectInputStream(is); Khi đó đối tượng được phục hồi lại trạng thái bởi câu lệnh: Object obj=(Object)ois.readObject(); Nếu chương trình gửi dữ liệu thì ta sẽ lấy dữ liệu từ luồng xuất đến từ Socket: ObjectOuput os=s.getObjectOuput(); Để tiến hành ghi đối tượng ta gắn kết luồng xuất thô lấy được từ Socket với luồng xuất đối tượng ObjectOuputStream: ObjectOuputStream oos=new ObjectOutputStream(os); Việc truyền đối tượng lúc này trở thành một công việc rất đơn giản: oos.writeObject(obj); oos.flush(); 2.4. Ví dụ minh họa Để minh họa kỹ thuật chúng ta viết một server thực hiện phép nhân hai mảng số nguyên với nhau. Client gửi hai đối tượng, mỗi đối tượng biểu diễn một mảng nguyên; server nhận các đối tượng này, thực hiện lời gọi phương nhân hai mảng số nguyên với nhau và gửi kết quả trả về cho client. Trước tiên chúng ta định nghĩa đối tượng để có thể sử dụng trong việc truyền các đối tượng. public class ArrayObject implements java.io.Serializable{ private int[] a=null; public ArrayObject(){ } public void setArray(int a[]){ this.a=a; } public int[] getArray(){ return a; } } Lớp ArrayObject là lớp được xây dựng để đóng gói các mảng số nguyên và có khả năng truyền đi qua lại trên mạng. Cấu trúc lớp như sau: trường thông tin là một mảng số nguyên a[]; phương thức setArray() thiết lập giá trị cho mảng. Phương thức getArray() trả về một mảng số nguyên từ đối tượng ArrayObject. Sưu tầm bởi: www.daihoc.com.vn Mô hình client/server tối thiểu phải có hai mođun client và server. Trong ví dụ này cũng vậy ta sẽ xây dựng một số mođun chương trình như sau: Đầu tiên chúng ta phát triển client. Client tạo ra hai thể hiện của các đối tượng ArrayObject và ghi chúng ra luồng xuất (thực chất là gửi tới server). public class ArrayClient{ public static void main(String[] args)throws Exception{ ObjectOuputStream oos=null; ObjectInputStream ois=null; int dat1[]={3,3,3,3,3,3,3}; int dat2[]={5,5,5,5,5,5,5}; Socket s=new Socket("localhost",1234); oos=new ObjectOuputStream(s.getObjectOuput()); ois=new ObjectInputStream(s.getInputStream()); ArrayObject a1=new ArrayObject(); a1.setArray(dat1); ArrayObject a2=new ArrayObject(); a2.setArray(dat2); ArrayObject res=null; int r[]=new int[7]; oos.writeObject(a1); oos.writeObject(a2); oos.flush(); res=(ArrayObject)ois.readObject(); r=res.getArray(); System.out.println("The result received from server..."); System.out.println(); for(int i=0;i<r.length;i++)System.out.print(r[i]+" "); } } Bước tiếp theo chúng ta phát triển server. Server là một chương trình cung cấp dịch vụ phục vụ các yêu cầu của client. Server nhận hai đối tượng ArrayObject và nhận về hai mảng từ hai đối tượng này và sau đó đem nhân chúng với nhau và gửi kết quả trở lại cho client. Sưu tầm bởi: www.daihoc.com.vn public class ArrayServer extends Thread { private ServerSocket ss; public static void main(String args[])throws Exception { new ArrayServer(); } public ArrayServer()throws Exception{ ss=new ServerSocket(1234); System.out.println("Server running on port "+1234); this.start(); } public void run(){ while(true){ try{ System.out.println("Waiting for client..."); Socket s=ss.accept(); System.out.println("Accepting a connection from:"+s.getInetAddress()); Connect c=new Connect(s); } catch(Exception e){ System.out.println(e); } } } } Trong mô hình client/server tại một thời điểm server có thể phục vụ các yêu cầu đến từ nhiều client, điều này có thể dẫn đến các vấn đề tương tranh. Chính vì lý do này mà lớp ArrayServer thừa kế lớp Thread để giải quyết vấn đề trên. Ngoài ra để nâng cao hiệu suất của chương trình thì sau khi đã chấp nhận liên kết từ một client nào đó, việc xử lý dữ liệu sẽ được dành riêng cho một tuyến đoạn để server có thể tiếp tục chấp nhận các yêu cầu khác. Hay nói cách khác, mỗi một yêu cầu của client được xử lý trong một tuyến đoạn riêng biệt. class Connect extends Thread{ private Socket client=null; private ObjectInputStream ois; Sưu tầm bởi: www.daihoc.com.vn private ObjectOuputStream oos; public Connect(){ } public Connect(Socket client){ this.client=client; try{ ois=new ObjectInputStream(client.getInputStream()); oos=new ObjectOuputStream(client.getObjectOuput()); } catch(Exception e){ System.err.println(e); } this.start(); } public void run(){ ArrayObject x=null; ArrayObject y=null; int a1[]=new int[7]; int a2[]=new int[7]; int r[]=new int[7]; try{ x=(ArrayObject)ois.readObject(); y=(ArrayObject)ois.readObject(); a1=x.getArray(); a2=y.getArray(); for(int i=0;i<a1.length;i++)r[i]=a1[i]*a2[i]; ArrayObject res=new ArrayObject(); res.setArray(r); oos.writeObject(res); oos.flush(); ois.close(); client.close(); } catch(Exception e){ } Sưu tầm bởi: www.daihoc.com.vn } } 3. Truyền các đối tượng thông qua giao thức UDP Một giao thức gần với giao thức TCP là giao thức UDP. Java hỗ trợ cho kiểu ứng dụng truyền tin phi liên kết trên giao thức UDP thông qua lớp DatagramSocket và DatagramPacket. Liệu chúng ta có thể viết được các chương trình nhập và xuất đối tượng bằng truyền tin datagram? Thực hiện điều này không thể tiến hành trực tiếp như với luồng Socket. Vấn đề là DatagramSocket không được gắn với bất kỳ luồng nào; mà nó sử dụng một tham số mảng byte để gửi và nhận dữ liệu. Hình 6 Có thể thấy rằng để xây dựng một gói tin datagram, đối tượng phải được chuyển thành một mảng byte. Việc chuyển đổi này rất khó để thực hiện nếu bản thân đối tượng có liên quan đến một số đối tượng phức tạp trong đồ thị đối tượng. Hình 6 minh họa dòng luân chuyển dữ liệu khi truyền một đối tượng thông qua một datagram. Dưới đây là bảy bước ta cần thực hiện để cài đặt mô hình truyền dữ liệu cho giao thức UDP Bước 1. Chuẩn bị: Tạo đối tượng cần truyền đi, giả sử đối tượng này là obj, làm cho nó khả tuần tự bằng cách thực thi giao tiếp Serializable. Bước 2. Tạo một luồng ByteArrayObjectOuput và đặt tên cho nó là baos. Bước 3. Xây dựng đối tượng ObjectOuputStream và đặt tên cho nó là oos. Tham số cho cấu tử ObjectOuputStream là baos Bước 4. Ghi đối tượng obj vào luồng baos bằng cách sử dụng phương thức writeObject() của oos. Bước 5. Tìm kiếm vùng đệm dữ liệu mảng byte từ bằng cách sử dụng phương thức toByteAray(). Object ObjectOuputStream ByteArrayObjectOuput DatagramPacket Object ObjectInputStream ByteArrayInputStream DatagramPacket Network Sưu tầm bởi: www.daihoc.com.vn Bước 6. Xây dựng đối tượng DatagramPacket và đặt tên là dp với dữ liệu đầu vào là vùng đệm dữ liệu đã tìm được ở bước 5. Bước 7. Gửi dp thông qua DatagramSocket bằng cách gọi phương thức send() của nó. Ví dụ minh họa chi tiết quá trình gửi một đối tượng InetAddress ia=InetAddress.getByName("localhost"); Student st=new Student("Peter",7,8,9); DatagramSocket ds=new DatagramSocket(); ByteArrayObjectOuput baos=new ByteArrayObjectOuput(5000); ObjectOuputStream oos=new ObjectOuputStream(new BufferedObjectOuput(baos)); oos.flush(); oos.writeObject(st); oos.flush(); byte[] b=baos.toByteAray(); DatagramPacket dp=new DatagramPacket(b,b.length,ia,1234); ds.send(dp); oos.close(); Để nhận một đối tượng ta cũng tiến hành các bước như trên nhưng theo thứ tự ngược lại, thay thế luồng ObjectOuputStream bằng ObjectInputStream và ByteArrayObjectOuput bằng ByteArrayInputStream. Ví dụ dưới đây minh họa chi tiết quá trình nhận một đối tượng DatagramSocket ds=new DatagramSocket(1234); while(true){ byte b[]=new byte[5000]; DatagramPacket dp=new DatagramPacket(b,b.length); ds.receive(dp); ByteArrayInputStream bais=new ByteArrayInputStream(new BufferedInputStream(b)); ObjectInputStream ois =new ObjectInputStream(bais); Student st=(Student)ois.readObject(); st.computeAverage(); st.print(); ois.close(); bais.close(); } Sưu tầm bởi: www.daihoc.com.vn 4. Kết luận Qua bài báo này tôi đã giới thiệu tổng quan về tuần tự hóa đối tượng. Thông qua các ví dụ chúng ta thấy không quá khó để làm việc với tuần tự hóa đối tượng và điều quan trọng hơn là chúng ta đã biết cách để truyền đi các đối tượng có cấu trúc phức tạp thông qua các Socket. Ngoài ra, bài báo cũng đã đề cập tới cách truyền đối tượng bằng cách sử dụng các gói tin datagram. Nhờ những ưu điểm của tiện ích tuần tự hóa đối tượng, tôi đã minh họa một cách truyền các đối tượng bằng cách sử dụng các gói tin datagram. Như chúng ta đã thấy, mặc dù trong giao thức này không hỗ trợ xử lý theo luồng dữ liệu nhưng tôi đã “luồng hóa” các đối tượng để đưa các đối tượng vào các mảng byte. Sự lựa chọn giữa việc sử dụng RMI hay giải pháp Socket kết hợp với tuần tự hóa phụ thuộc vào từng dự án và các yêu cầu của nó. Sự lựa chọn giải pháp nào chính là sự thỏa hiệp giữa các đặc trưng của mỗi giải pháp: nếu đối với RMI thì đó là tính đơn giản khi triển khai, ngược lại với Socket kết hợp với tuần tự hóa đối tượng thì đó lại là ưu thế về mặt hiệu năng. Nếu vấn đề hiệu năng có tầm quan trọng thì giải pháp lập trình Socket kết hợp tuần tự hóa đối tượng là giải pháp tốt hơn so với RMI. Sưu tầm bởi: www.daihoc.com.vn 90 TÀI LIỆU THAM KHẢO [1] Elliotte Rusty Harold, Java Network Programming [2] Nguyễn Phương Lan- Hoàng Đức Hải, Java lâp trình mạng, Nhà xuất bản Giáo dục [3] Darrel Ince & Adam Freemat, Programming the Internet with Java, Addison- Wesley [4] Mary Campione&Kathy Walrath&Alison Huml, Java™ Tutorial, Third Edition: A Short Course on the Basics, Addison Wesley [5] The Complete Java 2Reference [6] Nguyễn Thúc Hải, Mạng máy tính và các hệ thống mở, Nhà xuất bản Giáo dục [7] Đoàn Văn Ban, Lập trình hướng đối tượng với Java, Nhà xuất bản Khoa học và Kỹ thuật Tài liệu tham khảo [1] Douglas E.Comer, David L.Stevens, Client-Server Programming And Applications. In book: Internetworking with TCP/IPVolume III, Pearson Education, Singapore, 2004. [2] Herbert Schildt, JavaTM 2: The Complete Reference Fifth Edition, Tata McGraw-Hill Publishing Company Limited, India, 2002. [3] Elliote Rusty Harold, JavaTM Network Programming, Third Edition, Oreilly, 2005. [4] Qusay H. Mahmoud, Advanced Socket Programming, December 2001 [5] Shengxi Zhou, Transport Java objects over the network with datagram packets, 2006
File đính kèm:
- bai_giang_lap_trinh_mang_voi_ngon_ngu_java_phan_2.pdf