Java網絡編程之Socket網絡通信

package cn.webSocket.service;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
 
 
/**
 * 
 * 今天整理以下網絡編程的基礎Demo;
 * 
 * 網絡編程:
 * 
 * 1. 軟件架構:
 * 		BS: Browser/Server(瀏覽器/服務器)
 * 		CS: Client/Server(客戶端/服務器)
 * 
 * 2. 網絡通信三要素:
 * 
 * 		2.1. 網絡通信協議:
 * 			計算機必須遵守的協議,其中對數據的傳輸格式,傳輸速率,傳輸步驟等做了相應的規範,只有遵守相應的規則計算機之間才能進行準確無誤的交互;
 * 			(TCP/IP協議:傳輸控制協議/因特網互聯協議(Transmission Control Protocol/Internet Protocol
 * 				TCP協議是面向連接的通信協議:意思是說在數據傳遞前,會首先建立發送端和接收端之間的邏輯連接,然後進行數據傳輸;
 * 				目前TCP能夠極大程度上提供兩臺計算機之間可靠無差別的數據傳輸;其主要可靠性體現在數據交互前的準備階段:
 * 					第一階段:客戶端向服務端發送連接請求,等待服務器確認;
 * 					第二階段:服務器端迴應客戶端,收到連接請求;
 * 					第三階段:客戶端再次向服務端發送確認信息,服務器收到,確認連接成功;
 * 				以上的三次連接信息交互保證了數據傳輸的可靠性;完成上面的連接驗證後,就是正式的數據交互;由於TCP協議在數據傳輸上的安全性能顯著,
 * 				目前應用的場景較爲廣泛,如文件下載,網頁瀏覽等...
 * 
 * 			UDP協議:用戶數據報協議(User Datagram Protocol);UDP協議是面向無連接的一種協議,其特點是:數據傳輸時,無需事先建立與服務器的通信連接,
 * 				不管服務器是否啓動成功,直接將數據,數據源和目的地封裝進入一個限定64K以內大小的數據包中然後發送;這種協議其實是一種不安全的協議;
 * 				它存在的缺點就是它的不可靠性.雖然因沒有了預連接使其傳輸速度相對加快,但是容易丟失數據.所以在日常的應用中主要是應用在視頻會議或語音聊天等地方;
 * 
 * 			);
 * 
 * 			協議規範了不同設備如何連入因特網,以及相互之間如何數據傳遞的規則;
 * 			其內部包含了一系列用戶數據通訊處理的各種協議,並且採用的4層的分層模型,每一層都會呼叫下一層提供的協議來完成自己的需求;)
 * 
 * 		2.2. IP地址:用來記錄每一臺設備的唯一標識;
 * 			起源:IP地址早期只有IPv4,是一個32位的二進制數,通常被分爲4個字節,以a.b.c.d的形式組成;例如192.168.31.1;
 * 			其中的a,b,c,d都是0~255之間的十進制整數,大約最多爲42億個;在2011年2月的數據統計中,IPv4的IP已全數分配完;
 * 			後來:爲了擴大地址空間,重新定義了IPv6的地址空間,採用128位地址長度,每16個字節一組,分成8組16進制數來表示;
 * 			如:ABCD:EF01:2345:6789:ABCD:EF01:2345:6789;後來的統計成IPv6的方式分配IP可以爲地球上的每一粒沙子編著一個地址;
 * 
 * 		2.3. 端口號
 * 			網絡通信本質上是不同應用程序間的兩個進程之間的通信;爲了區分計算機中各自的進程,就會用到端口來區分;
 * 			IP標註網絡中不同的設備,而端口號則是標示設備中不同進程;
 * 			端口號:使用兩個字節表示的整數,取值範圍在0~65535之間;其中0~1023的端口大都會被知名的網絡服務和應用佔取,普通的應用程序則可以從
 * 				1024之後取.如果端口取值已被其他進程佔據,則新的程序將無法啓動,除非解除對應的端口占用;
 * 
 * 	也就是說:通過網絡協議+IP地址+端口號,就可以區別網絡中不同設備,進程的交互;
 * 
 * 3. TCP通訊規則:
 * 		3.1. 服務端程序需要預先啓動來等待客戶端程序的請求;
 * 		3.2. 客戶端主動連接成功服務器端方可進行通訊,而服務器端不可以主動連接客戶端;
 * 		3.3. java.net包中提供了兩種常見協議的支持(TCP,UDP)用來TCP通訊,裏面提供了一些低層次的通信細節;其中會用到兩個主要的類java.net.Socket|java.netServiceSocket;
 * 			java.net.Socket:客戶端(封裝客戶端套接字);
 * 			java.netServiceSocket:服務端;
 * 
 * 好,接下來就開始模擬一下關於Java中Socket通訊的Demo示例;
 * 
 * 在這裏說明一下,起始的時候沒有搞明白Socket到底是個什麼東西,既然通訊需要客戶端和服務器,那是不是我也需要擁有一個服務器和本地的開發機來配合;
 * 才能夠完成我的Socket通訊測試.直到後來經過多番研習後才明白其中的奧義.希望能夠通過下面的Demo來清晰的展現Socket通訊面紗後的真容;
 * 
 * 整體的還是和以往一樣,首先創建自己的測試類:
 * 但是這裏也說明一下,因爲我們是客戶端和服務器的交互,所以必定有兩個獨立的程序來發送各自的請求與響應,那麼既然是兩個獨立的程序,而且各自還有自己
 * 獨立的信息需要請求和發送,那麼各自必定需要有一個類似main函數的程序執行入口來發起自己的數據;
 * 
 * 而且Socket通訊的核心也就是網絡變成的核心:通訊協議+IP地址+端口號;
 * 
 * Socket和ServiceSocket是兩個終端各自封裝的底層通訊類,故而必然是兩個執行的入口;
 * 
 * 大意如上,或有不清晰之處,多加思量...抱拳!
 * 
 * 好 下面的類爲服務器端模擬測試類;
 * @author TANJIYUAN
 *
 */
public class Service {
 
	/**
	 * 程序入口|主函數;
	 * 
	 * 在這裏闡述一下大致的通信導向:
	 * 
	 * 	1. 服務端 啓動,創建ServerSocket對象,等待連接;
	 * 	2. 客戶端 啓動,創建Socket對象,請求連接;
	 * 	3. 服務端 接收連接,通過accept()方法監聽獲取客戶端Socket對象;
	 * 	4. 客戶端 通過Socket對象獲取對應的輸出流,向服務端發送數據;
	 * 	5. 服務端 通過獲取的客戶端Socket對象獲取對應的輸入流來讀取客戶端通過輸出流發送的數據;
	 * 	6. 服務端 接受到數據處理後通過獲取到的客戶端Socket對象獲取對應的輸出流,來向客戶端回寫數據;
	 * 	7. 客戶端 通過Socket對象獲取輸入流來讀取服務端回寫的數據內容;
	 * 	8. 資源釋放;連接斷開;
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
		try {

			/**
			 * 打印流
			 * 通過打印流改變打印數據的輸出位置;
			 * -------------------------------------------------------------------------
			 * 這裏說明一下:
			 * 如果直接在控制檯答應,無法全量看到Service服務器端和Client客戶端的打印數據;
			 * 所以引入以一個打印流,將數據內容調轉一個自定義地址進行數據信息打印;
			 * 此處初始化一個數據打印地址;
			 */
			String path = "D://Service.txt";
			
			// 初始化一個打印流; 控制編碼;
			PrintStream ps = new PrintStream(path, "UTF-8");
			
			// 改變打印流的位置,指定自定義打印流;
			System.setOut(ps);
			// -------------------------------------------------------------------------
			
			// 實例化服務器端Socket對象;指定服務器程序的進程端口號(綁定端口);
			ServerSocket server_socket = new ServerSocket(65534);

			System.out.println("服務端啓動... Weit Conn...");
			
			/**
			 * 通過accept()方法獲取客戶端Socket對象;
			 * accept()方法會一直處於阻塞狀態實時監聽,直到獲取到Socket對象爲止;
			 */
			Socket accept = server_socket.accept();
			
			// 獲取客戶端Socket對象的輸出流數據,得到一個數據輸入流對象;
			InputStream inputStream = accept.getInputStream();
			
			// 封裝一個自定義緩存數組;
			byte [] bytArr = new byte[8*1024];
			
			// 讀取客戶端Socket對象的流數據;(輸出流轉輸入流數據)
			int index = inputStream.read(bytArr,0,bytArr.length);
			
			// 輸出打印數據;
			System.out.print(new String(bytArr,0,index));
			
			// 獲取客戶端Socket對象的輸出流對象;
			OutputStream outputStream = accept.getOutputStream();
			
			// 向輸出流裝在數據;
			outputStream.write("En En Welcome Client......I'm Service".getBytes());
			
			// 資源釋放;
			outputStream.close();
			ps.close();
			inputStream.close();
			accept.close();
			server_socket.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
}
package cn.webSocket.client;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
 
/**
 * 以下是客戶端模擬測試類;
 * 
 * 響應的引文可以在服務器端測試類上的註釋中進行查看...
 * 
 * @author TANJIYUAN
 *
 */
public class Client {
 
	/**
	 * 程序的入口|主函數;
	 * @param args
	 */
	public static void main(String[] args) {
		
		try {

			/**
			 * 打印流
			 * 通過打印流改變打印數據的輸出位置;
			 * -------------------------------------------------------------------------
			 * 這裏說明一下:
			 * 如果直接在控制檯答應,無法全量看到Service服務器端和Client客戶端的打印數據;
			 * 所以引入以一個打印流,將數據內容調轉一個自定義地址進行數據信息打印;
			 * 此處初始化一個數據打印地址;
			 */
			String path = "D://Client.txt";
			
			// 初始化一個打印流; 控制編碼;
			PrintStream ps = new PrintStream(path, "UTF-8");
			
			// 改變打印流的位置,指定自定義打印流;
			System.setOut(ps);
			// -------------------------------------------------------------------------
 
			/**
			 * 初始化客戶端Socket對象;指定請求IP以及端口號;
			 * 需要注意:端口號是使用兩個字節表示的整數,取值範圍在0~65535之間;
			 */
			Socket socket = new Socket("127.0.0.1",65534);
			
			System.out.println("客戶端啓動... Will Conn...");
			
			// 獲取客戶端Socket對象的輸出流;
			OutputStream outputStream = socket.getOutputStream();
			
			// 裝在流數據;
			outputStream.write("Hello Server: I'm Client Client Client...".getBytes());
			
			// 獲取Socket對象的流數據;(讀取服務器端請求到的輸出流數據)
			InputStream inputStream = socket.getInputStream();
			
			// 封裝一個緩存數組;
			byte [] charArr = new byte [8*1024];
			
			// 讀取流數據;
			int index = inputStream.read(charArr);

			// 輸出打印數據;
			System.out.print(new String(charArr,0,index));
			
			// 資源釋放;
			outputStream.close();
			inputStream.close();
			ps.close();
			socket.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		} 
	}
}
package cn.webSocket.service;
 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
 
 
/**
 * 2.0: 服務端
 * 
 * WebSocket數據文件Demo;
 * 
 * 下面走一個小的數據數據文件交互Demo;
 * 
 * 同上面的樣例規則一樣,我們分服務器端和客戶端;
 * 
 * 那麼首先創建一個服務器端的測試類"Service";
 * @author TANJIYUAN
 *
 */
public class Service {
 
	/**
	 * 程序入口|主函數;
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		
		try {
			
			// 實例化指定端口的服務器端Socket對象;
			ServerSocket serverSocket = new ServerSocket(65530);
			
			/**
			 * PrintStream;
			 * 
			 * 通過打印流改變數據輸出方向;
			 * ------------------------------------------------------------------
			 * 首先初始化一個輸出地址;
			 */
			String path = "D://Server_Upload.txt";
			
			// 實例化打印流,並指定對應的編碼和輸出地址;
			PrintStream ps = new PrintStream(path, "UTF-8");
			
			// 指定自己的打印流;
			System.setOut(ps);
			// ------------------------------------------------------------------
			
			// 數據打印;
			System.out.println("Server start Ok ...");
			
			// 通過accept()方法實時監聽獲取客戶端Socket對象;
			Socket clientSocket = serverSocket.accept();
			
			// 實例化一個緩衝byte數組;
			byte [] byteArr = new byte [8*1024];
			
			/**
			 * getInputStream():通過獲取到的客戶端Socket對象獲取客戶端Socket對象的輸入流;
			 * 
			 * 通過指定客戶端輸入流實例化到一個緩衝輸入流中;
			 */
			BufferedInputStream bis = new BufferedInputStream(clientSocket.getInputStream());
			
			// 初始化一個數據輸出位置;
			String WritePath = "D://"+System.currentTimeMillis()+".png";
			
			// 實例化一個緩衝輸出流;
			BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(WritePath));
			
			// 預定義一個記錄緩衝流讀取源數據的標位;
			int length = 0;
			
			// 緩衝流循環讀取源數據;
			while((length = bis.read(byteArr)) != -1) {
				
				// 輸出流寫入數據;
				bos.write(byteArr, 0, length);
			}
			
			// 提交一次之前寫入的數據;
			bos.flush();
			
			// 再次寫入數據;
			bos.write("Data upload Over!  ".getBytes());
			
			// 資源釋放;
			bos.close();
			bis.close();
			clientSocket.close();
			ps.close();
			serverSocket.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}

	}
	
}
package cn.webSocket.client;
 
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
 
/**
 * 
 * 2.0: 客戶端;
 * 
 * 下面是客戶端的模擬測試類;
 * 
 * @author TANJIYUAN
 *
 */
public class Client {
 
	/**
	 * 程序的入口|主函數;
	 * @param args
	 */
	public static void main(String[] args) {

		try {
			
			/**
			 * PrintStream;
			 * 
			 * 通過打印流改變數據輸出方向;
			 * -------------------------------------------
			 * 初始化一個輸出地址;
			 */
			String logPath = "D://Client.txt";

			// 實例化打印流,並指定對應的編碼和輸出地址;
			PrintStream ps = new PrintStream(logPath,"UTF-8");

			// 指定自己定義的打印流;
			System.setOut(ps);
			// -------------------------------------------
			
			// 實例化客戶端Socket對象,指定請求的服務器IP和對應服務器上的進程端口;
			Socket socket = new Socket("127.0.0.1",65530);
			
			// 數據打印;
			System.out.println("Socket start Over!  ");
			
			// 初始化數據源;
			String path = "D://30139986c0adc88a76de14b375e0531.png";
			
			// 實例化輸入流;
			BufferedInputStream bis = new BufferedInputStream(new FileInputStream(path));
			
			// 實例化一個byte緩衝數組;
			byte [] charArr = new byte [8*1024];
			
			// 通過客戶端Socket對象獲取輸出流;
			OutputStream outputStream = socket.getOutputStream();

			// 預定義一個記錄源數據批量讀取的最後位標;
			int length = 0;
			
			// 循環讀取源數據;
			while((length = bis.read(charArr)) != -1) {
				
				// 數據寫入;
				outputStream.write(charArr, 0, length);
			}
			
			// 數據提交;
			outputStream.flush();
			
			// 再次寫入;
			outputStream.write("Socket send Over! ... ".getBytes());
			
			// 資源釋放;
			outputStream.close();
			bis.close();
			socket.close();
			ps.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 
	 * @param bytes
	 * @return
	 */
	public static byte[] getBytes(char[] chars) {
        Charset cs = Charset.forName("UTF-8");
        CharBuffer cb = CharBuffer.allocate(chars.length);
        cb.put(chars);
        cb.flip();
        ByteBuffer bb = cs.encode(cb);
        return bb.array();
    }
	
	/**
	 * 
	 * @param bytes[]
	 * @return
	 */
	public static char[] getChars(byte[] bytes) {
        Charset charSet = Charset.forName("UTF-8");
        ByteBuffer bb = ByteBuffer.allocate(bytes.length);
        bb.put(bytes);
        bb.flip();
        CharBuffer cb = charSet.decode(bb);
        return cb.array();
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章