C/S結構 :全稱爲Client/Server結構,是指客戶端和服務器結構。常見程序有QQ、迅雷等軟件。html
B/S結構:全稱爲Browser/Server結構,是指瀏覽器和服務器結構。常見瀏覽器有谷歌、火狐等。java
兩種架構各有優點,可是不管哪一種架構,都離不開網絡的支持。網絡編程,就是在必定的協議下,實現兩臺計算機 的通訊的程序。web
網絡通訊協議:通訊協議是對計算機必須遵照的規則,只有遵照這些規則,計算機之間才能進行通訊。這就 比如在道路中行駛的汽車必定要遵照交通規則同樣,協議中對數據的傳輸格式、傳輸速率、傳輸步驟等作了編程
統一規定,通訊雙方必須同時遵照,最終完成數據交換。數組
TCP/IP協議: 傳輸控制協議/因特網互聯協議( Transmission Control Protocol/Internet Protocol),是瀏覽器
Internet最基本、最普遍的協議。它定義了計算機如何連入因特網,以及數據如何在它們之間傳輸的標準。它安全
的內部包含一系列的用於處理數據通訊的協議,並採用了4層的分層模型,每一層都呼叫它的下一層所提供的服務器
協議來完成本身的需求。網絡
通訊的協議仍是比較複雜的, java.net 包中包含的類和接口,它們提供低層次的通訊細節。咱們能夠直接使用這些類和接口,來專一於網絡程序開發,而不用考慮通訊的細節。多線程
TCP:傳輸控制協議 (Transmission Control Protocol)。TCP協議是面向鏈接的通訊協議,即傳輸數據以前,
在發送端和接收端創建邏輯鏈接,而後再傳輸數據,它提供了兩臺計算機之間可靠無差錯的數據傳輸。
UDP:用戶數據報協議(User Datagram Protocol)。UDP協議是一個面向無鏈接的協議。傳輸數據時,不需
要創建鏈接,無論對方端服務是否啓動,直接將數據、數據源和目的地都封裝在數據包中,直接發送。每一個
數據包的大小限制在64k之內。它是不可靠協議,由於無鏈接,因此傳輸速度快,可是容易丟失數據。平常應
用中,例如視頻會議、QQ聊天等。
協議:計算機網絡通訊必須遵照的規則,已經介紹過了,再也不贅述。
IP地址:
什麼是IP地址:指互聯網協議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網絡中的計算機設備作惟一的編號。假如咱們把「我的電腦」比做「一臺電話」的話,那麼「IP地址」就至關於「電話號碼」。
IP地址分類:
IPv6:因爲互聯網的蓬勃發展,IP地址的需求量越來越大,可是網絡地址資源有限,使得IP的分配愈加緊張。有資料顯示,全球IPv4地址在2011年2月分配完畢。 爲了擴大地址空間,擬經過IPv6從新定義地址空間,採用128位地址長度,每16個字節一組,分紅8組十六進 制數,表示成 ABCD:EF01:2345:6789:ABCD:EF01:2345:6789 ,號稱能夠爲全世界的每一粒沙子編上一個網址,這樣就解決了網絡地址資源數量不夠的問題。
經常使用命令:
查看本機IP地址,在控制檯輸入: ipconfig
檢查網絡是否連通,在控制檯輸入:
ping 空格 IP地址 ping 220.181.57.216 【特殊的IP地址】 本機IP地址: 127.0.0.1 、 localhost 。
端口號:
網絡的通訊,本質上是兩個進程(應用程序)的通訊。每臺計算機都有不少的進程,那麼在網絡通訊時,如何區分這些進程呢?
若是說IP地址能夠惟一標識網絡中的設備,那麼端口號就能夠惟一標識設備中的進程(應用程序)了。
端口號:用兩個字節表示的整數,它的取值範圍是0-65535。其中,0~1023之間的端口號用於一些知名的網絡服務和應用,普通的應用程序須要使用1024以上的端口號。若是端口號被另一個服務或應用所佔用,會致使當前程序啓動失敗。
利用 協議 + IP地址 + 端口號 三元組合,就能夠標識網絡中的進程了,那麼進程間的通訊就能夠利用這個標識與其它進程進行交互。
TCP通訊能實現兩臺計算機之間的數據交互,通訊的兩端,要嚴格區分爲客戶端(Client)與服務端(Server)。
Socket 類:該類實現客戶端套接字,套接字指的是兩臺設備之間通信的端點。
構造方法:
public Socket(String host, int port)
:建立套接字對象並將其鏈接到指定主機上的指定端口號。若是指定的host是null ,則至關於指定地址爲回送地址。
回送地址(127.x.x.x) 是本機回送地址(Loopback Address),主要用於網絡軟件測試以及本地機進程間通訊,不管什麼程序,一旦使用回送地址發送數據,當即返回,不進行任何網絡傳輸。
代碼
Socket client = new Socket("127.0.0.1", 6666);
成員方法:
public InputStream getInputStream()
: 返回此套接字的輸入流。
public OutputStream getOutputStream() : 返回此套接字的輸出流。
public void close() :關閉此套接字
。
public void shutdownOutput() : 禁用此套接字的輸出流
。
ServerSocket 類:這個類實現了服務器套接字,該對象等待經過網絡的請求。
構造方法:public ServerSocket(int port)
:使用該構造方法在建立ServerSocket對象時,就能夠將其綁定到一個指 定的端口號上,參數port就是端口號。
代碼
ServerSocket server = new ServerSocket(6666);
成員方法:public Socket accept()
:偵聽並接受鏈接,返回一個新的Socket對象,用於和客戶端實現通訊。該方法 會一直阻塞直到創建鏈接。
TCP通訊分析圖解
【服務端】Scoket對象,獲取InputStream,讀取客戶端發送的數據。
【客戶端】釋放資源,斷開鏈接。
客戶端向服務器發送數據
服務端代碼
public static void main(String[] args) throws IOException { System.out.println("啓動服務端..."); // 1. 建立ServerSocket對象 ServerSocket server = new ServerSocket(6666); // 2. 接收客戶端的socket對象 Socket socket = server.accept(); // 3. 建立字節輸入流對象 InputStream is = socket.getInputStream(); // 4. 讀取數據 int len = 0; byte[]bts = new byte[1024]; len = is.read(bts); System.out.println(new String(bts,0,len)); // 關閉網絡流 is.close(); // 關閉socket socket.close(); }
客戶端代碼
public static void main(String[] args) throws IOException { System.out.println("啓動客戶端"); // 1. 建立Socket對象 Socket sk = new Socket("127.0.0.1",6666); // 2. 建立字節網絡流輸出對象 OutputStream os = sk.getOutputStream(); // 3. 輸出內容 os.write("你好哈,服務端大大".getBytes()); // 關閉網絡流 os.close(); // 關閉socket sk.close(); }
服務器向客戶端回寫數據
服務端代碼
public static void main(String[] args) throws IOException { System.out.println("啓動服務端..."); // 1. 建立ServerSocket對象 ServerSocket server = new ServerSocket(6666); // 2. 接收客戶端的socket對象 Socket socket = server.accept(); // 3. 建立字節輸入流對象 InputStream is = socket.getInputStream(); // 4. 讀取數據 int len = 0; byte[]bts = new byte[1024]; len = is.read(bts); System.out.println(new String(bts,0,len)); // 【-------服務端回寫數據--------】 // 1. 建立輸出網絡流 OutputStream os = socket.getOutputStream(); // 2. 寫入內容 os.write("我很好,謝謝,思密達".getBytes()); // 關閉socket socket.close(); server.close(); }
客戶端代碼
public static void main(String[] args) throws IOException { System.out.println("啓動客戶端"); // 1. 建立Socket對象 Socket sk = new Socket("127.0.0.1",6666); // 2. 建立字節網絡流輸出對象 OutputStream os = sk.getOutputStream(); // 3. 輸出內容 os.write("你好哈,服務端大大".getBytes()); // 【解析服務端的迴應】 // 1. 建立輸入網絡流 InputStream is = sk.getInputStream(); // 2. 讀取數據 int len = 0; byte[]bs = new byte[1024]; len = is.read(bs); System.out.println(new String(bs,0,len)); // 關閉socket sk.close(); }
服務端代碼
public static void main(String[] args) throws IOException { System.out.println("啓動服務端..."); // 1. 建立SeverSocket對象,用來處理客戶端請求 ServerSocket sever = new ServerSocket(6666); // 2. 獲取socket對象,用來處理請求 Socket socket = sever.accept(); // 3. 建立本地輸入流對象,用來讀取客戶端發送的數據 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 4. 建立本地輸出流對象,把客戶端上傳的文件寫入本地 String fileName = System.currentTimeMillis() + "" + (new Random().nextInt(99999)) + ".jpg"; File file = new File("d:\\upload"); if(!file.exists()){ file.mkdirs(); } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file +"\\"+ fileName)); // 5. 循環讀取客戶端數據 int len = 0; byte[]bts = new byte[1024]; while((len=bis.read(bts))!=-1){ bos.write(bts,0,len); } // 關閉資源 bis.close(); bos.close(); socket.close(); sever.close(); }
客戶端代碼
public static void main(String[] args) throws IOException { System.out.println("啓動客戶端..."); // 1. 建立Socket對象,鏈接服務端 Socket socket = new Socket("127.0.0.1",6666); // 2. 建立本地輸入流對象,讀取本地文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\Bruce\\Desktop\\logo\\logo01.png")); // 3. 建立網絡輸出流對象,向服務端傳送數據 BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); // 4. 定義字節數組,設置每次讀取的字節 int len = 0; byte[]bts = new byte[1024]; // 5. 循環讀取本地文件數據,並向服務端輸出 while ((len=bis.read(bts))!=-1){ bos.write(bts,0,len); } System.out.println("客戶端:文件上傳完畢!"); // 關閉資源 bis.close(); bos.close(); socket.close(); }
循環接收問題
問題:服務端,指保存一個文件就關閉了,以後的用戶沒法再上傳,這是不符合實際的,使用循環改進,能夠不斷 的接收不一樣用戶的文件
代碼:
while(true){ Socket accept = serverSocket.accept(); ...... }
效率問題
問題:服務端,在接收大文件時,可能耗費幾秒鐘的時間,此時不能接收其餘用戶上傳,因此,使用多線程技術優 化
代碼:
while(true){ Socket accept = serverSocket.accept(); // accept 交給子線程處理. new Thread(() ‐> { ...... InputStream bis = accept.getInputStream(); ...... }).start(); }
代碼
public static void main(String[] args) throws IOException { System.out.println("啓動服務端..."); // 1. 建立SeverSocket對象,用來處理客戶端請求 ServerSocket sever = new ServerSocket(6666); while (true){ new Thread(new Runnable() { @Override public void run() { try { // 2. 獲取socket對象,用來處理請求 Socket socket = sever.accept(); // 3. 建立本地輸入流對象,用來讀取客戶端發送的數據 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 4. 建立本地輸出流對象,把客戶端上傳的文件寫入本地 String fileName = System.currentTimeMillis() + "" + (new Random().nextInt(99999)) + ".jpg"; File file = new File("d:\\upload"); if(!file.exists()){ file.mkdirs(); } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file +"\\"+ fileName)); // 5. 循環讀取客戶端數據 int len = 0; byte[]bts = new byte[1024]; while((len=bis.read(bts))!=-1){ bos.write(bts,0,len); } // 關閉資源 bis.close(); bos.close(); socket.close(); System.out.println(Thread.currentThread().getName()+":已經保存到本地!"); }catch (IOException e) { e.printStackTrace(); } } }).start(); } }
圖解
代碼
服務端
public static void main(String[] args) throws IOException { System.out.println("啓動服務端..."); // 1. 建立SeverSocket對象,用來處理客戶端請求 ServerSocket sever = new ServerSocket(6666); while (true){ new Thread(new Runnable() { @Override public void run() { try { // 2. 獲取socket對象,用來處理請求 Socket socket = sever.accept(); // 3. 建立本地輸入流對象,用來讀取客戶端發送的數據 BufferedInputStream bis = new BufferedInputStream(socket.getInputStream()); // 4. 建立本地輸出流對象,把客戶端上傳的文件寫入本地 String fileName = System.currentTimeMillis() + "" + (new Random().nextInt(99999)) + ".jpg"; File file = new File("d:\\upload"); if(!file.exists()){ file.mkdirs(); } BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(file +"\\"+ fileName)); // 5. 循環讀取客戶端數據 int len = 0; byte[]bts = new byte[1024]; while((len=bis.read(bts))!=-1){ bos.write(bts,0,len); } // 回寫客戶端---------------- OutputStream os = socket.getOutputStream(); os.write("服務器端:已經保存在服務端本地".getBytes()); // 關閉資源 bis.close(); bos.close(); os.close(); socket.close(); System.out.println(Thread.currentThread().getName()+":已經保存到本地!"); }catch (IOException e) { e.printStackTrace(); } } }).start(); } }
客戶端
public static void main(String[] args) throws IOException { System.out.println("啓動客戶端..."); // 1. 建立Socket對象,鏈接服務端 Socket socket = new Socket("127.0.0.1",6666); // 2. 建立本地輸入流對象,讀取本地文件 BufferedInputStream bis = new BufferedInputStream(new FileInputStream("C:\\Users\\Bruce\\Desktop\\logo\\logo01.png")); // 3. 建立網絡輸出流對象,向服務端傳送數據 BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream()); // 4. 定義字節數組,設置每次讀取的字節 int len = 0; byte[]bts = new byte[1024]; // 5. 循環讀取本地文件數據,並向服務端輸出 while ((len=bis.read(bts))!=-1){ bos.write(bts,0,len); } bos.flush(); // 關閉輸出流,通知服務端,寫出數據完畢 socket.shutdownOutput(); System.out.println("客戶端:文件上傳完畢!"); // 接收服務返回的信息 InputStream is = socket.getInputStream(); while ((len=is.read(bts))!=-1){ System.out.print(new String(bts,0,len)); } // 關閉資源 is.close(); bis.close(); socket.close(); }
項目中有一個web項目,咱們寫服務端代碼,而後經過瀏覽器輸入地址127.0.0.1:8888/web/index.html
訪問網頁。
代碼:
public class WebSever { public static void main(String[] args) throws IOException { // 建立ServerSocket對象 ServerSocket server = new ServerSocket(8888); while (true){ new Thread(new Runnable() { @Override public void run() { try{ // 獲取socket對象 Socket socket = server.accept(); // 讀取接收的內容 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String line = reader.readLine(); // GET /web/index.html HTTP/1.1 String path = line.split(" ")[1].substring(1); // 建立本地字節輸入流 FileInputStream fis = new FileInputStream("day09_Socket\\" +path); // 建立網絡輸出流 OutputStream os = socket.getOutputStream(); // 寫入HTTP協議響應頭,固定寫法 os.write("HTTP/1.1 200 OK\r\n".getBytes()); os.write("Content‐Type:text/html\r\n".getBytes()); // 必需要寫入空行,不然瀏覽器不解析 os.write("\r\n".getBytes()); int len = 0; byte[]bts = new byte[1024]; while((len=fis.read(bts))!=-1){ os.write(bts,0,len); } // 關閉資源 os.close(); fis.close(); reader.close(); socket.close(); } catch (IOException e){ e.printStackTrace(); } } }).start(); } } }
頁面效果