【網絡通信協議】
通過計算機網絡可以使多臺計算機實現連接,位於同一個網絡中的計算機在進行連接和通訊時,需要遵循一定的規則。在計算機網絡中,這些連接和通訊的規則被稱爲網絡通信協議。它對數據的傳輸格式、傳輸速率、傳輸步驟等做了統一規定,通信雙方必須同時遵守才能完成數據的交換。目前應用最廣泛的是TCP/IP、UDP協議。
【IP地址和端口號】
IP地址:要想使網絡中的計算機能夠進行通信,必須爲每一個計算機指定一個標識號,通過這個標識號可以指定接受數據的計算機和發送數據的計算機。在TCP/IP協議中,這個標識號就是IP地址,它可以唯一標識一臺計算機。
端口號:通過IP地址可以訪問到指定的計算機,如果要訪問目標計算機中的某個應用程序,還要指定端口號。在計算機中,不同的應用程序是通過端口號區分的。端口號是用2個字節表示的,它的取值範圍爲0~65535,用戶的普通程序應使用1024以上的端口號。
【InetAddress】用於封裝一個IP地址,並提供一系列與IP地址相關的方法,前兩個方法用於獲取該類的實例對象。
import java.net.InetAddress;
import java.net.UnknownHostException;
/*
* java.net.InetAddress 表示互聯網中的IP地址
*
* 靜態方法: static InetAddress getLocalHost()
* 返回本地主機, 返回InetAddress對象(此對象不能new,只能通過調用靜態方法得到)
*
* static InetAddress getByName(String hostName) 傳遞主機名,獲取IP地址
*
* 非靜態方法:String getHostAddress() 獲取主機IP地址
* String getHostName() 獲取主機名
*/
public class InetAddressDemo {
public static void main(String[] args) throws UnknownHostException {
//fun_1();
fun_2();
}
// 傳遞主機名,獲取IP地址
public static void fun_2() throws UnknownHostException{
InetAddress inet = InetAddress.getByName("peng");
System.out.println(inet); // peng/192.168.3.32
}
// 獲得本機IP
public static void fun_1() throws UnknownHostException{
InetAddress inet = InetAddress.getLocalHost(); // 輸出的是主機名和IP地址
System.out.println(inet); // peng/192.168.3.32
// 對主機名和IP 進行分割
String host = inet.toString();
String[] strs = host.split("/");
for(String str:strs){
System.out.println(str); // peng 192.168.3.32
}
// 非靜態方法分別獲取主機名和IP
String ip = inet.getHostAddress();
String name = inet.getHostName();
System.out.println(ip+" "+name); // 192.168.3.32 peng
}
}
【UDP協議】
UDP是無連接通信協議,即在數據傳輸時,數據的發送端和接收端不建立邏輯連接。
特點:消耗資源少,通信效率高,通常用於視頻、音頻、普通數據的傳輸,例如視頻會議都使用UDP協議。由於UDP面向無連接性,因此傳輸重要數據時不建議使用UDP協議。
DatagramPacket類,用於封裝UDP通信中發送或者接收的數據。要想創建DatagramPacket類對象,先要了解它的構造方法。
發送端:不但接收存放數據的字節數組,還需要指定發送端的IP和端口號
接收端:接收端的構造方法只需接收一個字節數組,來存放接收到的數據。
DatagramSocket類,這個類的實例化對象可以發送和接收DatagramPacket數據包。
發送端:創建DatagramSocket對象,
接收端:構造方法中指定一個端口號,這樣可以監聽到指定端口
【程序】
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/*
* 實現UDP協議的發送端:
* 實現封裝數據的類:java.net.DatagramPacket 將你的數據包裝
* 實現數據傳輸的類:java.net.DatagramSocket 將數據包發出去
*
* 實現步驟:
* 1. 創建DatagramPacket對象,封裝數據,接受數據的地址和端口
* 2. 創建DatagramSocket對象
* 3. 調用DatagramSocket類方法send,發送數據包
* 4. 關閉資源
*
* DatagramPacket構造方法: Datagrampacket(byte[] buf, int length,InetAddress address, int port)
* DatagramSocket構造方法:DatagramSocket()空參 方法:send(DatagramPacket d)
*/
public class UDPSend {
public static void main(String[] args) throws IOException {
// 創建數據包對象,封裝要發送的數據,接收端IP 和端口號
byte[] data = "你好UDP".getBytes();
// 創建InetAddress對象,封裝要發送的IP地址
InetAddress inet = InetAddress.getByName("192.168.3.32");
//System.out.println(inet); // /192.168.3.32
DatagramPacket dp = new DatagramPacket(data,data.length,inet,6000);
// 創建DatagramSocket對象,數據包的發送和接收對象
DatagramSocket ds = new DatagramSocket();
// 調用ds方法 send,發送數據包
ds.send(dp);
// 關閉資源
ds.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/*
* 實現UDP接收端:
* 實現封裝數據包:java.net.DatagramPacket 將數據接收
* 實現數據輸出傳輸:java.net.DatagramSocket 接收數據包
* 實現步驟:
* 1. 創建DatagramSocket對象,綁定端口號 要和發送端的端口號一致
* 2. 創建字節數組,接收發來的數據
* 3. 創建數據包對象DatagramPacket對象
* 4. 調用DatagramSocket對象方法, receive(DatagramPacket dp) 接收數據,數據放在數據包中
* 5. 拆包 發送的IP地址 數據包對象DatagramPacket 方法getAddress() 獲取的是發送端的IP地址對象 返回值是InetAddress對象
* 接收到的字節個數 數據包對象DatagramPacket 方法getLength()
* 發送方的端口號 數據包對象DatagramPacket 方法getPort() 獲得發送端的端口
* 6. 關閉資源
*/
public class UDPReceive {
public static void main(String[] args)throws IOException {
// 創建數據包傳輸對象DatagramSocket 綁定端口號
DatagramSocket ds = new DatagramSocket(6000);
// 創建字節數組
byte[] data = new byte[1024]; // 1024*64
// 創建數據包對象,傳遞字節數組
DatagramPacket dp = new DatagramPacket(data, data.length);
// 調用ds對象方法receive傳遞數據包
ds.receive(dp);
// 獲取得到字節的個數
int length = dp.getLength(); // 7
// 獲取發送端的IP地址
String ip = dp.getAddress().getHostAddress();
// 獲得發送端的端口號
int port = dp.getPort();
System.out.println(new String(data,0,length)+" "+ip+" "+port);
ds.close();
}
}
【鍵盤輸入聊天】
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/*
* 實現UDP發送,發送鍵盤輸入的數據
*/
/*
* 實現UDP協議的發送端:
* 實現封裝數據的類:java.net.DatagramPacket 將你的數據包裝
* 實現數據傳輸的類:java.net.DatagramSocket 將數據包發出去
*
* 實現步驟:
* 1. 創建DatagramPacket對象,封裝數據,接受數據的地址和端口
* 2. 創建DatagramSocket對象
* 3. 調用DatagramSocket類方法send,發送數據包
* 4. 關閉資源
*
* DatagramPacket構造方法: Datagrampacket(byte[] buf, int length,InetAddress address, int port)
* DatagramSocket構造方法:DatagramSocket()空參 方法:send(DatagramPacket d)
*/
public class UDPSend {
public static void main(String[] args) throws IOException {
Scanner sc = new Scanner(System.in);
// 創建InetAddress對象,封裝要發送的IP地址
InetAddress inet = InetAddress.getByName("192.168.3.32");
//System.out.println(inet); // /192.168.3.32
// 創建DatagramSocket對象,數據包的發送和接收對象
DatagramSocket ds = new DatagramSocket();
while(true){
String message = sc.nextLine();
// 創建數據包對象,封裝要發送的數據,接收端IP 和端口號
byte[] data = message.getBytes();
DatagramPacket dp = new DatagramPacket(data,data.length,inet,6000);
// 調用ds方法 send,發送數據包
ds.send(dp);
}
// 關閉資源
//ds.close();
}
}
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
/*
* 實現UDP接收端 永不停息的接收端
*/
/*
* 實現UDP接收端:
* 實現封裝數據包:java.net.DatagramPacket 將數據接收
* 實現數據輸出傳輸:java.net.DatagramSocket 接收數據包
* 實現步驟:
* 1. 創建DatagramSocket對象,綁定端口號 要和發送端的端口號一致
* 2. 創建字節數組,接收發來的數據
* 3. 創建數據包對象DatagramPacket對象
* 4. 調用DatagramSocket對象方法, receive(DatagramPacket dp) 接收數據,數據放在數據包中
* 5. 拆包 發送的IP地址 數據包對象DatagramPacket 方法getAddress() 獲取的是發送端的IP地址對象 返回值是InetAddress對象
* 接收到的字節個數 數據包對象DatagramPacket 方法getLength()
* 發送方的端口號 數據包對象DatagramPacket 方法getPort() 獲得發送端的端口
* 6. 關閉資源
*/
public class UDPReceive {
public static void main(String[] args)throws IOException {
// 創建數據包傳輸對象DatagramSocket 綁定端口號
DatagramSocket ds = new DatagramSocket(6000);
// 創建字節數組
byte[] data = new byte[1024]; // 1024*64
// 創建數據包對象,傳遞字節數組
while(true){
DatagramPacket dp = new DatagramPacket(data, data.length);
// 調用ds對象方法receive傳遞數據包
ds.receive(dp);
// 獲取得到字節的個數
int length = dp.getLength(); // 7
// 獲取發送端的IP地址
String ip = dp.getAddress().getHostAddress();
// 獲得發送端的端口號
int port = dp.getPort();
System.out.println(new String(data,0,length)+" "+ip+" "+port);
}
//ds.close();
}
}
【TCP通信】
TCP通信是嚴格區分客戶端和服務器端的,必須先有客戶端先去連接服務器端,才能實現通信,服務器端不可以主動連接客戶端,且服務器端的程序需要事先啓動,等待客戶端的連接。
Java中兩個用於實現TCP通信的類,ServerSocket類,用於表示服務器;socket類,用於表示客戶端。
(1)ServerSocket 實現服務端程序
使用該構造方法,在創建ServerSocket對象時,將其綁定到一個指定的端口上 。
ServerSocket對象用於監聽某臺計算機的某個端口號,調用該對象的方法accept(),接收來自客戶端的請求。執行accept()方法後,服務器端程序會發生阻塞,直到客戶端發出連接請求。accept()方法纔會返回一個Socket對象,用於和客戶端實現通信,程序纔會繼續向下執行。
(2)Socket 實現客戶端程序
使用該構造方法創建Socket對象時,會根據參數去連接在指定主機上的指定端口上運行的服務器程序,host接收的是一個字符串型的IP地址。(常用)
參數address用於接收一個InetAddress類型的對象,該對象用於封裝一個IP地址。
InputStream getInputStream() |
該方法返回一個InputStream類型的輸入流對象。 若該對象由服務器端的Socket返回,用於讀取客戶端發送的數據,反之用於讀取服務器端發送的數據。 |
OutputStream getOutputStream() |
該方法返回一個OutputStream類型的輸出流對象。 若該對象由服務器端的Socket返回,用於向客戶端發送數據,反之用於向服務器端發送數據。 |
void close() | 用於關閉Socket連接,結束本次通信。 |
int getPort() | 該對象是Socket對象與服務器連接的端口號 |
InetAddress getLocalAddress() | 用於獲取Socket對象綁定的本地IP,並將IP地址封裝成InetAddress類型對象返回 |
【程序】
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/*
* 實現TCP服務程序
* java.net.ServerSocket
* 構造方法:
* ServerSocket(int port) 傳遞端口號
*
* 很重要的事情:必須要獲得 客戶端的套接字對象Socket
* Socket accept() 偵聽並接受此套接字的連接
* 服務器可以獲得 客戶端套接字的對象,
*/
public class TCPServer {
public static void main(String[] args) throws IOException {
// 創建綁定到特定端口的服務器套接字
ServerSocket server = new ServerSocket(5001);
// 調用服務器套接字對象中方法accept() 獲得客戶端套接字對象
Socket socket = server.accept();
// 通過客戶端套接字對象方法,獲取字節輸入流, 讀取到的是客戶端發送來的數據
InputStream in = socket.getInputStream();
byte[] data = new byte[1024];
int len = in.read(data);
System.out.println(new String(data, 0, len));
// 服務器端向客戶端回數據,字節輸出流,通過客戶端套接字對象 獲得字節輸出流
OutputStream out = socket.getOutputStream();
out.write("謝謝,收到".getBytes());
socket.close();
server.close();
}
}
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
* 實現TCP客戶端,連接到服務器 和服務器實現數據交換
* java.net.Socket 實現TCP客戶端程序的類
* 構造方法:
* Socket(String host, int port) 傳遞服務器IP和端口號
* 注意:構造方法只要運行,就會和服務器進行連接,若連接失敗,拋出異常
*
* OutputStream getOutputStream() 返回套接字的輸出流 作用:將數據輸出,輸出到服務器
* InputStream getInputStream() 返回套接字的輸入流 作用: 從服務器端讀取數據
*
* 客戶端、服務器端數據交換,必須使用套接字對象Socket中的方法獲取IO流,自己new的流不行
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
// 創建Socket對象,連接服務器
Socket socket = new Socket("192.168.3.32",5001);
// 通過客戶端的套接字對象Socket方法,獲得字節輸出流,將數據寫入服務器端
OutputStream out = socket.getOutputStream();
out.write("服務器ok".getBytes());
// 讀取服務端發回的數據,使用Socket套接字對象中的方法,獲得字節輸入流
InputStream in = socket.getInputStream();
byte[] data = new byte[1024];
int len = in.read(data);
System.out.println(new String(data,0,len));
socket.close();
}
}
【TCP 實現 上傳圖片到服務器端】
/*
* TCP 圖片上傳服務器
* 實現步驟:
* 1. 創建ServerSocket套接字對象,綁定監聽端口號8000
* 2. 方法accept()獲得客戶端的連接對象
* 3. 客戶端連接對象獲取字節輸入流,讀取客戶端發送的圖片
* 4. 創建File對象,綁定上傳文件夾 (判斷文件件是否存在,若不存在,創建文件夾)
* 5. 創建字節輸出流,數據目的是File對象所在的文件夾
* 6. 字節流讀取圖片,字節流將圖片寫到指定文件夾中
* 7. 將“上傳成功”返回客戶端
* 8. 關閉資源
*
*/
public class TCPServer {
public static void main(String[] args) throws IOException{
ServerSocket server = new ServerSocket(8000);
// 調用對象方法accept()獲取客戶端對象
Socket socket = server.accept();
//通過客戶端連接對象,獲取字節輸入流,讀取客戶端發來的圖片
InputStream in = socket.getInputStream();
// 將目的文件夾上傳到File封裝的文件夾
File upload = new File("E:\\workspace\\day31\\src\\usst\\javacode\\upload");
// 若不存在,則創建該文件夾
if(!upload.exists())
upload.mkdirs();
// 防止文件同名被覆蓋,重新定義文件夾名字 規則:usst+毫秒值+6位隨機數
String fileName = "usst" + System.currentTimeMillis() + new Random().nextInt(999999) + ".jpg";
// 創建字節輸出流,將圖片寫入到指定的文件夾中
FileOutputStream fos = new FileOutputStream(upload + File.separator + fileName);
// 讀 寫字節數組
int len = 0;
byte[] bytes = new byte[1024];
while((len = in.read(bytes)) != -1){
fos.write(bytes,0,len);
}
// 通過客戶端連接對象獲取字節輸出流,將“上傳成功”發送至客戶端
OutputStream out = socket.getOutputStream();
out.write("上傳成功".getBytes());
fos.close();
socket.close();
server.close();
}
}
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
/*
* TCP實現上傳圖片到服務器端:
* 實現步驟:
* 1. Socket套接字連接服務器端
* 2. 通過Socket對象方法,獲得字節輸出流,服務器端寫圖片
* 3. 使用自己的流對象,讀取圖片數據源 FileInputStream
* 4. 讀取圖片,使用字節輸出流,將圖片寫到服務器 採用字節數組進行緩衝
* 5. 通過Socket套接字獲取字節輸入流 讀取服務器端發回來的“上傳成功”
* 6. 關閉資源
*
*/
public class TCPClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("192.168.3.32", 8000);
// 獲取字節輸出流,將圖片傳送到服務器
OutputStream out = socket.getOutputStream();
// 創建字節輸入流,讀取本機上的數據源圖片
FileInputStream fis = new FileInputStream("E:\\workspace\\day31\\src\\usst\\javacode\\java學習路程.png");
// 開始讀 寫字節數組
int len = 0;
byte[] bytes = new byte[1024];
while((len = fis.read(bytes)) != -1){
out.write(bytes, 0, len);
}
// 給服務器寫終止序列
socket.shutdownOutput();
// 獲取字節輸入流,接受服務器發回來的“上傳成功”
InputStream in = socket.getInputStream();
len = in.read(bytes);
System.out.println(new String(bytes, 0, len));
fis.close();
socket.close();
}
}
【多線程上傳案例】
實現服務器端可以同時接收多個客戶端上傳的文件。修改服務器端的代碼。
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Random;
public class Upload implements Runnable{
private Socket socket;
// 構造方法傳參
public Upload(Socket socket){
this.socket = socket;
}
// 重寫run方法
public void run(){
try{
//通過客戶端連接對象,獲取字節輸入流,讀取客戶端發來的圖片
InputStream in = socket.getInputStream();
// 將目的文件夾上傳到File封裝的文件夾
File upload = new File("E:\\workspace\\day31\\src\\usst\\javacode\\upload");
// 若不存在,則創建該文件夾
if(!upload.exists())
upload.mkdirs();
// 防止文件同名被覆蓋,重新定義文件夾名字 規則:usst+毫秒值+6位隨機數
String fileName = "usst" + System.currentTimeMillis() + new Random().nextInt(999999)+socket.getPort()+ ".jpg";
// 創建字節輸出流,將圖片寫入到指定的文件夾中
FileOutputStream fos = new FileOutputStream(upload + File.separator + fileName);
// 讀 寫字節數組
int len = 0;
byte[] bytes = new byte[1024];
while((len = in.read(bytes)) != -1){
fos.write(bytes,0,len);
}
// 通過客戶端連接對象獲取字節輸出流,將“上傳成功”發送至客戶端
OutputStream out = socket.getOutputStream();
out.write("上傳成功".getBytes());
fos.close();
socket.close();
}catch(Exception ex){
ex.printStackTrace();
}
}
}
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
/*
*
*/
public class TCPThreadServer {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8004);
while(true){
// 獲取一個客戶端就必須開啓一個新線程
Socket socket = server.accept();
new Thread(new Upload(socket)).start();
}
}
}
【UDP與TCP的區別】
都能實現兩臺計算機的通信,通信的兩端都要創建socket對象。
UDP只有發送端和接收端,不區分客戶端和服務器端,計算機之間可以任意發送數據;TCP是嚴格區分客戶端和服務器端的,服務器端要事先開啓,必須先由客戶端去連接服務器端,才能實現通信。
通信時,首先創建代表服務器端的ServerSocket對象,該對象相當於開啓一個服務,並等待客戶的連接,然後創建代表客戶端的Socket對象,向服務器端發出連接請求,服務器端響應請求,兩者建立連接開始通信。