[Socket/計算機網絡] Java Socket編程:基礎篇

1 計算機網絡的核心概念

網絡通信

  1. 概念:兩臺設備之間通過網絡實現數據傳輸

2.網絡通信:將數據通過網絡從一臺設備傳輸到另一臺設備

  1. java.net包下提供了一系列的類或接口,供程序員使用,完成網絡通信

網絡

  1. 概念:兩臺或多臺設備通過一定物理設備連接起來構成了網絡
  2. 根據網絡的覆蓋範圍不同,對網絡進行分類:

局域網: 覆蓋範圍最小,僅僅覆蓋一個教室或一個機房
城域網:覆蓋範圍較大,可以覆蓋一個城市
廣域網:覆蓋範圍最大,可以覆蓋全國,甚至全球,萬維網是廣域網的代表

IP地址

  1. 概念: 用於唯一標識網絡中的每臺計算機/主機

  2. 查看ip地址: ipconfig

  3. ip地址的表示形式:點分十進制 xx.xx.xx.xx

  4. 每一個十進制數的範圍: 0~255

  5. ip地址的組成=網絡地址+主機地址,比如: 192.168.16.69

  6. lPv6是互聯網工程任務組設計的用於替代IPv4的下一代IP協議,其地址數量號稱可以爲全世界的每一粒沙子編上一個地址

  7. 由於IPv4最大的問題在於網絡地址資源有限,嚴重製約了互聯網的應用和發展。IPv6的使用,不僅能解決網絡地址資源數量的問題,而且也解決了多種接入設備連入互聯網的障礙

  8. 待完善的核心概念:

  • IP / Hostname / 域名 / DNS 4者的關係
  • 網絡號
  • 網段
  • 子網掩碼

IPv4地址的分類

參見: [網絡] IP地址的內外網判別與分類(IPv4) - 博客園/千千寰宇

域名

  1. 如 www.baidu.com,將ip映射到域名上,訪問域名就是訪問ip
  2. 好處:爲了方便記憶,解決記ip的困難

端口號

  1. 概念: 用於標識計算機上某個特定的網絡程序

  2. 表示形式: 以整數形式,端口範圍0~65535 [2個字節表示端口 0~2^16-1]

  3. 0~1024已經被佔用, 比如 ssh 22, ftp 21, smtp 25 http 80

  4. 常見的網絡程序端口號:

  • tomcat : 8080
  • mysql : 3306
  • oracle : 1521
  • sqlserver : 1433
  • ssh : 22

網絡通信協議

  • TCP/IP協議

TCP/IP (Transmission Control Protocol/Internet Protocol)的簡寫, 中文譯名爲傳輸控制協議/因特網互聯協議,又叫網絡通訊協議,這個協議是Internet最基本的協議、Internet國際互聯網絡的基礎。

簡單地說,就是由網絡層的IP協議和傳輸層的TCP協議組成的。

TCP協議: 傳輸控制協議

  1. 使用TCP協議前,須先建立TCP連接,形成傳輸數據通道

  2. 傳輸前,採用“三次握手"方式,是可靠的

  3. TCP協議進行通信的兩個應用進程: 客戶端、服務端

  4. 在連接中可進行大數據量的傳輸

  5. 傳輸完畢,需釋放已建立的連接,效率低

UDP協議: 用戶數據協議

  1. 將數據、源、目的封裝成數據包,不需要建立連接

  2. 每個數據報的大小限制在64K內,不適合傳輸大量數據

  3. 因無需連接,故是不可靠的

  4. 發送數據結束時無需釋放資源(因爲不是面向連接的),速度快

  5. 舉例: 發短信

2 Java Socket 概念及API

2.1 InetAddress

  • Method API
  • 獲取本機InetAddress對象 getLocalHost
  • 根據指定主機名/域名獲取ip地址對象 getByName
  • 獲取InetAddress對象的主機名 getHostName
  • 獲取InetAddress對象的地址 getHostAddress
/*
 * 演示InetAddress類的使用
 */
public class APITest {
    public static void main(String[] args) throws UnknownHostException {

        // 1.獲取本機的InetAddress對象
        InetAddress localHost = InetAddress.getLocalHost();
        System.out.println(localHost); // LAPTOP-RVFFB7FM/192.168.23.1

        // 2.根據機器的名字獲取InetAddress對象
        InetAddress inetAddress = InetAddress.getByName("C20432");
        System.out.println(inetAddress);// LAPTOP-RVFFB7FM/192.168.23.1

        // 3.根據域名返回InetAddress對象,比如www.baidu.com
        InetAddress inetAddress1 = InetAddress.getByName("www.baidu.com");
        System.out.println(inetAddress1);// www.baidu.com/120.232.145.185

        // 4.根據InetAddress對象,獲取對應的ip地址
        String address = inetAddress1.getHostAddress();
        System.out.println(address);// 120.232.145.185

        // 5.通過InetAddress對象,獲取對應的主機名
        String hostName = inetAddress1.getHostName();
        System.out.println(hostName);// www.baidu.com
    }
}

2.2 Socket

  1. 套接字(Socket)開發網絡應用程序被廣泛採用,以至於成爲事實上的網絡編程、網絡通信標準

  2. 通信的兩端都要有Socket,是兩臺機器間通信的端點,網絡通信其實就是Socket間的通信

  3. Socket允許程序把網絡連接當成一個流,數據在兩個Socket間通過IO傳輸

  4. 一般主動發起通信的應用程序屬客戶端,等待通信請求的爲服務端

3 TCP網絡編程

  1. 基於客戶端一服務端的網絡通信, 底層使用的是TCP/IP協議

  2. 應用場景舉例: 客戶端發送數據,服務端接受並顯示控制檯

  3. 基於Socket的TCP編程

3.1 案例:使用字節流(Client發 + Server收)

  1. 編寫1個Server端、1個 Client端。
  2. Server 端: 在 9999 端口監聽
  3. Client 端: 連接到 Server 端,發送 "Hello, Server",然後退出
  4. Server 端: 接收到 Client 端發送的信息,輸出,並退出。

Server端

public class SocketTCP01Server {
    public static void main(String[] args) throws IOException {
        // 1.在9999端口監聽,等待鏈接
        //  細節:要求該端口沒有其他人監聽
        ServerSocket serverSocket = new ServerSocket(9999);
 
        System.out.println("服務端,在9999端口監聽,等待連接...");
        // 2.如果沒有客戶端連接9999端口,該方法會阻塞等待連接
        // 如果有連接,返回一個socket
        Socket socket = serverSocket.accept();
 
        System.out.println(socket.getClass());
        // 3.通過socket.getInputStream() 讀取
        // 客戶端寫入到數據通道的數據,顯示
        InputStream inputStream = socket.getInputStream();
        byte[] bytes = new byte[1024];
        int readLen = 0;
        while((readLen = inputStream.read(bytes)) != -1) {
            System.out.println(new String(bytes, 0, readLen));
        }
        // 4.關閉流
        inputStream.close();
        socket.close();
        serverSocket.close();
        System.out.println("服務端退出...");
     }
}

Client端

public class SocketTCP01Client {
    public static void main(String[] args) throws IOException {
        // 1.連接9999端口,連接成功返回一個socket
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        // 2.連接上後,生成socket,通過socket.getOutputStream()
        OutputStream outputStream = socket.getOutputStream();
        // 3.輸出信息
        outputStream.write("hello, socket".getBytes());
        // 4.關閉流和連接,節省資源
        outputStream.close();
        socket.close();
        System.out.println("客戶端退出...");
    }
}

3.2 案例:使用字節流(Client發/收 + Server收/回)

  1. 編寫1個 Server 端、1個 Client 端
  2. Server 端 : 在9999端口監聽
  3. Client 端 : 連接到 Server 端,發送 "hello, server",並接收服務器端回發的 "hello, Client",再退出
  4. Server 端 : 接收到 Client 端發送的信息,輸出;併發送 "hello , client",再退出

3.2.1 Server 端

public class SocketTCP02Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);
        Socket socket = serverSocket.accept();
 
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }
 
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello Client".getBytes());
        // 設置結束標記
        socket.shutdownOutput();
        outputStream.close();
        inputStream.close();
        socket.close();
        serverSocket.close();
    }
}

3.2.2 Client 端

public class SocketTCP02Client {
    public static void main(String[] args) throws Exception{
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write("hello Server".getBytes());
        // 設置結束標記, socket.close()也做了同樣的事情
        // 但是因爲我們下面還有獲取數據通道信息,因此不能關閉,只能shutdownOutput()
        socket.shutdownOutput();
        InputStream inputStream = socket.getInputStream();
        byte[] buf = new byte[1024];
        int readLen = 0;
        while((readLen = inputStream.read(buf)) != -1) {
            System.out.println(new String(buf, 0, readLen));
        }
 
        outputStream.close();
        inputStream.close();
        socket.close();
    }
}

3.3 案例:使用字符流(Client發/收 + Server收/回)

  1. 編寫1個 Server 端、1個 Client 端
  2. Server 端 : 在9999端口監聽
  3. Client 端 : 連接到 Server 端,發送 "Hello, server",並接收服務器端回發的 "Hello, Client",再退出
  4. Server 端 : 接收到 Client 端發送的信息,輸出;併發送 "hello , client",再退出

3.3.1 Server 端

public class SocketTCP03Server {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(9999);
        Socket socket = serverSocket.accept();
 
        InputStream inputStream = socket.getInputStream();
        // IO讀取,使用字符流輸入,使用InputStreamReader將InputStream轉化爲字符流
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String s = reader.readLine();
        System.out.println(s);
 
        // 字符流方式的輸出
        OutputStream outputStream = socket.getOutputStream();
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
        writer.write("hello Client 字符流");
        writer.newLine(); // 插入一個換行符,代表寫入的內容結束
        writer.flush(); // 字符流需要手動刷新,否則數據不會寫入數據通道
 
        reader.close();
        writer.close();
        socket.close();
        serverSocket.close();
    }
}

3.3.2 Client 端

public class SocketTCP03Client {
    public static void main(String[] args) throws Exception{
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        OutputStream outputStream = socket.getOutputStream();
        // 通過輸出流,轉換爲字符流
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
        writer.write("hello Server 字符流");
        writer.newLine(); // 插入一個換行符,代表寫入的內容結束
        writer.flush(); // 字符流需要手動刷新,否則數據不會寫入數據通道
 
 
        InputStream inputStream = socket.getInputStream();
        BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
        String s = reader.readLine();
        System.out.println(s);
 
        reader.close();
        writer.close();
        socket.close();
        System.out.println("客戶端退出...");
    }
}

3.4 案例:使用字符流(Client發[圖片]/收[文本] + Server收&存[圖片]/回[文本])

  1. 編寫1個 Server 端、1個 Client 端
  2. Server 端 : 在8888端口監聽
  3. Client 端 : 連接到 Server 端,發送 1張圖片 e:\\qie.png
  4. Server 端 : 接收到 Client 端發送的圖片,保存到 src 下,發送 "收到圖片",再退出
  5. Client 端 : 接收到 Server 端發送的"收到圖片",再退出

3.4.0 StreamUtils

/**
 * 此類用於演示關於流的讀寫方法
 *
 */
public class StreamUtils {
	/**
	 * 功能:將輸入流轉換成byte[], 即可以把文件的內容讀入到byte[]
	 * @param is
	 * @return
	 * @throws Exception
	 */
	public static byte[] streamToByteArray(InputStream is) throws Exception{
		ByteArrayOutputStream bos = new ByteArrayOutputStream();//創建輸出流對象
		byte[] b = new byte[1024];//字節數組
		int len;
		while((len=is.read(b))!=-1){//循環讀取
			bos.write(b, 0, len);//把讀取到的數據,寫入bos	
		}
		byte[] array = bos.toByteArray();//然後將bos 轉成字節數組
		bos.close();
		return array;
	}
	/**
	 * 功能:將InputStream轉換成String
	 * @param is
	 * @return
	 * @throws Exception
	 */
	public static String streamToString(InputStream is) throws Exception{
		BufferedReader reader = new BufferedReader(new InputStreamReader(is));
		StringBuilder builder= new StringBuilder();
		String line;
		while((line=reader.readLine ())!=null){
			builder.append(line+"\r\n");
		}
		return builder.toString();	
	}
}

3.4.1 Server 端

public class TCPFileUploadServer {
    public static void main(String[] args) throws Exception {
        ServerSocket serverSocket = new ServerSocket(8888);
        // 等待連接
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        BufferedInputStream bis = new BufferedInputStream(inputStream);
        byte[] bytes = StreamUtils.streamToByteArray(bis);
        String destFilePath = "src\\a.jpg";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
        bos.write(bytes);
        bos.close();
        // 向客戶端回覆“收到圖片”
        BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
        writer.write("收到圖片");
        writer.flush();
        writer.close();
        bis.close();
        socket.close();
    }
}

3.4.2 Client 端

public class TCPFileUploadClient {
    public static void main(String[] args) throws  Exception{
        // 客戶端連接服務端8888端口,得到socket對象
        Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
        // 讀取本地的文件轉換爲字節數組
        String filePath = "D:\\image\\a.jpg";
        FileInputStream fileInputStream = new FileInputStream(filePath);
        BufferedInputStream bis = new BufferedInputStream(fileInputStream);
        // 該字節數組就是文件
        byte[] bytes = StreamUtils.streamToByteArray(bis);
        OutputStream outputStream = socket.getOutputStream();
//        outputStream.write(bytes);
        BufferedOutputStream bos = new BufferedOutputStream(outputStream);
        bos.write(bytes);
        
        socket.shutdownOutput(); // 寫入數據的結束標記
        BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
        String s = reader.readLine();
        System.out.println(s);
        reader.close();
        bos.close();
        bis.close();
        socket.close();
    }
}

4 UDP網絡編程

4.1 基本介紹

  1. 類 DatagramSocket 和 DatagramPacket[數據包/數據報]實現了基於 UDP協議網絡程序。

  2. UDP數據報通過數據報套接字 DatagramSocket 發送和接收,系統不保證UDP數據報一定能夠安全送到目的地,也不能確定什麼時候可以抵達。

  3. DatagramPacket 對象封裝了UDP數據報,在數據報中包含了發送端的IP地址和端口號以及接收端的IP地址和端口號。

  4. UDP協議中每個數據報都給出了完整的地址信息,因此無須建立發送方和接收方的連接

4.2 基本流程

  1. 核心的兩個類/對象 DatagramSocket與DatagramPacket

  2. 建立發送端,接收端(沒有服務端和客戶端概念)

  3. 發送數據前,建立數據包/報 DatagramPacket對象

  4. 調用DatagramSocket的發送、接收方法

  5. 關閉DatagramSocket

4.3 案例:約喫火鍋(發送端 發/收 + 接收端 收/發)

  1. 編寫1個 接收端A和1個發送端B
  2. 接收端 A : 在 9999 端口等待接收數據(receive)
  3. 發送端 B : 向接收端A發送數據 "hello, 明天喫火鍋~"
  4. 接收端 A : 接收到發送端B發送的數據,回覆 "好的,明天見",再退出
  5. 發送端 B : 接收A回覆的數據,再退出

4.3.1 發送端 B

public class UDPSenderB {
    public static void main(String[] args) throws Exception{
        // 1.創建一個DatagramSocket,監聽9998端口
        DatagramSocket socket = new DatagramSocket(9998);
        // 2.創建DatagramPacket,接收數據
        byte[] data = "你好,明天喫火鍋".getBytes();
        DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9999);
        // 3.發送數據
        socket.send(packet);
 
        // 接收消息
        data = new byte[1024];
        packet = new DatagramPacket(data, data.length);
        socket.receive(packet);
        int length = packet.getLength();
        data = packet.getData();
        String msg = new String(data, 0, length);
        System.out.println(msg);
        // 4.關閉資源
        socket.close();
    }
}

4.3.2 接收端 A

public class UDPReceiverA {
    public static void main(String[] args) throws Exception{
        // 1.創建一個DatagramSocket對象,監聽9999端口
        DatagramSocket socket = new DatagramSocket(9999);
        // 2.構建一個DatagramPacket,準備接受數據
        byte[] data = new byte[1024];
        DatagramPacket packet = new DatagramPacket(data, data.length);
        // 3.接收數據,如果沒有數據則堵塞,接收到的數據存放在packet中
        System.out.println("接收端A等待接收數據...");
        socket.receive(packet);
        // 4.接收完數據轉化爲String並輸出
        int length = packet.getLength();
        data = packet.getData();
        String msg = new String(data, 0, length);
        System.out.println(msg);
 
        // 回覆消息
        data = "好的,明天見".getBytes();
        packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9998);
        socket.send(packet);
        // 5.關閉資源
        socket.close();
 
    }
}

4.4 案例:問答(Client發/收 + Server收/發)

  1. 使用字符流的方式,編寫1個客戶端程序和服務端程序
  2. Client 端 : 發送 "name" ;
  3. Server 端 : 接收到 "name"後,返回 "我是 nova"
  4. Client 端 : 發送 "What is your hobby"
  5. Server 端 : 接收到後,返回 "編寫java程序"。

若不是這2個問題,則 Server 回覆“你說啥呢?”

4.4.1 Server

public class HomeWork01Server {
    public static void main(String[] args) throws Exception{
        System.out.println("服務器等待接收消息...");
 
        DatagramSocket socket = new DatagramSocket(9999);
        byte[] data = new byte[1024];
        DatagramPacket packet = new DatagramPacket(data, data.length);
        socket.receive(packet);
 
        int length = packet.getLength();
        data = packet.getData();
        String msg = new String(data, 0, length);
        System.out.println(msg);
        String returnMsg = "";
        if("name".equals(msg)) {
            returnMsg = "我是nova";
        } else if("hobby".equals(msg)){
            returnMsg = "編寫java程序";
        } else {
            returnMsg = "你說啥呢";
        }
        data = returnMsg.getBytes();
        packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9998);
        socket.send(packet);
 
        socket.close();
    }
}

4.4.2 Client

public class HomeWork01Client {
    public static void main(String[] args) throws Exception{
        System.out.println("請輸入您的消息:");
 
        Scanner sc = new Scanner(System.in);
        String msg = sc.next();
        DatagramSocket socket = new DatagramSocket(9998);
        byte[] data = msg.getBytes();
        DatagramPacket packet = new DatagramPacket(data, 0, data.length, InetAddress.getByName("192.168.23.1"), 9999);
        socket.send(packet);
 
        data = new byte[1024];
        packet = new DatagramPacket(data, data.length);
        socket.receive(packet);
        int length = packet.getLength();
        data = packet.getData();
        System.out.println(new String(data, 0, length));
 
    }
}

4.5 案例:我要聽音樂(Client發[文本]/收[文件] + Server收[文本]/回[文件])

  1. 編寫客戶端程序和服務器端程序
  2. Client 端:可以輸入1個音樂文件名,如:高山流水
  3. Server 端:收到音樂名後,可以給客戶端返回這個音樂文件。如果服務器沒有這個文件,則返回 1個默認的音樂即可。
  4. Client 端:收到音樂文件後,保存到本地 e:\

4.5.1 Server

public class HomeWork03Server {
    public static void main(String[] args) throws Exception{
        System.out.println("服務端監聽9999端口,等待接收文件名...");
        // 1.創建一個socket對象
        ServerSocket serverSocket = new ServerSocket(9999);
        // 2.接收傳遞過來的文件名
        Socket socket = serverSocket.accept();
        InputStream inputStream = socket.getInputStream();
        // 這裏使用了while循環,是考慮客戶端可能發送的文件名較大的情況
        byte[] buf = new byte[1024];
        int len = 0;
        String downLoadFileName = "";
        while((len = inputStream.read(buf)) != -1) {
            downLoadFileName += new String(buf, 0, len);
        }
 
        System.out.println("客戶端希望下載的文件名: " + downLoadFileName);
        // 3.確定文件名
        // 如果客戶端要求下載高山流水,則獲取文件流返回,否則,返回無名mp3
        String filePath = "";
        if("高山流水".equals(downLoadFileName)) {
            filePath = "src//高山流水.mp3";
        } else {
            filePath = "src//無名.mp3";
        }
        // 4.獲取文件輸入流並轉化爲字節數組
        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
        byte[] data = StreamUtils.streamToByteArray(bis);
        // 5.獲取socket輸出流並寫出數據
        BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
        bos.write(data);
        socket.shutdownOutput();
        // 6.關閉資源
        bos.close();
        bis.close();
        socket.close();
        serverSocket.close();
 
        System.out.println("服務端傳輸文件完畢,退出");
    }
}

4.5.2 Client

public class HomeWork03Client {
    public static void main(String[] args) throws Exception{
        System.out.println("請輸入下載的文件名:");
        // 1.獲取用戶輸入的文件名
        Scanner sc = new Scanner(System.in);
        String fileName = sc.next();
        // 2.創建連接
        Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
        // 3.獲取socket關聯的輸出流並寫出
        OutputStream outputStream = socket.getOutputStream();
        outputStream.write(fileName.getBytes());
        //   設置寫入結束的標記
        socket.shutdownOutput();
        // 4.獲取輸入流並轉化爲字節數組
        BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
        byte[] data = StreamUtils.streamToByteArray(bis);
 
        // 5.獲取寫出流,將文件寫入磁盤
        String fileNamePath = "src//com//hspedu/homework//" + fileName + ".mp3";
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fileNamePath));
        bos.write(data);
 
        // 6.關閉資源
        outputStream.close();
        bos.close();
        bis.close();
        socket.close();
 
        System.out.println("文件下載完畢,退出");
 
    }
}

X 參考文獻

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