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();
}
}