rong 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ủ.
30 trang |
Chia sẻ: diunt88 | Lượt xem: 4370 | Lượt tải: 1
Bạn đang xem trước 20 trang tài liệu Lập trình Socket với TCP, để xem tài liệu hoàn chỉnh bạn click vào nút DOWNLOAD ở trên
[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
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ự 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). 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 IP
1.2.1. Cách thức hoạt động
Chươ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 mode
Tuỳ 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 mode
Tươ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 file
Sao 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.
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:
// 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 client
System.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 server
System.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());
}
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 file
intCheckChoice = 1;
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 file
vecConnectionSockets = 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);}
}
}
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 client
outToServer.writeObject("IsFileTransfered");
outToServer.flush();
outToServer.writeObject(strFileName);
outToServer.flush();
outToServer.writeObject(arrByteOfSentFile);
outToServer.flush();
}
JOptionPane.showMessageDialog(null,"File
Sent!","Confirmation",JOptionPane.INFORMATION_MESS AGE);
} 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) {
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ình
Chú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ình
Tại máy chạy client mode
Chươ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ó đư