java_ lập trình tcp socket

22
[TUT]JAVA: Lập trình TCP Socket Quote: JAVA: Lập trình Socket với TCP Người soạn: Nimbus Tài liệu tham khảo: 1. Giáo trình Lập trình Hướng đối tượng JAVA - Ngọc Anh Thư Press - NXB Thống Kê 2. Computer Networking - By James F. Kurose and Keith W. Ross - Addison Wesley 3. Website http://www.planet-source-code.com 4. JAVA Lập trình mạng - Nguyễn Phương Lan và Hoàng Đức Hải - NXB Giáo Dục 1.1. Lập trình Socket với TCP 1.1.1. Giới thiệu Trong phần này chúng ta sẽ tìm hiểu về sự phát triển các ứng dụng mạng. Ta đã biết lõi (core) của một ứng dụng mạng bao gồm một cặp chương trình – một chương trình máy khách (client program) và một chương trình máy chủ (server program). Khi cả hai chương trình được thi hành, tiến trình (process) máy khách và máy chủ được tạo ra, và hai tiến trình này liên lạc với nhau bằng cách đọc từ và ghi đến những socket. Khi tạo ra một ứng dụng mạng, nhiệm vụ của chúng ta là viết mã cho cả chương trình máy khách và máy chủ. Có hai loại ứng dụng client-server. Một loại là những ứng dụng client-server mà nó được hiện thực trên những giao thức chuẩn đã được định nghĩa trong các RFC (Request for Comments). Với những ứng dụng kiểu này thì chương trình máy chủ và máy khách đều phải phù hợp với những quy tắc đã được nêu ra trong RFC. Ví dụ, một chương trình máy khách có thể hiện thực một chương trình FTP client, được định nghĩa trong [RFC 959], và chương trình máy chủ thì hiện thực một FTP server, cũng phải được định nghĩa trong [RFC 959] Loại còn lại của một ứng dụng client-server là một ứng dụng server-client giữ độc quyền (proprietary). Trường hợp này ứng với ứng dụng ta thực hiện trong bài tập này. Trong loại ứng dụng này thì chương trình máy khách lẫn máy chủ đều không cần phải phù hợp với bất cứ RFC nào. Một nhà phát triển đơn lẻ có thể tạo ra cả hai chương trình client và server, và nhà phát triển này hoàn toàn điều khiển tất cả những gì có trong mã lập trình. Nhưng bởi vì mã lập trình không được hiện thực trên giao thức chuẩn, nên những nhà phát triển độc lập khác sẽ không thể nào phát triển mã để có thể tương thích với ứng dụng này. Khi phát triển một ứng dụng kiểu này, những nhà phát triển phải chú ý để không sử dụng những cổng thông dụng đã được định nghĩa trong các RFC. Trong phần kế tiếp ta sẽ nghiên cứu những điểm mấu chốt để phát triển một ứng dụng client-server giữ độc quyền. Trong quá trình phát triển, một trong những quyết định quan trọng mà nhà phát triển cần phải làm là ứng dụng sẽ chạy trên TCP hay trên UDP. TCP thì hướng kết nối (connection- oriented) và cung cấp những kênh luồng truyền dữ liệu đáng tin mà qua đó dữ liệu sẽ được truyền giữa hai hệ thống cuối. UDP là một giao thức không kết nối và gởi đi những gói dữ liệu độc lập từ hệ thống cuối này đến những hệ thống còn lại, mà không hề bảo đảm dữ liệu có được nhận hay không. Trong khuôn khổ bài tập này thì ta chỉ nghiên cứu một ứng dụng nhỏ chạy trên TCP. Ứng dụng nhỏ này được viết bằng ngôn ngữ Java, ta chọn Java vì những lý do sau đây. Thứ nhất, những ứng dụng mạng kiểu này sẽ gọn gàng hơn khi viết bằng Java; với Java sẽ có ít dòng mã hơn, và mỗi dòng có thể dễ dàng giải thích với cả những người lập trình mới bắt đầu. Thứ hai, những chương trình client-server lập trình bằng Java đã gia tăng ngày càng thông dụng, và nó có thể trở thành tiêu chuẩn cho lập trình mạng trong vài năm tới. Java là một ngôn ngữ độc lập nền tảng, nó có cơ chể bẫy lỗi (điều quản các ngoại lệ) cường tráng mà có thể giải quyết hầu nó các lỗi xảy ra trong quá trình xuất/nhập và những hoạt động mạng, và khả năng phân luồng (thread) mạnh cung cấp những phương pháp đơn giản để hiện thực những server mạnh mẽ. 1.1.2. Lập trình Socket với TCP Ta đã biết rằng những tiến trình chạy trên những máy khác nhau có thể liên lạc với nhay bằng cách gởi những thông điệp đến socket. Chúng ta có thể nói một cách đơn giản rằng mỗi một tiến trình thì tương tự như một căn nhà và socket của tiến trình đó thì tương tự như là cánh cửa nhà. Như đã thể hiện trên hình vẽ bên dưới, socket là cánh cửa giữa tiến trình ứng dụng (Application Process) và TCP. Những nhà phát triển ứng dụng có thể kiểm soát mọi thứ bên phía lớp Application của Socket; tuy nhiên, có rất ít kiểm soát bên phía lớp Transport. Bây giờ ta hãy xét kỉ hơn sự tương tác giữa chương trình client và chương trình server. Máy khách client phải có nhiệm vụ khởi tạo sự giao tiếp với server. Để server có thể trả lời lại sự

Upload: tuan-anh

Post on 02-Mar-2015

199 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: JAVA_ Lập trình TCP Socket

[TUT]JAVA: Lập trình TCP SocketQuote:JAVA: Lập trình Socket với TCPNgười soạn: NimbusTài liệu tham khảo:1. Giáo trình Lập trình Hướng đối tượng JAVA - Ngọc Anh Thư Press - NXB Thống Kê2. Computer Networking - By James F. Kurose and Keith W. Ross - Addison Wesley3. Website http://www.planet-source-code.com4. JAVA Lập trình mạng - Nguyễn Phương Lan và Hoàng Đức Hải - NXB Giáo Dục 1.1. Lập trình Socket với TCP

1.1.1. Giới thiệuTrong phần này chúng ta sẽ tìm hiểu về sự phát triển các ứng dụng mạng. Ta đã biết lõi (core) của một ứng dụng mạng bao gồm một cặp chương trình – một chương trình máy khách (client program) và một chương trình máy chủ (server program). Khi cả hai chương trình được thi hành, tiến trình (process) máy khách và máy chủ được tạo ra, và hai tiến trình này liên lạc với nhau bằng cách đọc từ và ghi đến những socket. Khi tạo ra một ứng dụng mạng, nhiệm vụ của chúng ta là viết mã cho cả chương trình máy khách và máy chủ.Có hai loại ứng dụng client-server. Một loại là những ứng dụng client-server mà nó được hiện thực trên những giao thức chuẩn đã được định nghĩa trong các RFC (Request for Comments). Với những ứng dụng kiểu này thì chương trình máy chủ và máy khách đều phải phù hợp với những quy tắc đã được nêu ra trong RFC. Ví dụ, một chương trình máy khách có thể hiện thực một chương trình FTP client, được định nghĩa trong [RFC 959], và chương trình máy chủ thì hiện thực một FTP server, cũng phải được định nghĩa trong [RFC 959]

Loại còn lại của một ứng dụng client-server là một ứng dụng server-client giữ độc quyền (proprietary). Trường hợp này ứng với ứng dụng ta thực hiện trong bài tập này. Trong loại ứng dụng này thì chương trình máy khách lẫn máy chủ đều không cần phải phù hợp với bất cứ RFC nào. Một nhà phát triển đơn lẻ có thể tạo ra cả hai chương trình client và server, và nhà phát triển này hoàn toàn điều khiển tất cả những gì có trong mã lập trình. Nhưng bởi vì mã lập trình không được hiện thực trên giao thức chuẩn, nên những nhà phát triển độc lập khác sẽ không thể nào phát triển mã để có thể tương thích với ứng dụng này. Khi phát triển một ứng dụng kiểu này, những nhà phát triển phải chú ý để không sử dụng những cổng thông dụng đã được định nghĩa trong các RFC.Trong phần kế tiếp ta sẽ nghiên cứu những điểm mấu chốt để phát triển một ứng dụng client-server giữ độc quyền. Trong quá trình phát triển, một trong những quyết định quan trọng mà nhà phát triển cần phải làm là ứng dụng sẽ chạy trên TCP hay trên UDP. TCP thì hướng kết nối (connection-oriented) và cung cấp những kênh luồng truyền dữ liệu đáng tin mà qua đó dữ liệu sẽ được truyền giữa hai hệ thống cuối. UDP là một giao thức không kết nối và gởi đi những gói dữ liệu độc lập từ hệ thống cuối này đến những hệ thống còn lại, mà không hề bảo đảm dữ liệu có được nhận hay không. Trong khuôn khổ bài tập này thì ta chỉ nghiên cứu một ứng dụng nhỏ chạy trên TCP.Ứng dụng nhỏ này được viết bằng ngôn ngữ Java, ta chọn Java vì những lý do sau đây. Thứ nhất, những ứng dụng mạng kiểu này sẽ gọn gàng hơn khi viết bằng Java; với Java sẽ có ít dòng mã hơn, và mỗi dòng có thể dễ dàng giải thích với cả những người lập trình mới bắt đầu. Thứ hai, những chương trình client-server lập trình bằng Java đã gia tăng ngày càng thông dụng, và nó có thể trở thành tiêu chuẩn cho lập trình mạng trong vài năm tới. Java là một ngôn ngữ độc lập nền tảng, nó có cơ chể bẫy lỗi (điều quản các ngoại lệ) cường tráng mà có thể giải quyết hầu nó các lỗi xảy ra trong quá trình xuất/nhập và những hoạt động mạng, và khả năng phân luồng (thread) mạnh cung cấp những phương pháp đơn giản để hiện thực những server mạnh mẽ.

1.1.2. Lập trình Socket với TCP

Ta đã biết rằng những tiến trình chạy trên những máy khác nhau có thể liên lạc với nhay bằng cách gởi những thông điệp đến socket. Chúng ta có thể nói một cách đơn giản rằng mỗi một tiến trình thì tương tự như một căn nhà và socket của tiến trình đó thì tương tự như là cánh cửa nhà. Như đã thể hiện trên hình vẽ bên dưới, socket là cánh cửa giữa tiến trình ứng dụng (Application Process) và TCP. Những nhà phát triển ứng dụng có thể kiểm soát mọi thứ bên phía lớp Application của Socket; tuy nhiên, có rất ít kiểm soát bên phía lớp Transport.Bây giờ ta hãy xét kỉ hơn sự tương tác giữa chương trình client và chương trình server. Máy khách client phải có nhiệm vụ khởi tạo sự giao tiếp với server. Để server có thể trả lời lại sự giao tiếp ban đầu này thì server cũng phải luôn luôn sẳn sàng. Điều này bao gồm 2 yếu tố. Đầu tiên, chương trình bên server phải đang hoạt động, nó phải đang chạy như một tiến trình trước khi máy khách tiến hành sự giao tiếp đầu tiên. Thứ hai, chương trình bên server phải có sẳn một số cổng (port) để có thể tiếp đón (welcome) những giao tiếp khởi tạo từ bên phía client. Sử dụng sự tương tự như căn nhà/cánh cửa đối với process/socket như đã nói ở trên thì ta có thể xem như sự khởi tạo giao tiếp đầu tiên bên phía client như là một sự gõ cửa.Khi một tiến trình (process) bên server đang chạy, một tiến trình bên client có thể khởi tạo một TCP connection. Điều này được thực bên chương trình máy client bằng cách tạo ra một đối tượng socket (ta gọi nó là clientSocket).

Page 2: JAVA_ Lập trình TCP Socket

Khi một máy khách tạo ra một đối tượng socket thì nó sẽ chỉ đến địa chỉ của tiến trình máy chủ, theo cách gọi tên, thì đó là địa chỉ IP của máy server. Và cùng lúc tạo ra đối tượng clientSocket thì TCP ở client khởi tạo một three-way handshake (bài tập mạng TCP/IP sẽ giới thiệu rõ hơn về cách thức này) và thiết lập một TCP connection với server. Three-way handshake này thì hoàn toàn ẩn giấu với cả chương trình server lẫn chương trình client.Trong suốt quá trình three-way handshake, tiến trình bên máy khách sẽ “gõ vào cổng” của tiến trình máy chủ. Khi server nhận được sự gõ cửa này, nó sẽ tạo ra một cánh cửa mới (thật ra là một socket mới) để mà phục vụ cho từng client chuyên biệt. Trong bài tập này, cánh cửa chào đón chính là một đối tượng ServerSocket mà ta gọi đó là welcomeSocket. Khi một máy khách gõ cửa, chương trình sẽ viện dẫn phương thức accept() của welcomeSocket, phương thức này sẽ tạo ra một socket khác cho client. Vào cuối quá trình handshaking này thì một kết nối TCP sẽ được thiết lập giữa socket của client và một socket mới của server. Từ đây trở về sau, ta sẽ gọi new socket này bằng tên gọi là “connection socket”

TCP connection đuợc xem như là một ống dẫn ảo giữa client’s socket và server's connection socket. Lúc này, tiến trình bên máy khách có thể gửi những byte tuỳ ý đến socket của nó, TCP bảo đảm rằng tiến trình bên server sẽ nhận (thông qua connection socket) mỗi byte theo trình tự đã gửi. Hơn thế nữa, ngược lại tiến trình bên máy khách cũng có thể nhận byte từ socket của nó và tiến trình bên server cũng có thể gửi byte đến connection socket của nó. Điều này được mô tả ở hình bên dưới.

Bởi vì socket đóng một vai trò quan trọng trong những ứng dụng client-server, cho nên những ứng dụng client-server thường được gọi là socket programming. Trước khi đi đến ứng dụng bài tập cụ thể, ta sẽ nói về khái niệm luồng (stream). Theo nghĩa đen luồng là một dòng lưu chuyển. Theo nghĩa kỹ thuật, một luồng là một lộ trình qua đó dữ liệu di chuyển đi vào hay đi ra một tiến trình (process). Mỗi một luồng thì có thể là một luồng vào (input stream) hay là một luồng xuất (output stream) cho một tiến trình. Nếu một luồng là luồng vào thì nó sẽ đính kèm với những nguồn vào của một tiến trình, ví dụ như là một công cụ nhập chuẩn (keyboard) hoặc một socket mà dữ liệu đi vào nó từ network. Nếu một luồng là luồng xuất, thì nó sẽ được đính kèm với một vài nguồn xuất của tiến trình, ví dụ như là một công cụ xuất chuẩn (màn hình) hoặc một socket mà đi ra từ nó dữ liệu sẽ đi vào network.

=======================================

1.2. Chương trình ví dụ 1: Giao nhận file bằng IP1.2.1. Cách thức hoạt độngChương trình gồm một file duy nhất có tên là FileTransfer.java tức là nó chỉ có một mã nguồn cho cả hai bên server và client. Sau khi biên dịch file .java này thì ta sẽ chạy nó bằng lệnh java FileTransfer. Tiếp theo chương trình sẽ yêu cầu người dùng chọn lựa để computer trở thành client hoặc server, được thể hiện như trên hình vẽ. Ta nhập vào 1 nếu để chọn là client và nhập vào 2 nếu muốn máy tính trở thành server.

Chạy chương trình ở server modeTuỳ chọn 2 phải được chọn trước ở máy tính đã được chỉ định chạy server mode, sau đó máy tính này sẽ chờ đợi các kết nối đến nó. Ta phải nhập tiếp port number, ứng với server mode này thì ta có thể chọn bất cứ port number nào lớn hơn 1024, vì những port number dưới 1024 đã bị giữ trước và sử dụng bởi hệ thống (well-known ports). Chương trình này cũng có thể thực hiện giao nhận file giữa một server và nhiều máy con đồng thời trong một hệ thống mạng nên nó sẽ yêu cầu ta nhập vào số lượng máy client lớn nhất có thể kết nối đến server này.

Chạy chương trình ở client modeTương tự cho phía bên client, chương trình sẽ yêu cầu nhập vào địa chỉ của server (host address), ta có thể nhập địa chỉ IP hay nhập vào tên của máy chạy server mode đều được (trong trường hợp trên hình bên dưới thì tên máy chạy server mode có địa chỉ IP trong mạng LAN là 192.168.0.144). Tiếp tục ta sẽ nhập port number, client phải cùng đồng ý chọn nhập port number như bên server như bên server đã chọn thì mới có thể giao nhận file được.

Giao diện giao nhận fileSao khi chọn xong bước này thì chương trình ở cả hai máy sẽ hiển thị một cửa sổ dạng GUI để giúp cho việc giao nhận file được thực hiện dễ dàng.

Cả hai bên server và client đều có thể gửi file bằng cách click vào nút Browse để chọn file và sau đó click vào nút Send để gửi file đi.Sau khi click Send để gửi đi thì chương trình bên máy nhận sẽ hiển thị một hộp thoại để hỏi người dùng có chấp nhận nhận file hay không. Theo mô tả trên hình vẽ thì máy sieutoc.mshome.net (máy chạy server mode có địa chỉ IP 192.168.0.144 như hình ở trên) đã gửi một file có tên là TestTransferFile.txt cho máy khác.

Page 3: JAVA_ Lập trình TCP Socket

Sau khi chọn nhận file xong thì chương trình sẽ tiếp tục hiện một hộp thoại khác nửa để thông báo rằng file đã được gửi hay được nhận. File sau khi nhận sẽ được chứa trong cùng thư mục chứa file chương trình.

1.2.2. Hiện thực ứng dụng bằng mã lập trình Java:

Code://************************************************** ************************// Name: File transfer application// Description: transfer files on a network// Source: http://www.planet-source-code.com// File Name: FileTransfer.java//************************************************** ************************

import java.io.*;import java.net.*;import java.util.*;import java.awt.*;import java.awt.event.*;import javax.swing.*;

public class FileTransfer extends Frame {public static String strHostAddress = "";public static int intPortNumber = 0, intMaxClients = 0;public static Vector vecConnectionSockets = null;public static FileTransfer objFileTransfer;public static String strFileName = "",strFilePath = "";public static int intCheckChoice = 0;public static Socket clientSocket = null;public static ObjectOutputStream outToServer = null;public static ObjectInputStream inFromServer = null;

public static void main (String [] args) throws IOException {BufferedReader stdin = new BufferedReader(new InputStreamReader (System.in));System.out.println("\nSelect Type for this Computer:");System.out.println(" 1. Connect to host (i.e This computer is a client)");System.out.println(" 2. Wait for connections (i.e This computer is a server)");System.out.println();System.out.print("Please make a choice: ");System.out.flush();int intChoice = Integer.parseInt(stdin.readLine());

if (intChoice == 1) { // This computer is a clientSystem.out.print("Please type in the host address: ");System.out.flush();strHostAddress = stdin.readLine();System.out.print("Please type in the host port number: ");System.out.flush();intPortNumber = Integer.parseInt(stdin.readLine());}

if (intChoice == 2) { //This computer is a serverSystem.out.print("Give the port number to listen for requests: ");System.out.flush();

Page 4: JAVA_ Lập trình TCP Socket

intPortNumber = Integer.parseInt(stdin.readLine());System.out.print("Give the maximum number of clients for this server: ");System.out.flush();intMaxClients = Integer.parseInt(stdin.readLine());}objFileTransfer = new FileTransfer(intChoice);}public Label lblSelectFile;public Label lblTitle;public Label lblStudentName;public TextField tfFile;public Button btnBrowse;public Button btnSend;public Button btnReset;

public FileTransfer (int c) {setTitle("Java - File Transfer");setSize(300 , 300);setLayout(null);addWindowListener(new WindowAdapter () { public void windowClosing (WindowEvent e) { System.exit(0); } } );

lblTitle = new Label("FILE TRANSFER BY IP");add(lblTitle);lblTitle.setBounds(80,30,300,30);

lblSelectFile = new Label("Select File:");add(lblSelectFile);lblSelectFile.setBounds(15,87,100,20);

lblStudentName = new Label("Thanh Thien - Thanh Tuan");add(lblStudentName);lblStudentName.setBounds(130,270,200,20);

tfFile = new TextField("");add(tfFile);tfFile.setBounds(13,114,200,20);

btnBrowse = new Button("Browse");btnBrowse.addActionListener(new buttonListener());add(btnBrowse);

btnBrowse.setBounds(223,113,50,20);btnSend = new Button("Send");btnSend.addActionListener(new buttonListener());add(btnSend);btnSend.setBounds(64,168,50,20);

btnReset = new Button("Reset");btnReset.addActionListener(new buttonListener());add(btnReset);btnReset.setBounds(120,168,50,20);

show();

if (c == 1) { // Client receives fileintCheckChoice = 1;

Page 5: JAVA_ Lập trình TCP Socket

try {clientSocket = new Socket (strHostAddress,intPortNumber);outToServer = new ObjectOutputStream(clientSocket.getOutputStream()) ;outToServer.flush();inFromServer = new ObjectInputStream(clientSocket.getInputStream());int intFlag = 0;

while (true) {Object objRecieved = inFromServer.readObject();

switch (intFlag) {case 0:

if (objRecieved.equals("IsFileTransfered")) {intFlag++;}break;case 1:strFileName = (String) objRecieved;int intOption = JOptionPane.showConfirmDialog(this,clientSocket.ge tInetAddress().getHostName()+" is sending you "+strFileName+"!\nDo you want to recieve it?","Recieve Confirm",JOptionPane.YES_NO_OPTION,JOptionPane.QUE STION_MESSAGE);

if (intOption == JOptionPane.YES_OPTION) {intFlag++;

} else {intFlag = 0;}break;case 2:byte[] arrByteOfReceivedFile = (byte[])objRecieved;FileOutputStream outToHardDisk = new FileOutputStream(strFileName); outToHardDisk.write(arrByteOfReceivedFile);intFlag = 0; JOptionPane.showMessageDialog(this,"File Recieved!","Confirmation",JOptionPane.INFORMATION_ MESSAGE);break;}Thread.yield();}} catch (Exception e) {System.out.println(e);}}

if (c == 2) { // Server receives filevecConnectionSockets = new Vector();intCheckChoice = 2;

try {ServerSocket welcomeSocket = new ServerSocket(intPortNumber,intMaxClients);

while (true) {vecConnectionSockets.addElement(new ThreadedConnectionSocket(welcomeSocket.accept()));Thread.yield();}

Page 6: JAVA_ Lập trình TCP Socket

} catch (IOException ioe) {System.out.println(ioe);}}}

public static String showDialog () {FileDialog fd = new FileDialog(new Frame(),"Select File...",FileDialog.LOAD);fd.show();return fd.getDirectory()+fd.getFile();}

private class buttonListener implements ActionListener {

public void actionPerformed (ActionEvent ae) {byte[] arrByteOfSentFile = null;

if (ae.getSource() == btnBrowse) {strFilePath = showDialog();tfFile.setText(strFilePath);int intIndex = strFilePath.lastIndexOf("\\");strFileName = strFilePath.substring(intIndex+1);}

if (ae.getSource() == btnSend) {

try {FileInputStream inFromHardDisk = new FileInputStream (strFilePath);int size = inFromHardDisk.available();arrByteOfSentFile = new byte[size];inFromHardDisk.read(arrByteOfSentFile,0,size);

if (intCheckChoice == 2) {// Send file from server 

for (int i=0;i<vecConnectionSockets.size();i++) {ThreadedConnectionSocket tempConnectionSocket = (ThreadedConnectionSocket)vecConnectionSockets.ele mentAt(i);tempConnectionSocket.outToClient.writeObject("IsFi leTransfered");tempConnectionSocket.outToClient.flush();tempConnectionSocket.outToClient.writeObject(strFi leName);tempConnectionSocket.outToClient.flush();tempConnectionSocket.outToClient.writeObject(arrBy teOfSentFile);tempConnectionSocket.outToClient.flush();}}

if (intCheckChoice == 1) { // Send file from clientoutToServer.writeObject("IsFileTransfered");outToServer.flush();outToServer.writeObject(strFileName);outToServer.flush();outToServer.writeObject(arrByteOfSentFile);outToServer.flush();}JOptionPane.showMessageDialog(null,"File Sent!","Confirmation",JOptionPane.INFORMATION_MESS AGE);

Page 7: JAVA_ Lập trình TCP Socket

} catch (Exception ex) {} 

}

if (ae.getSource() == btnReset) {tfFile.setText("");}}}}

class ThreadedConnectionSocket extends Thread {public Socket connectionSocket;public ObjectInputStream inFromClient;public ObjectOutputStream outToClient;

public ThreadedConnectionSocket (Socket s) {connectionSocket = s;

try {outToClient = new ObjectOutputStream(connectionSocket.getOutputStrea m());outToClient.flush();inFromClient = new ObjectInputStream(connectionSocket.getInputStream( ));} catch (Exception e) {System.out.println(e);}start();}

public void run () {

try {int intFlag = 0;String strFileName = "";

while (true) {Object objRecieved = inFromClient.readObject();

switch (intFlag) {case 0:

if (objRecieved.equals("IsFileTransfered")) {intFlag++;}break;case 1:strFileName = (String) objRecieved;int intOption = JOptionPane.showConfirmDialog(null,connectionSocke t.getInetAddress().getHostName()+" is sending you "+strFileName+"!\nDo you want to recieve it?","Recieve Confirm",JOptionPane.YES_NO_OPTION,JOptionPane.QUE STION_MESSAGE);

if (intOption == JOptionPane.YES_OPTION) {

Page 8: JAVA_ Lập trình TCP Socket

intFlag++;

} else {intFlag = 0;}break;case 2:byte[] arrByteOfReceivedFile = (byte[])objRecieved;FileOutputStream outToHardDisk = new FileOutputStream(strFileName); outToHardDisk.write(arrByteOfReceivedFile);intFlag = 0; JOptionPane.showMessageDialog(null,"File Recieved!","Confirmation",JOptionPane.INFORMATION_ MESSAGE);break;}Thread.yield();}} catch (Exception e) {System.out.println(e);}}} ================================================== ========

1.2.3. Giải thích phần hiện thực chương trìnhChúng ta sẽ nghiên cứu chi tiết, từng dòng một trong mã chương trình để hiểu rõ cơ chế hoạt động của nó. Tuy nhiên để có thể dễ dàng nắm bắt nó thì ta sẽ nêu một số điểm mấu chốt trong chương trình

1.2.3.1 Những điểm mấu chốt của chương trìnhTại máy chạy client modeChương trình sẽ tại ra 4 stream và một socket được biểu diễn như hình vẽ:

§ Xét quá trình gửi file: Socket thì được gọi là clientSocket. Còn luồng (stream) inFromHardDisk có kiểu là FileInputStream, là một luồng nhập đến chương trình; nó thì đính kèm với ổ cứng máy tính. Một khi ta đã chọn được đường dẫn và tên file thì dữ liệu của file đó sẽ được “chảy” vào luồng inFromHardDisk. Còn luồng outToServer có kiểu là ObjectOutputStream là một luồng xuất của chương trình; nó được đính kèm với clientSocket. Dữ liệu mà client gửi đến mạng chảy trong luồng outToServer.

§ Xét quá trình nhận File: Luồng inFromServer có kiểu là ObjectInputStream, là một luồng nhập đến chương trình; nó được đính kèm với socket. Dữ liệu đến từ network sẽ chảy vào luồng inFromServer. Còn luồng outToHardDisk có kiểu là FileOutputStream, là luồng xuất của chương trình; nó được đính kèm với ổ cứng, dữ liệu từ luồng inFromServer sẽ đi vào luồng outToHardDisk, từ đó nó sẽ ghi file ra ổ cứng máy tính

Tại máy chạy server modeChương trình sẽ tại ra 4 stream và một socket được biểu diễn như hình vẽ:

§ Quá trình giao nhận file và send file cũng diễn ra tương tự như bên client với 4 luồng inFromHardDisk, outToHardDisk, inFromClient, outToClient và connectionSocket được biểu diễn như trên hình vẽ. Ở đây ta sẽ không trình bày lại.§ Chỉ có một chú ý quan trọng là trong ứng dụng này cho phép giao nhận file giữa một server và nhiều máy con đồng thời trong một hệ thống mạng nên để tối ưu hoá quá trình hoạt động thì ta khai báo thêm một lớp nữa gọi là ThreadedConnectionSocket, lớp này định nghĩa một đối tượng socket cũng tương tự như connectionSocket đã mô tả ở trên nhưng lớp này là lớp mở rộng của lớp Thread, điều này sẽ tận dụng được tính năng đa xâu (multithreading) của Java. Và những ThreadedConnectionSocket lại được đưa vào lớp Vector. Kết quả là ta có thể giao nhận file giữa một server và nhiều client trong một mạng với tốc độ xử lý tối ưu.

1.2.3.2 Phân tích chi tiết mã nguồn của chương trình2.3.2.1. Quá trình khai báo và tạo đối tượng

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

Page 9: JAVA_ Lập trình TCP Socket

import java.util.*;

java.io và java.net là những gói trong Java. Gói java.io chứa các lớp cho các luồng xuất và luồng nhập. Cụ thể là, gói java.io chứa các lớp ObjectOutputStream, ObjectInputStream, FileInputStream, FileOutputStream, những lớp này đã được sử dụng và mô tả ở trên. Còn gói java.net thì cung cấp những lớp để hỗ trợ mạng. Cụ thể là, nó chứa các lớp Socket và ServerSocket. Đối tượng clientSocket trong chương trình này là một sự dẫn xuất từ lớp Socket. Còn gói java.util thì chứa một vài lớp hữu ích nhất mà ta sẽ dựa vào nó. Trong chương trình này ta có sử dụng lớp Vector của gói này.

import java.awt.*;import java.awt.event.*;import javax.swing.*;

Gói java.awt.* chứa những lớp để tạo ra giao diện người dùng đồ hoạ (GUI), để tạo ra một giao diện hình ảnh để dễ làm việc. Một thành phần GUI là một đối tượng hình ảnh mà người dùng tương tác với nó thông qua chuột hoặc bàn phím. Các thành phần này có thể là các nút (button), nhãn (label), trường văn bản (TextField) v.v... Còn java.awt.event.* để giúp điều quản các sự kiện, khi người dùng thực hiện một hành động như dời chuột, nhấn phím, nhả phím ... Tất cả các hành động này tạo ra một sự kiện. Có một số sự kiện cần được chương trình điều quản một cách rõ rệt, ta sẽ phân tích kỉ điều này khi đi vào từng dòng mã cụ thể. Còn Swing chính là một sự bổ sung AWT, nó cung cấp cho ta nhiều thành phần giao diện người dùng khác mà AWT không có. Trong chương trình này ta có sử dụng thành phần JOptionPane của Swing.

public class FileTransfer extends Frame {public static String strHostAddress = "";public static int intPortNumber = 0, intMaxClients = 0;public static Vector vecConnectionSockets = null;

Ý nghĩa của những dòng mã trên là ta khai báo một lớp FileTransfer chính cho chương trình, lớp này lớp mở rộng của lớp Frame. Một “Frame” là một cửa sổ độc lập. Các khung thường được dùng trong các ứng dụng yêu cầu giao diện người dùng đồ hoạ. Tiếp theo ta khai báo các biến thuộc tính cho class này, strHostAddress là địa chỉ của server mà client kết nối đến, intPortNumber là số thứ tự của cổng để giao nhận dữ liệu, intMaxClients là số máy client tối đa kết nối đến server các biến này sẽ được gán từ phía người dùng. Còn vecConnectionSockets là một Vector chứa các ThreadedConnectionSocket để hổ trợ cho việc nhiều máy client kết nối đến một máy chủ như đã bàn ở trên. Trong trường hợp này ta dùng kiểu Vector vì lớp Java Vector cung cấp một dạng mảng chỉnh cỡ được có thể tăng trưởng khi có thêm một phần tử được bổ sung vào nó.

public static FileTransfer objFileTransfer;

Còn objFileTransfer là một đối tượng thuộc chính lớp FileTransfer, trong trường hợp này ta đã dùng cấu tử trong lớp FileTransfer này. Một “cấu tử” là một phương pháp đặc biệt có cùng tên như của lớp. Ở đoạn mã phía dưới ta sẽ thấy đối tượng objFileTransfer sẽ được khởi tạo bằng cách gọi cấu tử objFileTransfer = new FileTransfer(intChoice).

public static String strFileName = "",strFilePath = "";public static int intCheckChoice = 0;

Hai biến thuộc tính strFileName và strFilePath sẽ chứa tên và đường dẫn của file cần gửi đi. Biến intCheckChoice sẽ giúp cho ta xác định máy đang chạy kiểu server mode hay client mode trong quá trình gửi file. 

public static Socket clientSocket = null;public static ObjectOutputStream outToServer = null;public static ObjectInputStream inFromServer = null;

Các variable clientSocket, outToServer, inFromServer cũng được khai báo, và các tượng này đã được mô tả trên các hình vẽ 1-8 ở trên. Luồng outToServer có kiểu là ObjectOutputStream là một luồng xuất của chương trình; nó được đính kèm với socket. Dữ liệu mà client gửi đến mạng chảy trong luồng outToServer. Luồng inFromServer có kiểu là ObjectInputStream, là một luồng nhập đến chương trình; nó được đính kèm với socket. Dữ liệu đến từ network sẽ chảy vào luồng inFromServer.

Page 10: JAVA_ Lập trình TCP Socket

public static void main (String [] args) throws IOException {BufferedReader stdin = new BufferedReader(new InputStreamReader (System.in));System.out.println("\nSelect Type for this Computer:");System.out.println(" 1. Connect to host (i.e This computer is a client)");System.out.println(" 2. Wait for connections (i.e This computer is a server)");System.out.println();System.out.print("Please make a choice: ");System.out.flush();int intChoice = Integer.parseInt(stdin.readLine());

Đoạn mã sẽ tạo nên một đối tượng luồng stdin có kiểu là BufferedReader. Luồng nhập này sẽ khởi tạo với System.in, để gắn luồng này với công cụ nhập chuẩn (trong trường hợp này là bàn phím). Những lệnh này cho phép chương trình đọc được kí tự nhập từ bàn phím, từ đó gán vào biến intChoice để chọn kiểu server mode hay client mode.

if (intChoice == 1) { // This computer is a clientSystem.out.print("Please type in the host address: ");System.out.flush();strHostAddress = stdin.readLine();System.out.print("Please type in the host port number: ");System.out.flush();intPortNumber = Integer.parseInt(stdin.readLine());}

if (intChoice == 2) { //This computer is a serverSystem.out.print("Give the port number to listen for requests: ");System.out.flush();intPortNumber = Integer.parseInt(stdin.readLine());System.out.print("Give the maximum number of clients for this server: ");System.out.flush();intMaxClients = Integer.parseInt(stdin.readLine());}

Nếu intChoice bằng 2 tức máy chọn kiểu server mode, thì ta sẽ yêu cầu người dùng chọn số thứ tự cổng, chọn số máy client tối đa có thể kết nối đến. Nếu intChoice bằng 1 tức máy chọn kiểu client mode, thì ta sẽ yêu cầu người dùng nhập vào địa chỉ của máy server, nhập vào số thứ tự cổng để giao tiếp với server. Tất cả các giá trị này sau đó được gán vào các biến cần thiết như trong đoạn mã.

objFileTransfer = new FileTransfer(intChoice);

Dòng mã này khởi tạo một đối tượng objFileTransfer bằng cách dùng cấu tử, là hàm FileTransfer có cùng tên với lớp.

public Label lblSelectFile;public Label lblTitle;public Label lblStudentName;public TextField tfFile;public Button btnBrowse;public Button btnSend;public Button btnReset;

Ta tiếp tục khai báo các thành phần giao diện người dùng đồ hoạ, nó được mô tả trên hình vẽ.

public FileTransfer (int c) {setTitle("Java - File Transfer");setSize(300 , 300);

Page 11: JAVA_ Lập trình TCP Socket

setLayout(null);addWindowListener(new WindowAdapter () { public void windowClosing (WindowEvent e) { System.exit(0); } } );

lblTitle = new Label("FILE TRANSFER BY IP");add(lblTitle);lblTitle.setBounds(80,30,300,30);

lblSelectFile = new Label("Select File:");add(lblSelectFile);lblSelectFile.setBounds(15,87,100,20);

lblStudentName = new Label("Thanh Thien - Thanh Tuan");add(lblStudentName);lblStudentName.setBounds(130,270,200,20);

tfFile = new TextField("");add(tfFile);tfFile.setBounds(13,114,200,20);

btnBrowse = new Button("Browse");btnBrowse.addActionListener(new buttonListener());add(btnBrowse);

btnBrowse.setBounds(223,113,50,20);btnSend = new Button("Send");btnSend.addActionListener(new buttonListener());add(btnSend);btnSend.setBounds(64,168,50,20);

btnReset = new Button("Reset");btnReset.addActionListener(new buttonListener());add(btnReset);btnReset.setBounds(120,168,50,20);

show();

Trường hợp này ta dùng cấu tử để khởi tạo một đối tượng, một cấu tử khởi tạo một đối tượng ngay khi tạo. Nó trùng tên với lớp, trong chương trình này định nghĩa một cấu tử của lớp tên FileTransfer. Trong phần đầu của cấu tử đơn giản ta chỉ thiết kế giao diện người dùng, ví dụ mã lệnh lblTitle = new Label("FILE TRANSFER BY IP") sẽ gán nội dung cho nó là “FILE TRANSFER BY IP” và sau đó lệnh add(lblTitle) sẽ đưa nó lên Frame hiện tại, còn câu lệnh lblTitle.setBounds(80,30,300,30) có dạng tổng quát là setBounds(x,y,width,height) để thiết lập vị trí và kích thước của label này.

Bây giờ ta hãy set những câu mã lệnh sau:btnBrowse = new Button("Browse");btnBrowse.addActionListener(new buttonListener());add(btnBrowse);

Đây là quá trình điều quản sự kiện khi nhấn button Browse. Một ‘EventListener’ lắng chờ một sự kiện cụ thể do đối tượng phát sinh. Đến lượt nó sẽ gọi các phương pháp điều quản sự kiện, có tên là ‘bộ điều quản sự kiện’. Một bộ lắng chờ sự kiện [event listener] cung cấp các phương pháp để điều quản sự kiện này. Lớp thực thi bộ lắng chờ trong trường hợp này là lớp buttonListener. Lớp này sẽ được định nghĩa ở phần sau của mã chương trình.

================================================== ==================2.3.2.2. Quá trình nhận file (Receive file)

Nhận file bên phía clientTiếp theo đây sẽ là một trong những phần quan trọng của chương trình, phần này mô tả quá trình nhận file bên phía client

Page 12: JAVA_ Lập trình TCP Socket

if (c == 1) { // Client receives fileintCheckChoice = 1;

Biến c là sẽ xác định máy tính đang chạy ở mode nào, trong trường hợp này c=1 tức máy đang chạy ở mode client, ta gán cho biến intCheckChoice = 1 để tiếp tục xác định đây là mode client trong trường hợp nếu có gửi file từ client.

try {clientSocket = new Socket (strHostAddress,intPortNumber);outToServer = new ObjectOutputStream(clientSocket.getOutputStream()) ;outToServer.flush();inFromServer = new ObjectInputStream(clientSocket.getInputStream());

Tiếp theo ta tiến hành tạo ra một clientSocket từ các giá trị có sẳn strHostAddress và intPortNumber. Ta cũng tạo ra hai luồng outToServer và inFromServer như đã đề cập ở đầu chương trình, phương thức outToServer.flush() sẽ xả hết tất cả những gì còn trong luồng outToServer này ra ngoài.Vì đây là quá trình nhận file tại client (từ server) nên ta sẽ chỉ cần sử dụng luồng inFromServer, luồng này đón nhận những dữ liệu từ clientSocket bằng phương thức clientSocket.getInputStream(). 

int intFlag = 0;while (true) {Object objRecieved = inFromServer.readObject();

Còn biến cờ intFlag giúp ta thực hiện việc xử lý dữ liệu gửi từ server qua 3 giai đoạn bằng cách dùng lệnh switch. Còn đối tượng objRecieved đọc dữ liệu từ luồng inFromServer.

switch (intFlag) {case 0:

if (objRecieved.equals("IsFileTransfered")) {intFlag++;}break;

Giai đoạn 1, chương trình sẽ kiểm tra điều kiện objRecieved.equals("IsFileTransfered") bởi vì trong quá trình gửi file đi từ server, chương trình bên server đã ghi vào đối tượng gửi đi một chuỗi "IsFileTransfered", ở quá trình nhận ở client nếu objRecieved.equals("IsFileTransfered") = true thì ta biết chắc rằng dữ liệu đã được gửi đi bằng cùng một chương trình bên server, ta kết thúc giai đoạn này bằng việc tăng biến intFlag thêm một đơn vị . 

case 1:strFileName = (String) objRecieved;int intOption = JOptionPane.showConfirmDialog(this,clientSocket.ge tInetAddress().getHostName()+" is sending you "+strFileName+"!\nDo you want to recieve it?","Recieve Confirm",JOptionPane.YES_NO_OPTION,JOptionPane.QUE STION_MESSAGE);

if (intOption == JOptionPane.YES_OPTION) {intFlag++;

} else {intFlag = 0;}break;

Qua giai đoạn 2, lúc này intFlag có giá trị là 1, ta lấy ra từ objRecieved một giá trị kiểu string chứa tên file, tên file này đã được thêm vào objRecieved trong quá trình gửi file từ server, lúc này một hộp thoại sẽ hiện ra bên phía server để xác nhận rằng ta có muốn nhận file đó không.

Nếu ta chọn “Yes” thì intFlag sẽ được tăng thêm một đơn vị nửa, còn ngược lại thì intFlag sẽ trở về giá trị 0. 

case 2:byte[] arrByteOfReceivedFile = (byte[])objRecieved;FileOutputStream outToHardDisk = new FileOutputStream(strFileName); outToHardDisk.write(arrByteOfReceivedFile);intFlag = 0; JOptionPane.showMessageDialog(this,"File Recieved!","Confirmation",JOptionPane.INFORMATION_ MESSAGE);break;}Thread.yield();}} catch (Exception e) {System.out.println(e);}}

Page 13: JAVA_ Lập trình TCP Socket

Qua đến giai đoạn 3, (intFlag lúc này bằng 2) sau khi ta đã chọn Yes thì dữ liệu của file chứa trong objRecieved sẽ được đọc vào mảng byte arrByteOfReceivedFile thông qua lệnh byte[] arrByteOfReceivedFile = (byte[])objRecieved

Luồng outToHardDisk sau đó sẽ tạo ra một file có tên trùng với tên file đã gửi, dữ liệu từ arrByteOfReceivedFile sẽ được “chảy” vào luồng này. Như vậy lúc này đây bên ổ cứng của máy client mới được tạo một file giống có nội dung và tên giống hệt như file đã được gửi từ server, file này được nằm cùng thư mục với file chương trình. Chương trình cũng hiển thị một hộp thoại để báo cho ta biết file đã được nhận.Câu mã lệnh Thread.yield() có tác dụng khi mà một xâu (thread) không có việc gì để làm nửa, thì nó có thể gọi phương thức yield() này để báo cho bộ lập lịch (scheduler) biết để thực thi một thread khác. Còn đoạn mã try ... catch được dùng để điều quản các ngoại lệ (exception) hay còn gọi là bẫy lỗi.

Nhận file bên serverBây giờ ta xét tiếp đến trường hợp nhận file bên server:

if (c == 2) { // Server receives filevecConnectionSockets = new Vector();intCheckChoice = 2;

try {ServerSocket welcomeSocket = new ServerSocket(intPortNumber,intMaxClients);

while (true) {vecConnectionSockets.addElement(new ThreadedConnectionSocket(welcomeSocket.accept()));Thread.yield();}} catch (IOException ioe) {System.out.println(ioe);}}Như ta đã biết thì chương trình bên server phải có nhiệm vụ tạo ra một welcomeSocket để trong quá trình three-way handshake thực hiện sẽ tạo ra kết nối TCP giữa máy server và máy client. welcomeSocket này được tạo ra dựa vào hai giá trị intPortNumber và intMaxClients đã được gán giá trị trước đó. Và cũng đã nói từ trước chương trình này bên server có khả năng đồng thời transfer file với nhiều máy client nên nếu chỉ tạo ra một connectionSocket thì không đủ nên ta phải ra một Vector các connectionSocket và để tối ưu hoá quá trình thực thi thì ta dùng chức năng multithreading của Java bằng cách tạo sử dụng các đối tượng ConnectionSocket ở dạng được tích hợp sẳn trong class ThreadedConnectionSocket. Các đối tượng của class ThreadedConnectionSocket được định nghĩa là welcomeSocket.accept() và nói được đưa vào Vector thông qua phương thức addElement của đối tượng Vector vecConnectionSockets. Sau đây ta sẽ tìm hiểu kỉ về class ThreadedConnectionSocket mới được tạo thêm.

class ThreadedConnectionSocket extends Thread {public Socket connectionSocket;public ObjectInputStream inFromClient;public ObjectOutputStream outToClient;

Class ThreadedConnectionSocket này là một lớp mở rộng của lớp Thread nên nó có tính năng đa xâu, các thuộc tính của lớp này bao gồm một connectionSocket và hai luồng như đã đề cập ở phần đầu là inFromClient và outToClient. 

public ThreadedConnectionSocket (Socket s) {connectionSocket = s;

try {outToClient = new ObjectOutputStream(connectionSocket.getOutputStrea m());outToClient.flush();inFromClient = new ObjectInputStream(connectionSocket.getInputStream( ));} catch (Exception e) {System.out.println(e);}start();}Ta cũng định nghĩa một cấu tử trong lớp này, phương thức outToClient.flush() sẽ xả hết tất cả những gì còn trong luồng outToClient này ra ngoài. Vì đây là quá trình nhận file tại server (từ client) nên ta sẽ chỉ cần sử dụng luồng inFromClient, luồng này đón nhận những dữ liệu từ connectionSocket bằng phương thức connectionSocket.getInputStream(). Chương trình bắt đầu thi hành một xâu bằng một lệnh gọi phương pháp start() thuộc về lớp Thread. Đến lượt phương pháp này triệu gọi phương pháp run() ở đó định nghĩa công việc được thực hiện. 

public void run () {

try {int intFlag = 0;String strFileName = "";

while (true) {Object objRecieved = inFromClient.readObject();

Page 14: JAVA_ Lập trình TCP Socket

switch (intFlag) {case 0:

if (objRecieved.equals("IsFileTransfered")) {intFlag++;}break;case 1:strFileName = (String) objRecieved;int intOption = JOptionPane.showConfirmDialog(null,connectionSocke t.getInetAddress().getHostName()+" is sending you "+strFileName+"!\nDo you want to recieve it?","Recieve Confirm",JOptionPane.YES_NO_OPTION,JOptionPane.QUE STION_MESSAGE);

if (intOption == JOptionPane.YES_OPTION) {intFlag++;

} else {intFlag = 0;}break;case 2:byte[] arrByteOfReceivedFile = (byte[])objRecieved;FileOutputStream outToHardDisk = new FileOutputStream(strFileName); outToHardDisk.write(arrByteOfReceivedFile);intFlag = 0; JOptionPane.showMessageDialog(null,"File Recieved!","Confirmation",JOptionPane.INFORMATION_ MESSAGE);break;}Thread.yield();}} catch (Exception e) {System.out.println(e);}}}Nếu quan sát kỉ thì ta sẽ thấy cách xử lý của nó hoàn toàn giống như quá trình nhận file bên client đã xét ở trên. Những dòng mã lệnh trong phương pháp run() hoàn toàn tương tự như quá trình nhận file bên client, biến cờ intFlag giúp ta thực hiện việc xử lý dữ liệu gửi từ client qua 3 giai đoạn bằng lệnh switch. Ở đây ta sẽ không cần phân tích lại ba giai đoạn đó..Như vậy cho đến lúc này đây ta đã phân tích xong một cách chi tiết quá trình nhận file tại client từ server và cũng đã phân tích sự giống nhau và khác nhau đối với quá trình nhận file tại server từ client (so với nhận file tại client). Ở phần kế tiếp ta sẽ đi vào nghiên cứu quá trình gửi file tại client và quá trình gửi file tại server.

================================================== ===========

2.3.2.3. Quá trình gửi file (Send file)

public static String showDialog () {FileDialog fd = new FileDialog(new Frame(),"Select File...",FileDialog.LOAD);fd.show();return fd.getDirectory()+fd.getFile();}Đây là một hàm nhỏ để giúp cho ta có thế chọn được file cùng với đường dẫn của một file thông qua một hộp thoại quen thuộc. Kết quả trả về sẽ là tên thư mục + tên file đã chọn. Hàm này sẽ được dùng ở đoạn mã sau của chương trình.Như đã đề cập ở phần trước, thì một ‘EventListener’ lắng chờ một sự kiện cụ thể do đối tượng phát sinh. Đến lượt nó sẽ gọi các phương pháp điều quản sự kiện, có tên là ‘bộ điều quản sự kiện’. Một bộ lắng chờ sự kiện [event listener] cung cấp các phương pháp để điều quản sự kiện này. Lớp thực thi bộ lắng chờ trong trường hợp này là lớp buttonListener.

private class buttonListener implements ActionListener {

public void actionPerformed (ActionEvent ae) {byte[] arrByteOfSentFile = null;

if (ae.getSource() == btnBrowse) {strFilePath = showDialog();tfFile.setText(strFilePath);int intIndex = strFilePath.lastIndexOf("\\");strFileName = strFilePath.substring(intIndex+1);}Như ta thấy trong đoạn mã này thì khi người dùng click vào button Browse (ae.getSource() == btnBrowse) thì chương trình sẽ gọi hàm showDialog() đã được định nghĩa trước, hàm này sẽ trả vào biến strFilePath tên và đường

Page 15: JAVA_ Lập trình TCP Socket

dẫn đầy đủ của một file được chọn, sau đó ta lại hiển thị giá trị này lên TextField tfFile, sau đó bằng một vài hàm xử lý chuỗi thì ta đã lấy được giá trị tên file và gán vào biến strFileName. Biến strFileName này chỉ chứa tên file (ví dụ là TestTransferFile.txt) mà không hề có chứa đường dẫn thư mục.Ở phần kế tiếp ta sẽ xét phần quan trọng của chương trình, đó là phần send file (khi người dùng nhấn button Send)

if (ae.getSource() == btnSend) {

try {FileInputStream inFromHardDisk = new FileInputStream (strFilePath);int size = inFromHardDisk.available();arrByteOfSentFile = new byte[size];inFromHardDisk.read(arrByteOfSentFile,0,size);

Chương trình sẽ tạo ra một luồng nhập có tên inFromHardDisk có kiểu là FileInputStream, nó liên kết với file với đường dẫn nằm trong biến strFilePath, biến size sẽ xác định được số byte có thể đọc được mà không bị phong toả. Câu lệnh inFromHardDisk.read(arrByteOfSentFile,0,size) sẽ đọc dữ liệu từ strFilePath và ghi vào mảng arrByteOfSentFile. 

Gửi file bên server

if (intCheckChoice == 2) {// Send file from server 

for (int i=0;i<vecConnectionSockets.size();i++) {ThreadedConnectionSocket tempConnectionSocket = (ThreadedConnectionSocket)vecConnectionSockets.ele mentAt(i);tempConnectionSocket.outToClient.writeObject("IsFi leTransfered");tempConnectionSocket.outToClient.flush();tempConnectionSocket.outToClient.writeObject(strFi leName);tempConnectionSocket.outToClient.flush();tempConnectionSocket.outToClient.writeObject(arrBy teOfSentFile );tempConnectionSocket.outToClient.flush();}}intCheckChoice bằng 2, tức ta đang xét trường hợp gửi file từ server đi, vì server có thể gửi file cho nhiều client nên ta, phải tạo một vòng lặp for với số lần lặp bằng số client kết nối đến server, giá trị này chính là bằng vecConnectionSockets.size(). Trong mỗi lần lặp thì ta tiến hành send file bằng các bước như sau:- Khởi tạo một tempConnectionSocket thuộc class ThreadedConnectionSocket đã tạo trước đó.- Ghi vào đối tượng này một chuỗi "IsFileTransfered" để giúp xác nhận lại file bên nhận.- Đưa dữ liệu từ mảng byte arrByteOfSentFile (mảng đang chứa dữ liệu của file cần gửi) vào đối tượng tempConnectionSocket để đưa dữ liệu ra socket.- Ứng với mỗi bước thì ta dùng phương thức flush( ) để ghi ra dữ liệu của luồng, điều này bảo đảm rằng tất cả dữ liệu nằm ở những luồng dưới đã được gửi đi.

Gửi file bên client

if (intCheckChoice == 1) { // Send file from clientoutToServer.writeObject("IsFileTransfered");outToServer.flush();outToServer.writeObject(strFileName);outToServer.flush();outToServer.writeObject(arrByteOfSentFile);outToServer.flush();}intCheckChoice bằng 1, tức ta đang xét trường hợp gửi file từ client đi, mỗi client chỉ có thể kết nối đến một server nên ta dùng luồng outToServer đã khai báo trước từ đầu chương trình. Ở đây cũng xin nhắc lại là luồng outToServer có kiểu là ObjectOutputStream là một luồng xuất của chương trình; nó được đính kèm với clientSocket. Dữ liệu mà client gửi đến mạng chảy trong luồng outToServer. Ta thực hiện qua các bước cũng tương tự như bên server.- Ghi vào luồng này một chuỗi "IsFileTransfered" để giúp xác nhận lại file bên nhận- Đưa dữ liệu từ mảng byte arrByteOfSentFile (mảng đang chứa dữ liệu của file cần gửi) vào luồng outToServer, luồng này được gắn với clientSocket.- Ứng với mỗi bước thì ta dùng phương thức flush( ) để ghi ra dữ liệu của luồng, điều này bảo đảm rằng tất cảu dữ liệu nằm ở những luồng dưới đã được gửi đi.

JOptionPane.showMessageDialog(null,"File Sent!","Confirmation",JOptionPane.INFORMATION_MESS AGE);

Sau khi gửi dữ liệu ra socket thành công thì ta hiện một hộp thoại thông báo File đã được gửi đi.

if (ae.getSource() == btnReset) {tfFile.setText("");}

Còn trong trường hợp người dùng nhấn button Reset thì chương trình chỉ xoá trắng TextField tfFile và chờ đợi

Page 16: JAVA_ Lập trình TCP Socket

người dùng thao tác lại từ đầu.1.2.4. Nhận xétNhư vậy ta đã vừa phân tích xong từng chức năng, từ dòng mã một trong chương trình giao nhận file bằng IP. Ta hãy nhớ lại rằng TCP cung cấp dịch vụ giao nhận dữ liệu đáng tin. Điều này có nghĩa rằng, nếu file trong quá trình giao nhận file trong mạng bị hư hỏng thì giao thức TCP sẽ tiến hành giao nhận lại file, để bảo đảm rằng dữ liệu luôn được phân phối chính xác. Sự giao nhận trở lại này hoàn toàn ẩn giấu đối với chương trình ứng dụng, tức là người lập trình không cần phải quan tâm đến sự truyền nhận file trở lại (retransmission) này.

Để kiểm tra chương trình này thì ta phải dịch file FileTransfer.java (hay chép những file đã biên dịch) ở cả bên server lẫn client. Ta cũng phải chạy chương trình ở máy chạy server mode trước. Điều này sẽ tạo ra một process bên server để chờ đợi sự giao tiếp từ phía máy chạy client mode. Sau đó ta chạy chương trình ở client mode ở máy client, ta phải bảo đảm rằng phải bao gồm đúng tên địa chỉ của server (host address), và phải chọn đúng số thứ tự cổng đã thiết lập sẳn bên server. Trong một vài trường hợp hiếm gặp thì chương trình sẽ không giao nhận file được do bị firewall, vì thế ta phải bảo đảm rằng cổng mà ta chọn không bị block bởi firewall. Sau khi chạy xong client mode thì chương trình sẽ tạo ra một process ở client và thiết lập một TCP connnection giữa máy chạy mode server và máy chạy mode client. Cuối cùng, để giao nhận file thì ta chỉ việc chọn file trong ổ cứng và nhấn Send để gửi đi.

================================================== ========

1.3. Chương trình 2: Giao nhận xử lý chuỗi1.3.1. Cách thức hoạt độngChương trình này có đặc điểm như sau:1. Một máy khách đọc một dòng chữ từ công cụ nhập chuẩn (bàn phím) và gửi dòng này ra socket của nó và từ đó nó được gửi đến server2. Một máy chủ đọc một dòng chữ từ connection socket của nó3. Máy chủ sẽ thực hiện việc đảo ngược chuỗi (dòng) đó.4. Máy chủ sẽ gửi dòng chữ đã thay đổi ra connection socket của nó và từ đó dòng chữ sẽ được gửi qua máy khách5. Máy khách đọc dòng chữ đã thay đổi từ socket của nó và in dòng chữ đó ra màn hình.

Chương trình ở máy client có tên là EchoClient.java, và chương trình ở máy chủ có tên là EchoServer.java. 

Một khi cả hai chương trình đã được biên dịch ở host tương ứng, chương trình bên server phải được thực thi trước bên server, nó sẽ tạo ra một process tại server. Như đã đề cập ở những phần trên, tiến trình (process) ở máy chủ sẽ chờ đợi cho đến khi có sự giao tiếp của tiến trình ở máy khách. Khi chương trình ở máy khách được thực thi, một tiến trình được tạo ra ở máy khách, và tiến trình này sẽ tiếp xúc với máy chủ và thiết lập một TCP connection với nó. Người dùng tại máy khách lúc này đây có thể sử dụng ứng dụng này để gửi từng dòng và nhận về dòng chữ đã đảo ngược.

1.3.2. EchoClient.javaCũng tương tự như chương trình 1 giao nhận file thì ở đây, chương trình EchoClient tạo ra 3 luồng và một socket như được mô tả ở hình bên dưới 

Socket thì được gọi là clientSocket. Còn luồng (stream) inFromUser có kiểu là BufferedReader, là một luồng nhập đến chương trình; nó được đính kèm bộ nhập chuẩn, trong trường hợp này là bàn phím. Khi người sử dụng gõ kí tự vào bàn phím máy tính, dòng kí tự này sẽ chảy vào luồng inFromUser. Luồng inFromServer là một luồng nhập khác của chương trình; nó được gắn vào socket. Những kí tự đến từ mạng (network) sẽ chảy vào luồng inFromServer. Cuối cùng, luồng outToServer là một luồng xuất của chương trình của chương trình; nó cũng được gắn vào socket. Những kí tự mà máy khách gửi đến mạng sẽ chảy vào luồng outToServer.

Phần ghi chú và giải thích được ghi kèm trong mã nguồn của chương trìnhCode:import java.net.*;import java.io.*;

public class EchoClient {

public static void main(String args[]){if (args.length !=1){System.out.println("Usage: java EchoClient <serverAddr>");return;}

// Tạo đối tượng liên kết với máy chủ (thực tế chỉ là liên kết với clientSocket)ClientConnect client = new ClientConnect(args[0],3456);

// Nhận dữ liệu từ bàn phím và gửi đến máy chủ xử lýclient.requestServer();

// Cắt đứt kết nối với đối tượng liên kếtclient.shutdown();}}

Page 17: JAVA_ Lập trình TCP Socket

// Thiết kế lớp liên kết với máy chủ thông qua socket và nhận dữ liệu từ bàn phímclass ClientConnect {

// Lưu giữ đối tượng kết nốiSocket clientSocket;

// Luồng dùng gửi dữ liệu đến clientSocket DataOutputStream outToServer;

// Luồng dùng đọc dữ liệu do máy chủ gửi về BufferedReader inFromServer; 

/*Phương thức khởi dựng tạo kết nối với máy chủ theo địa chỉ destination và cổng port*/public ClientConnect(String destination,int port){try{

// Tạo socket kết nối phía clientclientSocket = new Socket(destination,port);

// Lấy vào luồng nhập dùng đọc dữ liệu do máy chủ gửi vềinFromServer = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

// Lấy về luồng xuất dùng gửi dữ liệu đến socket từ đó chuyển đến máy chủoutToServer= new DataOutputStream( clientSocket.getOutputStream());System.out.println("Connected to server at port 3456.");} catch (Exception e) {

// Nếu gặp lỗi thì in lỗi ra màn hìnhSystem.out.println(e);}}

/*Phương thức này sẽ nhận chuỗi dữ liệu từ bàn phím và gửi đến máy chủ xử lý*/public void requestServer(){

// Tạo luồng để đọc dữ liệu từ bàn phímBufferedReader inFromUser = new BufferedReader(new InputStreamReader(System.in));

boolean finished=false;do {try{

// Tạo menu lựa chọn. S: gửi dữ liệu, R: nhận dữ liệu, Q: chấm dứtSystem.out.print("Send, receive, or quit (S/R/Q): ");System.out.flush();

String line = inFromUser.readLine();

if(line.length()>0){line=line.toUpperCase();switch (line.charAt(0)){

// Nếu người dùng chọn S, dữ liệu nhập vào sẽ được gửi đến clientSocket, từ đó chuyển qua máy chủcase 'S':String sendLine = inFromUser.readLine();outToServer.writeBytes(sendLine);outToServer.write(13);outToServer.write(10);outToServer.flush();break;

// Nếu người dùng chọn R, dữ liệu do máy chủ xử lý sẽ được nhận về và in racase 'R':int inByte;System.out.print(">>>>");while ((inByte = inFromServer.read()) != '\n')System.out.write(inByte);System.out.println();break;

Page 18: JAVA_ Lập trình TCP Socket

// Nếu người dùng chọn Q, chương trình sẽ chấm dứtcase 'Q':finished=true;break;

default:break;}}}catch (Exception e){

// Nếu lỗi xuất nhập thì in lỗi ra màn hìnhSystem.out.println(e);}} while(!finished);}

/*Phương thức này được dùng để đóng kết nối từ máy khách với máy chủ*/public void shutdown(){try{clientSocket.close();}catch (IOException ex){System.out.println("IO error closing socket");}}} 1.3.3. EchoServer.javaChương trình EchoServer cũng được giải thích tương tự như chương trình EchoClient. Phần ghi chú và giải thích được ghi kèm trong mã nguồn của chương trìnhCode:import java.net.*;import java.io.*;

public class EchoServer {

public static void main(String args[]){try{

/*Tạo đối tượng ServerSocket dùng để lắng nghe kết nối từ các máy khách gửi đến cổng 3456*/ServerSocket welcomeSocket = new ServerSocket(3456);int localPort = welcomeSocket.getLocalPort();System.out.println("Echo Server is listening on port "+localPort+".");

//Chờ đợi kết nối từ máy khách/*Lưu ý: Đến đây chương trình sẽ dừng lại và chờ đợi cho đến khi có kết nối xảy ra*/Socket connectionSocket = welcomeSocket.accept();/*Có kết nối xảy ra. Lấy các thông tin từ máy khách và cho in ra màn hình*/

String destName = connectionSocket.getInetAddress().getHostName();int destPort = connectionSocket.getPort();System.out.println("Accepted connection to "+destName +" on port "+destPort+".");

//Lấy về luồng nhập để đọc dữ liệu từ connectionSocket, do máy khách gửi đến/*Lưu ý: Ta đưa luồng nhập vào bộ lọc luồng để chuyển đổi thành luồng BufferReader có khả năng đọc được một chuỗi ký tự bằng phương thức readLine()*/ BufferedReader inFromClient = new BufferedReader(new InputStreamReader(connectionSocket.getInputStream( )));

// Lấy về luồng xuất để ghi dữ liệu vào connectionSocket, từ đó chuyển đến máy kháchDataOutputStream outToClient = new DataOutputStream(connectionSocket.getOutputStream( ));

// Chờ đọc vào xử lý dữ liệu do máy khách gửi đến

Page 19: JAVA_ Lập trình TCP Socket

boolean finished = false;do {

// Đọc một chuỗi kí tự từ connectionSocket do máy khách gửi đến String inLine = inFromClient.readLine();

// In chuỗi nhận được ra màn hìnhSystem.out.println("Received: "+inLine);

// So sánh chuỗi gửi đến. Nếu là quit thì chấm dứtif(inLine.equalsIgnoreCase("quit")) finished=true;

// Xử lý đảo ngược chuỗiString outLine=new EchoString(inLine.trim()).getString();

// Gửi trả kết quả về connectionSocket từ đó trả về máy kháchfor(int i=0;i<outLine.length();++i)outToClient.write((byte)outLine.charAt(i));outToClient.write(13);outToClient.write(10);outToClient.flush();

System.out.println("Sent: "+outLine);} while(!finished);

// Đóng luồng và các kết nốiinFromClient.close();outToClient.close();connectionSocket.close();welcomeSocket.close();

}catch (IOException e){

// Nếu có lỗi thì in ra màn hìnhSystem.out.println(e);}}}

//Lớp này dùng để xử lý và đảo ngược nội dung của một chuỗiclass EchoString {String s;public EchoString(String in){

int len = in.length();char outChars[] = new char[len];

//Đảo ngược kí tự từ trước ra saufor(int i=0;i<len;++i)outChars[len-1-i]=in.charAt(i);s = String.valueOf(outChars);}

public String getString(){return s;}} 1.3.4. Kết quả

Và sau đây là quá trình trao đổi giữa máy khách và máy chủ:

Máy khách Máy chủSend, receive, or quit (S/R/Q): SHello Server !Send, receive, or quit (S/R/Q): R>>>>! revreS olleH Received: Hello Server !Sent: ! revreS olleHSend, receive, or quit (S/R/Q): s123456789Send, receive, or quit (S/R/Q): r>>>>987654321 Received: 123456789Sent: 987654321Send, receive, or quit (S/R/Q): SHow are you?Send, receive, or quit (S/R/Q): r>>>>?uoy era woH Received: How are you?Sent: ?uoy era woHSend, receive, or quit (S/R/Q): q 

1.4. Tài liệu tham khảo:1. Giáo trình Lập trình Hướng đối tượng JAVA - Ngọc Anh Thư Press - NXB Thống Kê2. Computer Networking - By James F. Kurose and Keith W. Ross - Addison Wesley

Page 20: JAVA_ Lập trình TCP Socket

3. Website http://www.planet-source-code.com4. JAVA Lập trình mạng - Nguyễn Phương Lan và Hoàng Đức Hải - NXB Giáo Dục