Lập trình Socket với TCP

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ủ.

pdf30 trang | Chia sẻ: diunt88 | Lượt xem: 4219 | Lượt tải: 1download
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ó đư
Tài liệu liên quan