java實現兩臺電腦間TCP協議文件傳輸

  記錄下以前所作的客戶端向服務端發送文件的小項目,總結下學習到的一些方法與思路。java

注:本文參考自《黑馬程序員》視頻。程序員

  首先明確需求,在同一局域網下的機器人A想給喜歡了好久的機器人B發送情書,可是機器人B事先並不知道小A的心思,那麼做爲月老(紅娘)該如何幫助他們呢?正則表達式

  而後創建模型並拆分需求。這裏兩臺主機使用網線直連,在物理層上確保創建了鏈接,接下來即是利用相應的協議將信息從電腦A傳給電腦B。在這一步上,能夠將此過程抽象爲網絡+I/O(Input、Output)的過程。若是能在一臺電腦上實現文件之間的傳輸,再加上相互的網絡協議,羞澀的A不就能夠將情書發送給B了嗎?所以要先解決在一臺電腦上傳輸信息的問題。爲了在網絡上傳輸,使用必要的協議是必要的,TCP/IP協議簇就是爲了解決計算機間通訊而生,而這裏主要用到UDP和TCP兩種協議。當小A能夠向小B發送情書後,又出現了衆多的追求者,那麼小B如何去處理這麼多的併發任務呢?這時便要用到多線程的技術。編程

  所以接下來將分別介紹此過程當中所用到了I/O流(最基礎)、網絡編程(最重要)、多線程知識(較重要)和其中一些小技巧。數組

1、I/O流

  I/O流用來處理設備之間的數據傳輸,Java對數據的傳輸經過流的方式。服務器

  流按操做數據分爲兩種:字節流與字符流。若是數據是文本類型,那麼須要使用字符流;若是是其餘類型,那麼使用字節流。簡單來講,字符流=字節流+編碼表。網絡

  流按流向分爲:輸入流(將硬盤中的數據讀入內存),輸出流(將內存中的數據寫入硬盤)。多線程

  簡單來講,想要將某文件傳到目的地,須要將此文件關聯輸入流,而後將輸入流中的信息寫入到輸出流中。將目的關聯輸出流,就能夠將信息傳輸到目的地了。併發

  Java提供了大量的流對象可供使用,其中有兩大基類,字節流的兩個頂層父InputStream與OutputStream;字符流的兩個頂層父類Reader與Writer。這些體系的子類都以父類名做爲後綴,而子類名的前綴就是該對象的功能。ide

流對象技巧

  下提供4個明確的要點,只要明確如下幾點就能比較清晰的確認使用哪幾個流對象。

  1, 明確源和目的(匯)

  • 源     :InputStream       Reader
  • 目的  :OutputStream    Writer

  2, 明確數據是不是純文本數據

  • 源   :是純文本  :Reader

           非純文本  :InputStream

  • 目的:是純文本  :Writer

           非純文本  :OutputStream

  到這裏就能夠明確需求中具體要用哪一個體系。

  3, 明確具體的設備。

  • 源設備:

        硬盤:File

        鍵盤:System.in

        內存:數組

        網絡:Socket流

  • 目的設備:

        硬盤:File

        控制檯:System.out

        內存:數組

        網絡:Socket流

  4,是否須要其餘額外功能。

a) 是否須要高效(緩衝區)?

是,就加上buffer。

b) 是否須要轉換?

  • 源:InputStreamReader 字節流->字符流
  • 目的:OutputStreamWriter 字符流->字節流

  在這裏源爲硬盤,目的也爲硬盤,數據類型爲情書,多是文字的情書,也多是小A唱的歌《情書》,所以使用字節流比較好。所以分析下來源是文件File+字節流InputStream->FileInputStream,目的是文件File+字節流OutputStream->FileOutputStream,  接下來即是數據如何從輸入流到輸出流的問題。

  兩個流之間沒有直接關係,須要使用緩衝區來做爲中轉,爲了將讀入流與緩衝區關聯,首先自定義一個緩衝區數組byte[1024]。爲了將讀入流與緩衝區關聯,使用fis.read(buf);爲了將寫出流與緩衝區關聯,使用fos.write(buf,0,len)。爲了將流中的文件寫出到輸出源中,要使用fos.flush或者fos.close。flush能夠屢次刷新,而close只能使用一次。

  代碼以下,其中讀寫中會遇到的異常爲了程序的清晰閱讀,直接拋出,建議實際使用時利用try,catch處理。

 1 public class IODemo {
 2     /**
 3      * 需求:將指定文件從D盤目錄d:\1下移動到d:\2下
 4      * @param args
 5      * @throws IOException 
 6      */
 7     public static void main(String[] args) throws IOException {
 8         //1,明確源和目的,創建輸入流和輸出流
 9         //注意路徑須要使用\\,將\轉義
10         FileInputStream fis = new FileInputStream("d:\\1\\1.png");//源爲d盤1目錄下文件1.png
11         FileOutputStream fos = new FileOutputStream("d:\\2\\2.png");//目的爲d盤2目錄下文件2.png
12         //2,使用自定義緩衝區將輸入流和輸出流關聯起來
13         byte[] buf = new byte[1024];//定義1024byte的緩衝區
14         int len = 0;//輸入流讀到緩衝區中的長度
15         //3,將數據從輸入流讀入緩衝區
16         //循環讀入,當讀到文件最後,會獲得值-1
17         while((len=fis.read(buf))!=-1){
18             fos.write(buf,0,len);//將讀到長度部分寫入輸出流
19         }
20         //4,關流,須要關閉底層資源
21         fis.close();
22         fos.close();
23     }
24 }

   這樣小A就能夠本身給本身發送情書啦,接下來怎麼利用網絡給小A和小B前線搭橋呢?

2、網絡編程

  在I/O技術中,網絡的源設備都是Socket流,所以網絡能夠簡單理解爲將I/O中的設備換成了Socket。

  首先要明確的是傳輸協議使用UDP仍是TCP。這裏直接使用TCP傳輸。

TCP

  TCP是傳輸控制協議,具體的特色有如下幾點:

  • 創建鏈接,造成傳輸數據的通道
  • 在鏈接中進行大數據量傳輸
  • 經過三次握手完成鏈接,是可靠協議
  • 必須創建鏈接,效率會稍低

Socket套接字

  無論使用UDP仍是TCP,都須要使用Socket套接字,Socket就是爲網絡服務提供的一種機制。通訊的兩端都有Socket,網絡通訊其實就是Socket間的通訊,數據在兩個Socket間經過I/O傳輸

TCP傳輸

  TCP傳輸的兩端分別爲客戶端與服務端,java中對應的對象爲Socket與ServerSocket。須要分別創建客戶端與服務端,在創建鏈接後經過Socket中的IO流進行數據的傳輸,而後關閉Socket。

  一樣,客戶端與服務器端是兩個獨立的應用程序。

  Socket類實現客戶端套接字,ServerSocket類實現服務器套接字。

  客戶端向服務端發送信息創建通道,通道創建後服務器端向客戶端發送信息。

  客戶端通常初始化時要指定對方的IP地址和端口,IP地址能夠是IP對象,也能夠是IP對象字符串表現形式。

  創建通道後,信息傳輸經過Socket流,爲底層創建好的,又有輸入和輸出,想要獲取輸入或輸出流對象,找Socket來獲取。爲字節流。getInputStream()和getOutputStream()方法來獲取輸入流和輸出流。

  服務端獲取到客戶端Socket對象,經過其對象與Cilent進行通信。

  客戶端的輸出對應服務端的輸入,服務端的輸出對應客戶端的輸入。

  下面將以前的功能複雜化,變成將客戶端硬盤上的文件發送至服務端。

客戶端與服務端的演示

客戶端

 1 //客戶端發數據到服務端
 2         /*
 3          * TCP傳輸,客戶端創建的過程
 4          * 1,建立TCP客戶端Socket服務,使用的是Socket對象。
 5          *         建議該對象一建立就明確目的地。要鏈接的主機。
 6          * 2,若是鏈接創建成功,說明數據傳輸通道已創建。
 7          *         該通道就是Socket流,是底層創建好的。既然是流,說明這裏既有輸入,又有輸出。
 8          * 3,使用輸出流,將數據寫出。
 9          * 4,關閉資源。
10          */
11         // 創建客戶端Socket
12         Socket s = new Socket(InetAddress.getLocalHost(), 9003);
13         // 得到輸出流
14         OutputStream out = s.getOutputStream();
15         // 得到輸入流
16         FileInputStream fis = new FileInputStream("d:\\1\\1.png");
17         // 發送文件信息
18         byte[] buf = new byte[1024];
19         int len = 0;
20         while ((len = fis.read(buf)) != -1) {
21             // 寫入到Socket輸出流
22             out.write(buf, 0, len);
23         }
24         s.shutdownOutput();
25         // 關流
26         out.close();
27         fis.close();
28         s.close();

   注意:在創建客戶端Socket服務的時候,須要指定服務端的IP地址和端口號,此處在實如今一臺電腦上演示,所以服務端的地址是本機的IP地址。

服務端

 1         // 創建服務端
 2         ServerSocket ss = new ServerSocket(9003);// 須要指定端口,客戶端與服務端相同,通常在1000-65535之間
 3         //服務端通常一直開啓來接收客戶端的信息。
 4         while (true) {
 5             // 獲取客戶端Socket
 6             Socket s = ss.accept();
 7             // 獲取輸入流與輸出流
 8             InputStream in = s.getInputStream();// 輸入流
 9             FileOutputStream fos = new FileOutputStream("d:\\3\\3.png");
10             // 建立緩衝區關聯輸入流與輸出流
11             byte[] buf = new byte[1024];
12             int len = 0;
13             // 數據的寫入
14             while ((len = in.read(buf)) != -1) {
15                 fos.write(buf, 0, len);
16             }
17             // 關流
18             fos.close();
19             s.close();
20         }

  由於此時尚未用到File類,所以與流關聯的文件夾必須被提早建立,不然沒辦法成功寫入。因此建議後續使用File對象來完成文件與流的關聯。

3、傳輸任意類型後綴的文件

  由於只有一次通訊的過程,所以服務端事先不知道客戶端所傳輸文件的類型,所以可讓服務端與客戶端進行簡單的交互,這裏只考慮成功傳輸的狀況。

  具體實現過程爲:1、客戶端向服務端發送文件完整名稱;2、服務端接收到完整名稱,提取文件後綴名發送給客戶端;3、客戶端接收到服務端發送的後綴名進行校驗,不一樣則關閉客戶端Socket流,結束客戶端進程;4、若是正確,則發送文件信息。5、服務端根據接收到的文件名稱和客戶端ip地址創建相應的文件夾(若是不存在,則創立文件夾),將客戶端Socket輸入流信息寫入文件,關閉客戶端流。這樣由於多了一次傳輸文件後綴名的過程,所以能夠傳輸任意類型的文件,便於以後的拓展,如能夠加入圖形界面,選擇任意想要傳輸的文件。

  這樣基礎功能已經大部分完成,可是此時一次只能鏈接一個客戶端,這樣若是機器人小B有若干追求者,也只能乖乖等小A將文件傳輸完畢,爲了解決能夠同時接收多個客戶端的信息,須要用到多線程的技術。

4、多線程

  多線程的實現有兩種方法,一種是繼承Thread類,另外一種是實現Runnable接口而後做爲線程任務傳遞給Thread對象,這裏選擇第二種實現Runnable接口。須要覆寫此接口的run()方法,在以前的基礎之上改動,將獲取到的客戶端Socket對象傳入線程任務的run()方法,線程任務類須要持有Socket的引用,利用構造函數對此引用進行初始化。將讀取輸入流相當閉客戶端流的操做封裝至run()方法。須要注意的是,此過程當中代碼會拋出異常,而實現接口類不能throw異常,只能進行try,catch處理(接口中無此異常聲明,所以不能拋出)。在服務器類中,新建Thread對象,將線程任務類對象傳入,調用Thread類的start()方法開啓線程。

5、總結

  以上便基本實現了此任務的核心功能,即經過TCP協議,實現了多臺客戶端與主機間任意類型文件的傳輸,其中最核心的知識點在於I/O流,即須要弄清輸入流與輸出流,利用緩衝區進行兩者的關聯;在此基礎上,加入了網絡技術編程,將輸入輸出流更改成Socket套接字;爲了增長拓展性,引入文件對象,實現客戶端與服務端的交互;爲了實現多臺電腦與主機的文件傳輸,引入了多線程。程序中爲了儘可能簡化與抽象最核心的內容,一些代碼與邏輯不免有紕漏,但願你們多多指正與交流。固然此過程徹底能夠由UDP協議完成,在某些場景下UDP也更有優點,此處再也不贅述。

完整代碼

客戶端

 1 import java.io.File;
 2 import java.io.FileInputStream;
 3 import java.io.IOException;
 4 import java.io.InputStream;
 5 import java.io.OutputStream;
 6 import java.net.InetAddress;
 7 import java.net.Socket;
 8 import java.net.UnknownHostException;
 9 
10 public class Client {
11     public static void main(String[] args) throws UnknownHostException, IOException {
12         /*
13          * 客戶端先向服務端發送一個文件名,服務端接收到後給客戶端一個反饋,而後客戶端開始發送文件
14          */
15         //創建客戶端Socket
16         Socket s = new Socket(InetAddress.getLocalHost(), 9001);//修改成服務器IP地址
17         //得到輸出流
18         OutputStream out = s.getOutputStream();
19         //關聯發送文件
20         File file = new File("D:\\1.png");
21         String name = file.getName();//獲取文件完整名稱
22         String[] fileName = name.split("\\.");//將文件名按照.來分割,由於.是正則表達式中的特殊字符,所以須要轉義
23         String fileLast = fileName[fileName.length-1];//後綴名
24         //寫入信息到輸出流
25         out.write(name.getBytes());
26         //讀取服務端的反饋信息
27         InputStream in = s.getInputStream();
28         byte[] names = new byte[50];
29         int len = in.read(names);
30         String nameIn = new String(names, 0, len);
31         if(!fileLast.equals(nameIn)){
32             //結束輸出,並結束當前線程
33             s.close();
34             System.exit(1);
35         }
36         //若是正確,則發送文件信息
37         //讀取文件信息
38         FileInputStream fr = new FileInputStream(file);
39         //發送文件信息
40         byte[] buf = new byte[1024];
41         while((len=fr.read(buf))!=-1){
42             //寫入到Socket輸出流
43             out.write(buf,0,len);
44         }
45         //關流
46         out.close();
47         fr.close();
48         s.close();
49     }
50 }

服務端

任務類

 1 import java.io.File;
 2 import java.io.FileOutputStream;
 3 import java.io.InputStream;
 4 import java.io.OutputStream;
 5 import java.net.Socket;
 6 
 7 public class Task implements Runnable {
 8     private Socket s;
 9     public Task(Socket s){
10         this.s = s;
11     }
12     @Override
13     public void run() {
14         String ip = s.getInetAddress().getHostAddress();
15         try{
16             //獲取客戶端輸入流
17             InputStream in = s.getInputStream();
18             //讀取信息
19             byte[] names = new byte[100];
20             int len = in.read(names);
21             String fileName = new String(names, 0, len);
22             String[] fileNames = fileName.split("\\.");
23             String fileLast = fileNames[fileNames.length-1];
24             //而後將後綴名發給客戶端
25             OutputStream out = s.getOutputStream();
26             out.write(fileLast.getBytes());
27             //新建文件
28             File dir = new File("d:\\server\\"+ip);
29             if(!dir.exists())
30                 dir.mkdirs();
31             File file = new File(dir,fileNames[0]+"."+fileLast);
32             FileOutputStream fos = new FileOutputStream(file);
33             //將Socket輸入流中的信息讀入到文件
34             byte[] bufIn = new byte[1024];
35             while((len = in.read(bufIn))!=-1){
36                 //寫入文件
37                 fos.write(bufIn, 0, len);
38             }
39             fos.close();
40             s.close();
41         }catch(Exception e){
42             e.printStackTrace();
43         }
44     }
45 }

服務器類

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {

    public static void main(String[] args) throws IOException {
        /*
         * 服務端先接收客戶端傳過來的信息,而後向客戶端發送接收成功,新建文件,接收客戶端信息
         */
        //創建服務端
        ServerSocket ss = new ServerSocket(9001);//客戶端端口須要與服務端一致
        while(true){
            //獲取客戶端Socket
            Socket s = ss.accept();
            new Thread(new Task(s)).start();
        }
    }
}

  以上內容就到這裏,若有錯誤和不清晰的地方,請你們指正!

相關文章
相關標籤/搜索