網絡編程
Java 是第一大編程語言和開發平臺。它有助于企業降低成本、縮短開發周期、推動創新以及改善應用服務。如今全球有數百萬開發人員運行著超過 51 億個 Java 虛擬機, Java 仍是企業和開發人員的首選開發平臺。
??
課程內容的介紹
1. 計算機網絡基礎
2. Socket 和 ServerSocket
3. TCP Socket 通信模型
4. UDP 編程
一、計算機網絡基礎
1.什么是計算機網絡
把分布在不同地理區域的計算機與專門的外部設備用通信線路互連成一個規模大 、功能強的網絡系統,從而使眾多的計算機可以方便地互相傳遞信息,共享硬件、軟件、數據信息等資源。
2.了解網絡通信協議
2.1.網絡通信協議
要使計算機連成的網絡能夠互通信息,需要對數據傳輸速率、傳輸代碼、代碼結構、傳輸控制步驟、出錯控制等制定一組標準,這一組共同遵守的通信標準就是網絡通信協議,不同的計算機之間必須使用相同的通訊協議才能進行通信。
2.2 常見的網絡通信協議
TCP/IP, 使用最為廣泛的通訊協議。
TCP/IP 是英文 Transmission Control Protocol/Internet Protocol 的縮寫,意思是 “ 傳輸控制協議 / 網際協議” 。
2.2.1三次握手
TCP 協議建立連接需要三次會話 ( 握手 )
1. 第一次握手,客戶端連接服務器。
2. 第二次握手,服務器接收了客戶端的連接請求,服務器發送給客戶端的確認消息。
3. 第三次握手,客戶端獲取到了服務器的確認信息,知道了服務已經知道我要連接他了,但是服務器還不知道客戶端已經知道了,所以客戶端發送給服務器消息說我們已經接受到了你的確認信息。
經過這三次握手,那么客戶端和服務器都知道了要連接。
??
2.2.2四次揮手
在 TCP 協議中如果客戶端要斷開連接那么需要進行四次揮手操作。
1. 第一次,客戶端發送請求關閉的消息給服務器。
2. 第二次,服務器接受到了客戶端的消息,服務器發送消息給客戶端確認(我收到了你的關閉請求,但是我數據還沒有傳完,等我傳會告訴你的)。
3. 第三次,服務器給客戶端要傳輸的數據以及傳完了,服務器發送消息給客戶端數據傳完了,客戶端你可以斷開連接了。
4. 第四次,客戶端發送消息給服務器,我要斷開了,你也斷開吧。
??
3.IP和端口號
3.1 什么是IP
網絡中每臺計算機的一個標識號,是一個邏輯地址, 127.0.0.1 或 localhost 代表本機地址。
IP 地址由四段組成,每個字段是一個字節, 8 位,最大值是 255 ,IP地址由兩部分組成,即 網絡地址 和 主機地址 。網絡地址表示其屬于互聯網的哪一個網絡,主機地址表示其屬于該網絡中的哪一臺主機。二者是主從關系。IP 地址的四大類型標識的是網絡中的某臺主機。IPv4的地址長度為 32 位,共 4 個字節,但實際中我們用 點分十進制 記法。 192.168.1.1
? ?
3.2 分類
IP 地址根據網絡號和主機號來分,分為 A 、 B 、 C 三類及特殊地址 D 、 E 。 全 0 和全 1 的都保留不用。
A 類 : (1.0.0.0-126.0.0.0) (默認子網掩碼: 255.0.0.0 或 0xFF000000 )第一個字節為網絡號,后三個字節為主機號。該類IP 地址的最前面為 “0” ,所以地址的網絡號取值于 1~126 之間。一般用于大型網絡。
B 類 : (128.0.0.0-191.255.0.0) (默認子網掩碼: 255.255.0.0 或 0xFFFF0000 )前兩個字節為網絡號,后兩個字節為主機號。該類IP 地址的最前面為 “10” ,所以地址的網絡號取值于 128~191 之間。一般用于中等規模網絡。
C 類 : (192.0.0.0-223.255.255.0) (子網掩碼: 255.255.255.0 或 0xFFFFFF00 )前三個字節為網絡號,最后一個字節為主機號。該類IP 地址的最前面為 “110” ,所以地址的網絡號取值于 192~223 之間。一般用于小型網絡。
D 類 :是多播地址。該類 IP 地址的最前面為 “1110” ,所以地址的網絡號取值于 224~239 之間。一般用于多路廣播用戶[1] 。
E 類 :是保留地址。該類 IP 地址的最前面為 “1111” ,所以地址的網絡號取值于 240~255 之間。
??
Java 中對 IP 的操作。
package com.bobo.ip;import java.net.InetAddress;public class IpDemo01 {/*** Java中對IP的封裝操作* @param args*/public static void main(String[] args) throws Exception {System.out.println(InetAddress.getLocalHost());System.out.println(InetAddress.getByName("127.0.0.1"));}
}
科普:為什么局域網中的 ip 地址都是 192.168 開頭的?
私有地址
A 類地址: 10.0.0.0 - 10.255.255.255
B 類地址: 172.16.0.0 - 172.31.255.255
C 類地址: 192.168.0.0 - 192.168.255.255
? ??
3.3 IPv4和IPv6
目前的全球因特網所采用的協議族是TCP/IP協議族。IP是TCP/IP協議族中網絡層的協議,是TCP/IP協議族的核心協議。目前IP協議的版本號是4(簡稱為IPv4),發展至今已經使用了30多年。
IPv4的地址位數為32位,也就是最多有2的32次方的電腦可以聯到Internet上。
近十年來由于互聯網的蓬勃發展,IP位址的需求量愈來愈大,使得IP位址的發放愈趨嚴格,各項資料顯示全球IPv4位址可能在2005至2008年間全部發完。
??
什么是IPv6?
IPv6是下一版本的互聯網協議,也可以說是下一代互聯網的協議,它的提出最初是因為隨著互聯網的迅速發展,IPv4定義的有限地址空間將被耗盡,地址空間的不足必將妨礙互聯網的進一步發展。
為了擴大地址空間,擬通過IPv6重新定義地址空間。IPv6采用128位地址長度,幾乎可以不受限地提供地址。按保守方法估算IPv6實際可分配的地址,整個地球的每平方米面積上仍可分配1000多個地址。在IPv6的設計過程中除了一勞永逸地解決了地址短缺問題以外,還考慮了在IPv4中解決不好的其它問題,主要有端到端IP連接、服務質量(QoS)、安全性、多播、移動性、即插即用等。
??
IPv6與IPv4相比有什么特點和優點?
更大的地址空間。IPv4中規定IP地址長度為32,即有2^32-1個地址;而IPv6中IP地址的長度為128,即有2^128-1個地址。
更小的路由表。IPv6的地址分配一開始就遵循聚類(Aggregation)的原則,這使得路由器能在路由表中用一條記錄(Entry)表示一片子網,大大減小了路由器中路由表的長度,提高了路由器轉發數據包的速度。
增強的組播(Multicast)支持以及對流的支持(Flow-control)。這使得網絡上的多媒體應用有了長足發展的機會,為服務質量(QoS)控制提供了良好的網絡平臺。
加入了對自動配置(Auto-configuration)的支持。這是對DHCP協議的改進和擴展,使得網絡(尤其是局域網)的管理更加方便和快捷。
更高的安全性.在使用IPv6網絡中用戶可以對網絡層的數據進行加密并對IP報文進行校驗,這極大的增強了網絡安全。
3.4 端口號
具有網絡功能的應用軟件的標識號。
特點:
1. 端口是一個軟件結構,被客戶程序或服務程序用來發送和接收數據,一臺服務器有 256*256 個端口。
2. 0-1023 是公認端口號,即已經公認定義或為將要公認定義的軟件保留的。
3. 1024-65535 是并沒有公共定義的端口號,用戶可以自己定義這些端口的作用。
4. 端口與協議有關: TCP 和 UDP 的端口互不相干。
??
二、網絡通信
1.Socket和ServerSocket
1.1 什么是Socket
兩個應用程序可以通過一個雙向的網絡通信連接實現數據的交換,這個雙向的鏈路的一端我們稱為一個Socket 。
??
1.2 Socket案例
1.2.1 基本連接操作
? ?
服務端的創建
package com.bobo.socket01;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** Socket的第一個案例** @param args*/public static void main(String[] args) {try {// 創建一個ServerScoket對象 如果服務器關閉了,那么我們是連接不上的ServerSocket ss = new ServerSocket(8888);System.out.println("服務器啟動了....");// 接受客戶端的訪問while(true){Socket s = ss.accept(); // 這是一個阻塞的方法,等待客戶端的訪問。System.out.println(s.getInetAddress().getHostAddress() + " : 客戶端來訪問了");s.close();}} catch (IOException e) {e.printStackTrace();}}
}
客戶端的創建
package com.bobo.socket01;import java.net.Socket;
import java.rmi.server.ExportException;public class TestClientSocket {/*** Socket 客戶端程序* @param args*/public static void main(String[] args) {try{// 127.0.0.1 / localhost 表示的當前的主機// 獲取一個Socket對象 指定要訪問的服務器 ip+端口 可以定位到要訪問的是哪臺計算機上的服務Socket socket = new Socket("192.168.8.71",8888);System.out.println("客戶端開始鏈接了...");socket.close();}catch (Exception e){e.printStackTrace();}}
}
當前計算機的描述
1. 127.0.0.1
2. localhost
3. 當前電腦分配的 IP(192.168.8.71)
??
1.2.2 客戶端發送消息給服務器
通過字節輸入輸出流實現。
??
服務端代碼
package com.bobo.socket02;import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務端 接收客戶端的消息* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(8899);while(true){// 等待獲取客戶端的連接Socket s = ss.accept();System.out.println(s.getInetAddress().getHostAddress()+"連接了...");// 獲取一個字節輸入流,獲取客戶端傳遞的信息InputStream inputStream = s.getInputStream();byte[] b = new byte[1024];int num = 0 ;while((num = inputStream.read(b)) != -1){System.out.println(new String(b,0,num));}// 關閉相關的資源inputStream.close();s.close();}}catch (Exception e){e.printStackTrace();}}
}
客戶端代碼
package com.bobo.socket02;import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;public class TestClientSocket {/*** 客戶端* 發送消息到服務器* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",8899);// 通過socket對象獲取一個字節輸出流OutputStream outputStream = socket.getOutputStream();outputStream.write("服務器,你好啊,我是XXXXX".getBytes());outputStream.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
效果:
用緩沖流來操作。
??
服務端:
package com.bobo.socket03;import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務端 接收客戶端的消息* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(8899);while(true){// 等待獲取客戶端的連接Socket s = ss.accept();System.out.println(s.getInetAddress().getHostAddress()+"連接了...");BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(s.getInputStream()));System.out.println(bufferedReader.readLine());bufferedReader.close();s.close();}}catch (Exception e){e.printStackTrace();}}
}
客戶端代碼
package com.bobo.socket03;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;public class TestClientSocket {/*** 客戶端* 發送消息到服務器* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",8899);// 通過socket對象獲取一個字節輸出流BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bufferedWriter.write("哈哈哈哈哈....");bufferedWriter.newLine(); // 必須要顯示的調用bufferedWriter.flush();bufferedWriter.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
輸出效果
1.2.3 服務端發送消息給客戶端
服務端代碼:
package com.bobo.socket04;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務端* 發送消息給客戶端* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(8989);while(true){Socket s = ss.accept();System.out.println(s.getInetAddress().getHostAddress()+" 連接了");// 服務器發送消息給客戶端BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));bufferedWriter.write(s.getInetAddress().getHostAddress()+" 你好啊,你的連接我已經收到了");bufferedWriter.newLine();bufferedWriter.flush();bufferedWriter.close();s.close();}} catch (IOException e) {e.printStackTrace();}}
}
客戶端代碼
package com.bobo.socket04;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;public class TestClientSocket {/*** 客戶端 接收服務的消息* @param args*/public static void main(String[] args) {try {Socket s = new Socket("localhost",8989);System.out.println("客戶端啟動了...");BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(s.getInputStream()));System.out.println(bufferedReader.readLine());bufferedReader.close();s.close();} catch (IOException e) {e.printStackTrace();}}
}
輸出結果:
1.2.4 課堂案例
客戶端接收鍵盤輸入信息,然后將該信息發送給服務器,服務器讀取客戶端發送來的信息。
? ??
服務端代碼
package com.bobo.socket05;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務端* 接收客戶端的消息* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(9999);while(true){Socket s = ss.accept();BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(s.getInputStream()));System.out.println(s.getInetAddress().getHostAddress()+":"+bufferedReader.readLine());bufferedReader.close();s.close();}} catch (IOException e) {e.printStackTrace();}}
}
客戶端代碼
package com.bobo.socket05;import java.io.*;
import java.net.Socket;public class TestClientSocket {/*** 客戶端接收鍵盤輸入信息,然后將該信息發送給服務器,服務器讀取客戶端發送來的信息* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",9999);System.out.println("客戶端啟動了,請輸入要發送的消息:");// 接收鍵盤輸入BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));// 客戶端發送消息BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));bufferedWriter.write(bufferedReader.readLine());bufferedWriter.newLine();bufferedWriter.flush();bufferedWriter.close();bufferedReader.close();socket.close();} catch (IOException e) {e.printStackTrace();}}
}
效果
1.2.5 一對一聊天實現
實現一個客戶端發送消息給服務器,然后服務器獲取到消息后再返還消息給客戶端,一對一通信的效果。
? ??
服務端代碼:
package com.bobo.socket06;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務端* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(8080);while(true){Socket s = ss.accept();// 服務端通過鍵盤錄入的方式 返回消息給客戶端BufferedReader input = new BufferedReader(new InputStreamReader(System.in));// 讀取客戶端傳遞的消息BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));// 返回消息給客戶端BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s.getOutputStream()));// 實現頻繁讀取寫入的效果while(true){System.out.println(s.getInetAddress().getHostAddress()+":" + br.readLine()) ;// 返回信息給客戶端qbw.write(input.readLine());bw.newLine();bw.flush();}}} catch (IOException e) {e.printStackTrace();}}
}
客戶端代碼:
package com.bobo.socket06;import java.io.*;
import java.net.Socket;public class TestClientSocket {/*** 客戶端* 實現和服務器一對一的聊天** @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",8080);// 接收鍵盤輸入BufferedReader input = new BufferedReader(new InputStreamReader(System.in));// 讀取信息BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 寫入信息BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));while(true){bw.write(input.readLine());bw.newLine();bw.flush();System.out.println("服務器說:" + br.readLine());}} catch (IOException e) {e.printStackTrace();}}
}
效果:
效果雖然是實現了,但是總的體驗還是不是很好,因為是在讀寫在一個線程中,就造成了讀寫操作的阻塞。
1.2.6 自由聊天的實現
在上面的案例中客戶端和服務器的讀寫操作同一個線程中進行的,所以會造成消息的阻塞,那么我們可以結合前面講過的多線程的知識來解決當前的問題。將讀的操作交給一個線程,將寫的操作交給一個線程,那么讀寫之間就沒有阻塞現象了。具體實現如下:
? ??
讀操作的線程:
package com.bobo.socket07.thread;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;/*** 專門用來負責讀取數據的線程* 線程只需要獲取到對應的Socket對象* 就可以獲取Socket中的數據及向Socket中寫入數據*/
public class ReadSocketThread implements Runnable{private Socket socket;public ReadSocketThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));while(true){String str = br.readLine();System.out.println(str);}} catch (IOException e) {e.printStackTrace();}}
}
寫操作的線程
package com.bobo.socket07.thread;import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;/*** 專門用來發送數據的線程*/
public class WriterSocketThread implements Runnable {private Socket socket;public WriterSocketThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try{// 獲取鍵盤數據BufferedReader input = new BufferedReader(new InputStreamReader(System.in));// 輸出信息BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));while(true){String str = input.readLine();bw.write(str);bw.newLine();bw.flush();}}catch (Exception e){}}
}
服務端代碼
package com.bobo.socket07;import com.bobo.socket07.thread.ReadSocketThread;
import com.bobo.socket07.thread.WriterSocketThread;import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務器* 客戶端和服務器實現自由聊天* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(7891);while(true){Socket s = ss.accept();// 讀客戶端數據和寫客戶端數據都交給子線程去處理// 讀取數據的線程new Thread(new ReadSocketThread(s)).start();// 寫入數據的線程new Thread(new WriterSocketThread(s)).start();// 讀寫操作分別交給兩個不同的線程來處理,就不會出現 讀取必須要等待輸入完成后再執行的情況}} catch (IOException e) {e.printStackTrace();}}
}
客戶端代碼
package com.bobo.socket07;import com.bobo.socket07.thread.ReadSocketThread;
import com.bobo.socket07.thread.WriterSocketThread;import java.io.IOException;
import java.net.Socket;public class TestClientSocket {/*** 實現客戶端和服務自由聊天* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",7891);// 讀取數據的線程new Thread(new ReadSocketThread(socket)).start();new Thread(new WriterSocketThread(socket)).start();} catch (IOException e) {e.printStackTrace();}}
}
效果
1.2.7 有退出的自由聊天
上面的案例除了顯示的把服務關掉之外程序會一直運行,那么如果我們要顯示的斷開聊天,我們只需要在讀寫線程中添加斷開的邏輯即可。
? ??
讀取數據的線程
package com.bobo.socket08.thread;import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;public class ReadSocketThread implements Runnable{private Socket socket;public ReadSocketThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));while(true){String str = br.readLine();System.out.println(socket.getInetAddress().getHostAddress()+":" + str);// 添加退出聊天的邏輯if("exit".equals(str)){ // 表示退出System.out.println(socket.getInetAddress().getHostAddress()+"退出了...");br.close();break; // 結束循環,終止線程}}} catch (IOException e) {e.printStackTrace();}}
}
寫入數據的線程
package com.bobo.socket08.thread;import java.io.*;
import java.net.Socket;public class WriterSocketThread implements Runnable{private Socket socket;public WriterSocketThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {BufferedReader input = new BufferedReader(new InputStreamReader(System.in));BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));while(true){String str = input.readLine();bw.write(str);bw.newLine();bw.flush();// 如果我發送的是 exit 的話,那么讀到這個信息的 會退出,此時發送也應該要退出if("exit".equals(str)){// 讀取數據的線程也要退出System.out.println(socket.getInetAddress().getHostAddress()+"退出了");bw.close();input.close();break;}}} catch (IOException e) {e.printStackTrace();}}
}
三、TCP Socket 通信模型
1.通信原理
2.課堂案例
實現 TCP 通信模型實現客戶端 1 對 1 的通信,實現案例。
讀寫的線程和上面案例是一樣的,就不再復制了。
? ??
客戶端代碼
package com.bobo.socket09;import com.bobo.socket09.thread.ReadSocketThread;
import com.bobo.socket09.thread.WriterSocketThread;import java.io.IOException;
import java.net.Socket;public class TestClientSocket {/*** 實現客戶端對客戶端的一對一聊天* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",7799);new Thread(new ReadSocketThread(socket)).start(); // 啟動讀取信息的線程new Thread(new WriterSocketThread(socket)).start();} catch (IOException e) {e.printStackTrace();}}
}
服務器代碼
package com.bobo.socket09;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;public class TestServerSocket {/*** ServerSocket* 模擬客戶端和客戶端一對一的聊天* @param args*/public static void main(String[] args) {// 記錄以及建立連接的SocketList<Socket> list = new ArrayList<>();try {ServerSocket ss = new ServerSocket(7799);for(int i = 0 ; i < 2 ; i++){Socket s = ss.accept();// 客戶端建立連接后 將連接對應的Socket對象保存起來list.add(s);}// 第一個Socket接收到信息之后 應該將信息在第二個Socket中輸出// 第二個Socket接收到信息之后 應該將信息在第一個Socket中輸出new Thread(new ServerThread(list.get(0),list.get(1))).start();new Thread(new ServerThread(list.get(1),list.get(0))).start();} catch (IOException e) {e.printStackTrace();}}
}/*** ServerSocket端的線程* 專門處理信息的中轉*/
class ServerThread implements Runnable{private Socket s1 ; // 發送者private Socket s2 ; // 接收者public ServerThread(Socket s1, Socket s2) {this.s1 = s1;this.s2 = s2;}@Overridepublic void run() {try {while(true){BufferedReader br = new BufferedReader(new InputStreamReader(s1.getInputStream()));// 獲取 發送者發送的消息String str = br.readLine();// 將消息轉發給另一個客戶端BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(s2.getOutputStream()));bw.write(str);bw.newLine();bw.flush();}} catch (IOException e) {e.printStackTrace();}}
}
在 IDEA 中默認是不支持同一個主方法執行多次的,要并發執行需要如下設置。
3.文件上傳下載
文件上傳:客戶端將 File 傳輸給服務器, File 會保存在服務中。
文件下載:客戶端從服務器獲取 File 。
? ?
第一種實現的方式:
package com.bobo.socket10;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** ServerSocket* 接收客戶端傳遞的文件* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(9999);while(true){Socket s = ss.accept();// 讀取文件BufferedInputStream bi = new BufferedInputStream(s.getInputStream());// 將上傳的文件保存到服務指定的位置BufferedOutputStream bo = new BufferedOutputStream(new FileOutputStream("D:/aaa.txt"));// 又是一個文件復制byte[] b = new byte[1024*1024];int num = 0;while((num = bi.read(b)) != -1){bo.write(b,0,num);}bi.close();bo.close();}} catch (IOException e) {e.printStackTrace();}}
}
package com.bobo.socket10;import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.Socket;public class TestClientSocket {/*** Client* 發送文件到服務器中 上傳操作* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",9999);// 先讀取某個文件BufferedInputStream bi = new BufferedInputStream(new FileInputStream("d:/IO/1.mp4"));// 將讀取的文件寫入到Socket中BufferedOutputStream bo = new BufferedOutputStream(socket.getOutputStream());// 文件的復制byte[] b = new byte[1024*1024];int num = 0 ;//bo.write("1.mp4".getBytes());while((num = bi.read(b)) != -1){bo.write(b,0,num);}bo.close();bi.close();} catch (IOException e) {e.printStackTrace();}}
}
這種實現方式我們發現服務端可以獲取到客戶端傳遞的數據,但是并不清楚傳遞的是什么類型的數據,名稱叫什么也不知道,所以處理起來比較麻煩。
package com.bobo.socket11;import java.io.*;
import java.net.Socket;public class TestClientSocket {/*** 客戶端* @param args*/public static void main(String[] args) {try {Socket socket = new Socket("localhost",9988);File file = new File("d:/IO/1.mp4");// 讀取文件BufferedInputStream bi = new BufferedInputStream(new FileInputStream(file));// 文件寫入Socket ObjectOutputStreamObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());// 先寫入文件名稱oos.writeUTF(file.getName());oos.writeLong(file.length());// 文件的傳輸byte[] b = new byte[1024*1024];int num = 0;while((num = bi.read(b)) != -1){oos.write(b,0,num);}// 關閉資源oos.close();bi.close();} catch (IOException e) {e.printStackTrace();}}
}
package com.bobo.socket11;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class TestServerSocket {/*** 服務端* @param args*/public static void main(String[] args) {try {ServerSocket ss = new ServerSocket(9988);while(true){Socket s = ss.accept();// 讀取文件 ObjectInputStreamObjectInputStream bis = new ObjectInputStream(s.getInputStream());// 獲取文件名稱String fileName = bis.readUTF();// 獲取文件的大小long length = bis.readLong();System.out.println("文件名稱:" + fileName + "文件大小:" + length);// 保存文件到服務器BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(new File("d:/", fileName)));byte[] b = new byte[1024*1024];int num = 0;int i = 1 ;while((num = bis.read(b)) != -1){bos.write(b,0,num);System.out.println("保存的進入:" + (1024*1024*i)/length * 100 + "%" );i++;}bis.close();bos.close();}} catch (IOException e) {e.printStackTrace();}}
}
四、了解UDP
1.UDP協議
UDP(User Datagram Protocol) 用戶數據報協議, UDP 和 TCP 位于同一層 - 傳輸層,但它對于數據包的順序錯誤或重發沒有TCP 來的可靠。
TCP: 相當于打電話。
UDP: 相當于發電報。
??
UDP 是一種面向無連接的通信協議。
UDP 向應用程序提供了一種發送封裝的原始 IP 數據報的方法,并且發送時無需建立連接,不保證可靠數據的傳輸。
? ??
2.TCP協議和UDP協議的差別
面向無連接的數據傳輸,不可靠的,但效率高。
一次發送的數據不能超過 64KB