天天看點

能夠傳送檔案的聊天室

回顧最初學習Java之時,曾經做了一個類似QQ的登入界面。但那也僅僅是一個界面,并沒有實作真正意義上的登陸伺服器。近日學習了有關通信技術的原理,結合以前學過的知識,不僅實作了多人之間簡單的文字對話,而且還能給對方傳送檔案。同時,在和别人交流的過程中也收獲了不少心得,真可謂是“受益匪淺”啊。

實作上述的功能:

首先,得弄明白的計算機之間是怎麼交流的?參閱文獻,我們能夠了解到:“……為了使不同計算機廠家生産的計算機能夠互相通信,以便在更大的範圍内建立計算機網絡,ISO在1978年提出了“開放系統互聯參考模型”,它将計算機網絡體系結構的通信協定劃分為七層,自下而上依次為:實體層、資料鍊路層、網絡層、傳輸層、會話層、表示層、應用層。……”說了一大段,就自身目前的知識水準來看,底層的過程(實體層和資料鍊路層)還不能解釋清楚,倘若從自己能夠把握的層面(網絡層、傳輸層)出發,也就就不難了解了。

在傳輸層,主機之間的資料傳輸遵循TCP,UDP協定等。其中,TCP協定是“面向連接配接”的協定,意思就是在正式通信之前,必須要先與對方建立起連接配接。就好比你給别人打電話,必須等線路接通了,雙方之間才能互相通話;而UDP是“面向非連接配接”的,就是在正式通信前不必與對方先建立連接配接,不管對方狀态如何就直接發送。這個與手機短信非常相似:你在發短信的時候,隻需要輸入對方手機号并發送出去就OK了,而不需考慮對方的狀态。上述兩種協定各有千秋,有差異那是因為适用于不同要求的通信環境。

在網絡層,主機之間要實作網絡互連,就得遵循IP協定。以太網、分組交換網等,它們互相之間不能互通,其主要原因是因為它們所傳送資料的基本單元(技術上稱之為“幀”)的格式不同。IP協定把各種不同“幀”統一轉換成“網協資料包”格式,這種轉換使所有各種計算機都能在網際網路上實作互通。同時,IP還有一個重要的任務:給網際網路的每一台聯網裝置規定一個位址,并且這個位址是唯一的。

基于以上的認識,我選擇建立一個遵循TCP/IP協定的聊天室。在那之前,我們得明白“伺服器”和“客戶機”的概念。這就好比我們日常生活中對話一樣,前者可以喻為“話題的開啟者”,在等待着别人的參與,另一方是“話題的參與者” ,主動去參與别人開啟的話題。。。是以,首要任務就是開啟話題:Java實作網絡通訊程式需要引入java.net包下面的API。過程如下:

第一步,通俗的講,就是一個人(暫定這個人叫A)買了一台隻能夠接聽别人(暫定這個人叫B)來電的手機,并且給手機注冊了手機号碼。你可能會問,怎麼會有這樣的人嘛,SB?嘿嘿,勿噴,先聽我娓娓道來,A買手機ss_A,并注冊号碼: ServerSocket ss_A= new ServerSocket(port); 其中port為“端口序号”,并且port是一個0~65535的int型十進制數;在這裡,我們把每個手機号碼了解為"ip位址 + port"為什麼要這樣,一會兒就明白了)。一台手機可能不隻有一個号碼哦,嘿嘿。

第二步,A等待B給 他(她) 打電話:ss_A.accept(); 這個方法會“阻塞”,也就是A一直等待,直到B給他(她)打電話。才會跳出這個方法。

第三步,B有“要事”得跟A商量,是以B得去買一部手機,并且給A打過去: Socket s_B = new Socket (ip, port); 因為每台“手機”的ip是唯一的,而“手機号碼”可能不止一個,是以用“ip位址+port”表示“手機号碼”就能說得過去了。。一旦B給A打電話,如果沒有撥錯“手機号碼”的話,就能撥通,然後A與B之間的連接配接就建立起來,這個連接配接的橋梁就是s_A : Socket s_A = ss_A.accept();

第四步,既然已經連接配接上了,那麼,接下來就可以“說話”了?嘿嘿,還差一步:要找到“話筒”和“聽筒”,試想如果沒有這兩個,會怎麼樣呢。 這裡有很多類型“話筒”和“聽筒”噢:DataOutputStream和DataInputStream ,OutputStream和InputStream與BufferedOutputStream和BufferedInputStream等,隻需選其中一種就可以了,以A為例:A的手機的“話筒”:DataOutputStream dos = new DataOutputStream(s.getOutputStream());“聽筒”:DataInputStream dis = new DataInputStream(s.getInputStream()); ,B同理。

第五步,可以商量“要事”啦!!。怎麼聽?又怎麼說? 在此之前,雙方要在事先“約定”說什麼話就怎麼聽。 還是以A為例,A要“說話" ,對着A的“話筒”說不就完了嗎? 也就是 dos.writeUTF; 當然也有writeInt(); , writeByte(); writeFloat();等,就了解為“說不同的語言”吧,“聽筒”也類似,對方怎麼說你就怎麼聽。否則很有可能造成“聽風就是雨”。

第六步,到這裡的話很感謝您能繼續閱讀下去,不過我很遺憾的告訴您:“沒有第六步啦!!” 既然都能夠實作簡單對話了,那麼傳送檔案就不在話下了。雙方要事先達成傳送檔案的約定,那就是定義一個“秘密密碼”,如果有其中一方發出了這條“秘密密碼”,那麼另一方收到“秘密密碼”之後就做好接收檔案的準備,然後“密碼的發出者”還得把 檔案的基本屬性:檔案名稱,檔案大小,檔案内容等發送給對方,“檔案的接收者”就根據接收到的消息在自己指定的地方建立一個一模一樣的檔案即可。到了這一步,要做的工作就和第五步一樣了。

至此,分析過程基本完成。檔案傳送部分的關鍵代碼如下:

發送檔案端:

//給“發送檔案”按鈕 添加監聽器
		sendFile.addMouseListener(new MouseAdapter() {

			@Override
			public void mouseClicked(MouseEvent e) {

				JFileChooser jfc = new JFileChooser();//觸發按鈕之後執行個體化檔案選擇器
				jfc.showOpenDialog(null);//在螢幕上居中顯示對話框
		        File f = jfc.getSelectedFile();//擷取檔案選擇器選中的檔案

		        if(f != null && f.isFile()){ //如果選中的檔案不為空并且就是檔案
		        	try {
		        	   String str = "THIS_IS_A_FILE_WILL_BE_SEND";//傳送檔案的“秘密密碼”
		               dis = new DataInputStream(new BufferedInputStream(new FileInputStream(f.getAbsolutePath())));
		               dos = new DataOutputStream(s.getOutputStream());
		               //将檔案名及長度傳給用戶端
		               dos.writeUTF(str); //寫入“密碼”來告訴伺服器“我要傳送檔案給你”
		               dos.writeUTF(f.getName());//寫入“檔案名”來告訴伺服器“我要傳送的檔案的檔案名是XXX”
		               dos.flush();//強制輸出
		               dos.writeLong((long) f.length());//寫入“檔案占用的空間大小”來告訴伺服器“我要傳送的檔案所占用的空間大小為XXXXX”
		               dos.flush();//強制輸出
		               int bufferSize = 8192;緩沖區的大小
		               byte[] buf = new byte[bufferSize];//開辟預定緩沖區大小的byte數組,用來存取檔案資料
		               //将檔案内容寫入“輸入流”
		               while (true) {
		                   int read = 0;
		                   if (dis != null) read = dis.read(buf);
		                   if (read == -1) break;
		                   dos.write(buf, 0, read);
		               }
		               dos.flush();//強制輸出
		               System.out.println("檔案傳輸完成");
		               String string = "您成功上傳了" + f.getName() + "到伺服器!";
		               appendMassage(string);//在“聊天記錄區域”顯示傳送結果

					} catch (IOException e1) {
						e1.printStackTrace();
					  }
		        }
			}
		});
           

接收檔案端:首先,讀入一行字元串,以此來判斷下一步的操作:

String str = dis.readUTF();//從“用戶端”的“輸入流”中讀出字元串賦給str 
           

然後,判斷是否為 傳送檔案的"秘密密碼",如果是,就執行接收檔案的方法,否則就當作文本資訊處理:

//判斷是否要接收檔案
 if(str.contains("THIS_IS_A_FILE_WILL_BE_SEND")) {
     getFile();//接收檔案的方法
 }
           

接收檔案的方法如下:

//接收檔案的方法
        public void getFile() {

        	try {
                String savePath = "C:\\Users\\Administrator.ACER-PC\\Desktop\\";//預存放接收檔案存放的路徑
                int bufferSize = 8192;//緩沖區的大小
                byte[] bytebuffer = new byte[bufferSize];//開辟預定緩沖區大小的byte數組,用來存取檔案資料
                int passedLength = 0;//已經傳送的檔案的大小
                long fileLength = 0;//檔案總長度
                savePath += dis.readUTF();//存放接收檔案的絕對路徑 = 預存放接收檔案存放的路徑 + 檔案名
                System.out.println("dis.readUTF():" + dis.readUTF());
             dos = new DataOutputStream(new BufferedOutputStream(new BufferedOutputStream(new FileOutputStream(savePath))));
                fileLength = dis.readLong();
                System.out.println("檔案的長度為:" + fileLength + "\n");
                System.out.println("開始接收檔案!" + "\n");      
                while (true) {
                    int read = 0;
                    if (dis != null) {
                        read = dis.read(bytebuffer);
                }
                passedLength += read;//已經傳送的長度 = 輸入流讀取的長度
                if (read == -1)  break;
                 System.out.println("檔案已接收了" +  (passedLength * 100 / fileLength) + "%");
                    dos.write(bytebuffer, 0, read);
                }
                System.out.println("檔案接收完成,存為: " + savePath + "\n");
//                fileOut.close();
            } catch (Exception e) {
                System.out.println("接收消息錯誤!!" + "\n");
                return;
            }
        }
           

心得:一方面,加深了對技術的了解;另一方面,我認為更重要的是,一個人如果真的決心去做一件事,那麼任何理由都不成理由.就像現在,我熬到這個點也要寫好我的技術部落格一樣,再累再苦再難再忙,都不要忘了使自己進步,都不要忘了堅持自己的理想。隻要時常用理想來激勵自己,就不會迷失自己,碌碌無為。