- 第21章 網路程式設計
- 網路的相關概念
- 網路通訊
- 網路
- ip 地址
- ipv4 地址分類
- 域名
- 網路通訊協議
- TCP 和 UDP
- InetAddress 類
- 相關方法
- 應用案例
- Socket
- 基本介紹
- TCP 網路通訊程式設計
- 基本介紹
- 應用案例1(使用位元組流)
- 應用案例2(使用位元組流)
- 應用案例3(使用字元流)
- 應用案例4
- netstat 指令
- TCP 網路通訊
- UDP 網路通訊程式設計
- 基本介紹
- 基本流程
- 應用案例
- 本章作業
- 網路的相關概念
第21章 網路程式設計
網路的相關概念
網路通訊
- 概念:兩臺裝置之間透過網路實現資料傳輸
- 網路通訊:將資料透過網路從一臺裝置傳輸到另一臺裝置
- java.net包下提供了一系列的類或介面,供程式設計師使用,完成網路通訊
網路
- 概念:兩臺或多臺裝置透過一定物理裝置連線起來構成了網路
- 根據網路的覆蓋範圍不同,對網路進行分類:
- 區域網:覆蓋範圍最小,僅僅覆蓋一個教室或一個機房
- 都會網路:覆蓋範圍較大,可以覆蓋一個城市
- 廣域網:覆蓋範圍最大,可以覆蓋全國,甚至全球,全球資訊網是廣域網的代表
ip 地址
-
概念:用於唯一標識網路中的每臺計算機/主機
-
檢視ip地址:
ipconfig
-
ip地址的表示形式: 點分十進位制XX.XX.XX.XX
-
每一個十進位制數的範圍:0~255
-
ip地址的組成=網路地址+主機地址,比如:192.168.16.69
-
iPv6是網際網路工程任務組設計的用於替代IPv4的下一代IP協議,其地址數量號稱可以為全世界的每一粒沙子編上一個地址。
-
由於IPv4最大的問題在於網路地址資源有限,嚴重製約了網際網路的應用和發展。IPv6的使用,不僅能解決網路地址資源數量的問題,而且也解決了多種接入裝置連入網際網路的障礙。
ipv4 地址分類
特殊的:127.0.0.1表示本機地址
域名
- www.baidu.com
- 好處: 為了方便記憶,解決記
ip
的困難 - 概念: 將
ip
地址對映成域名,這裡怎麼對映上——HTTP協議
埠號
- 概念: 用於標識計算機上某個特定的網路程式
- 表示形式: 以整數形式,埠範圍0~65535 [2個位元組表示埠 0~2^16-1]
- 0~1024已經被佔用, 比如ssh 22, ftp 21, smtp 25 http 80
- 常見的網路程式埠號
- tomcat: 8080
- mysql: 3306
- oracle: 1521
- sqlserver: 1433
網路通訊協議
協議(tcp/ip)
TCP/IP (Transmission Control Protocol/Internet Protocol) 的簡寫,中文譯名為傳輸控制協議/因特網互聯協議,又叫網路通訊協議,這個協議是lnternet最基本的協議、Internet國際網際網路絡的基礎,簡單地說,就是由網路層的IP協議和傳輸層的TCP協議組成的。
TCP 和 UDP
TCP協議:傳輸控制協議
- 使用TCP協議前,須先建立TCP連線,形成傳輸資料通道
- 傳輸前,採用“三次握手"方式,是可靠的
- TCP協議進行通訊的兩個應用程序: 客戶端、服務端
- 在連線中可進行大資料量的傳輸
- 傳輸完畢,需釋放已建立的連線, 效率低
UDP協議:使用者資料協議
- 將資料、源、目的封裝成資料包,不需要建立連線
- 每個資料包的大小限制在64K內,不適合傳輸大量資料
- 因無需連線,故是不可靠的
- 傳送資料結束時無需釋放資源(因為不是面向連線的),速度快
InetAddress 類
相關方法
- 獲取本機InetAddress物件getLocalHost
- 根據指定主機名/域名獲取ip地址物件getByName
- 獲取InetAddress物件的主機名getHostName
- 獲取InetAddress物件的地址getHostAddress
應用案例
編寫程式碼,獲取計算機的主機名和IP 地址相關API
package com.hspedu.api;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
* 演示InetAddress 類的使用
*/
public class API_ {
public static void main(String[] args) throws UnknownHostException {
//1. 獲取本機的InetAddress 物件
InetAddress localHost = InetAddress.getLocalHost();
System.out.println(localHost);//DESKTOP-S4MP84S/192.168.12.1
//2. 根據指定主機名 獲取 InetAddress物件
InetAddress host1 = InetAddress.getByName("DESKTOP-S4MP84S");
System.out.println("host1=" + host1);//DESKTOP-S4MP84S/192.168.12.1
//3. 根據域名返回 InetAddress物件, 比如 www.baidu.com 對應
InetAddress host2 = InetAddress.getByName("www.baidu.com");
System.out.println("host2=" + host2);//www.baidu.com / 110.242.68.4
//4. 透過 InetAddress 物件,獲取對應的地址
String hostAddress = host2.getHostAddress();//IP 110.242.68.4
System.out.println("host2 對應的ip = " + hostAddress);//110.242.68.4
//5. 透過 InetAddress 物件,獲取對應的主機名/或者的域名
String hostName = host2.getHostName();
System.out.println("host2對應的主機名/域名=" + hostName); // www.baidu.com
}
}
Socket
基本介紹
- 套接字(Socket)開發網路應用程式被廣泛採用,以至於成為事實上的標準。
- 通訊的兩端都要有Socket,是兩臺機器間通訊的端點。
- 網路通訊其實就是Socket間的通訊。
- Socket允許程式把網路連線當成一個流,資料在兩個Socket間透過IO傳輸。
- 一般主動發起通訊的應用程式屬客戶端,等待通訊請求的為服務端。
示意圖
TCP 網路通訊程式設計
基本介紹
- 基於客戶端—服務端的網路通訊
- 底層使用的是TCP/IP協議
- 應用場景舉例: 客戶端傳送資料,服務端接受並顯示控制檯
- 基於Socket的TCP程式設計
最後需要關閉socket,不然連結太多會出現問題。
應用案例1(使用位元組流)
- 編寫一個伺服器端,和一個客戶端
- 伺服器端在9999埠監聽
- 客戶端連線到伺服器端,傳送"hello, server",然後退出
- 伺服器端接收到客戶端傳送的資訊,輸出,並退出
ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
package com.hspedu.socket;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
/**
* 客戶端,傳送 "hello, server" 給服務端
*/
public class SocketTCP01Client {
public static void main(String[] args) throws IOException {
//思路
//1. 連線服務端 (ip , 埠)
//解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
// 如果連結網路,第一個引數可以改為IP
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客戶端 socket返回=" + socket.getClass());
//2. 連線上後,生成Socket, 透過socket.getOutputStream()
// 得到 和 socket物件關聯的輸出流物件
OutputStream outputStream = socket.getOutputStream();
//3. 透過輸出流,寫入資料到 資料通道
outputStream.write("hello, server".getBytes());
//4. 關閉流物件和socket, 必須關閉
outputStream.close();
socket.close();
System.out.println("客戶端退出.....");
}
}
package com.hspedu.socket;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務端
*/
public class SocketTCP01Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本機 的9999埠監聽, 等待連線
// 細節: 要求在本機沒有其它服務在監聽9999
// 細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服務端,在9999埠監聽,等待連線..");
//2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
// 如果有客戶端連線,則會返回Socket物件,程式繼續
Socket socket = serverSocket.accept();
System.out.println("服務端 socket =" + socket.getClass());
//
//3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
InputStream inputStream = socket.getInputStream();
//4. IO讀取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根據讀取到的實際長度,顯示內容.
}
//5.關閉流和socket
inputStream.close();
socket.close();
serverSocket.close();// 最後也需要關閉
}
}
應用案例2(使用位元組流)
- 編寫一個服務端,和一個客戶端。
- 伺服器端在9999埠監聽。
- 客戶端連線到服務端,傳送"hello, server",並接收伺服器端回發的"hello,client",再退出。
- 伺服器端接收到客戶端傳送的資訊,輸出,併傳送"hello, client".再退出。
注意:設定結束標記。確保輸出結束。
package com.hspedu.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本機 的9999埠監聽, 等待連線
// 細節: 要求在本機沒有其它服務在監聽9999
// 細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服務端,在9999埠監聽,等待連線..");
//2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
// 如果有客戶端連線,則會返回Socket物件,程式繼續
Socket socket = serverSocket.accept();
System.out.println("服務端 socket =" + socket.getClass());
//
//3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
InputStream inputStream = socket.getInputStream();
//4. IO讀取
byte[] buf = new byte[1024];
int readLen = 0;
while ((readLen = inputStream.read(buf)) != -1) {
System.out.println(new String(buf, 0, readLen));//根據讀取到的實際長度,顯示內容.
}
//5. 獲取socket相關聯的輸出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write("hello, client".getBytes());
// 設定結束標記
socket.shutdownOutput();
//6.關閉流和socket
outputStream.close();
inputStream.close();
socket.close();
serverSocket.close();//關閉
}
}
package com.hspedu.socket;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
/**
* 客戶端,傳送 "hello, server" 給服務端
*/
@SuppressWarnings({"all"})
public class SocketTCP02Client {
public static void main(String[] args) throws IOException {
//思路
//1. 連線服務端 (ip , 埠)
//解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客戶端 socket返回=" + socket.getClass());
//2. 連線上後,生成Socket, 透過socket.getOutputStream()
// 得到 和 socket物件關聯的輸出流物件
OutputStream outputStream = socket.getOutputStream();
//3. 透過輸出流,寫入資料到 資料通道
outputStream.write("hello, server".getBytes());
// 設定結束標記
socket.shutdownOutput();
//4. 獲取和socket關聯的輸入流. 讀取資料(位元組),並顯示
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));
}
//5. 關閉流物件和socket, 必須關閉
inputStream.close();
outputStream.close();
socket.close();
System.out.println("客戶端退出.....");
}
}
應用案例3(使用字元流)
- 編寫一個服務端,和一個客戶端
- 服務端在9999埠監聽
- 客戶端連線到服務端,傳送"hello, server”,並接收服務端回發的“hello,client",,再退出
- 服務端接收到客戶端傳送的資訊,輸出,併傳送"hello, client",再退出
這裡結束標記也可以採用 writer.newLine();
,但是這要求對方讀取必須使用 readLine()
。
package com.hspedu.socket;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* 客戶端,傳送 "hello, server" 給服務端, 使用字元流
*/
@SuppressWarnings({"all"})
public class SocketTCP03Client {
public static void main(String[] args) throws IOException {
//思路
//1. 連線服務端 (ip , 埠)
//解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
System.out.println("客戶端 socket返回=" + socket.getClass());
//2. 連線上後,生成Socket, 透過socket.getOutputStream()
// 得到 和 socket物件關聯的輸出流物件
OutputStream outputStream = socket.getOutputStream();
//3. 透過輸出流,寫入資料到 資料通道, 使用字元流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello, server 字元流");
bufferedWriter.newLine();//插入一個換行符,表示寫入的內容結束, 注意,要求對方使用readLine()!!!!
bufferedWriter.flush();// 如果使用的字元流,需要手動重新整理,否則資料不會寫入資料通道
//4. 獲取和socket關聯的輸入流. 讀取資料(字元),並顯示
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
//5. 關閉流物件和socket, 必須關閉
bufferedReader.close();//關閉外層流
bufferedWriter.close();
socket.close();
System.out.println("客戶端退出.....");
}
}
package com.hspedu.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務端, 使用字元流方式讀寫
*/
@SuppressWarnings({"all"})
public class SocketTCP03Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本機 的9999埠監聽, 等待連線
// 細節: 要求在本機沒有其它服務在監聽9999
// 細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服務端,在9999埠監聽,等待連線..");
//2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
// 如果有客戶端連線,則會返回Socket物件,程式繼續
Socket socket = serverSocket.accept();
System.out.println("服務端 socket =" + socket.getClass());
//
//3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
InputStream inputStream = socket.getInputStream();
//4. IO讀取, 使用字元流, 老師使用 InputStreamReader 將 inputStream 轉成字元流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);//輸出
//5. 獲取socket相關聯的輸出流
OutputStream outputStream = socket.getOutputStream();
// 使用字元輸出流的方式回覆資訊
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write("hello client 字元流");
bufferedWriter.newLine();// 插入一個換行符,表示回覆內容的結束
bufferedWriter.flush();//注意需要手動的flush
//6.關閉流和socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();//關閉
}
}
應用案例4
- 編寫一個服務端,和一個客戶端
- 伺服器端在8888埠監聽
- 客戶端連線到服務端,傳送一張圖片e:llqie.png
- 伺服器端接收到客戶端傳送的圖片,儲存到src下,傳送“收到圖片"再退出
- 客戶端接收到服務端傳送的“收到圖片”,再退出
- 該程式要求使用StreamUtils.java, 我們直接使用其中封裝好的方法。
package com.hspedu.upload;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
/**
* 檔案上傳的客戶端
*/
public class TCPFileUploadClient {
public static void main(String[] args) throws Exception {
//客戶端連線服務端 8888,得到Socket物件
Socket socket = new Socket(InetAddress.getLocalHost(), 8888);
//建立讀取磁碟檔案的輸入流
//String filePath = "e:\\qie.png";
String filePath = "e:\\abc.mp4";
BufferedInputStream bis = new BufferedInputStream(new FileInputStream(filePath));
// 把檔案讀到字元陣列中!!!!!
// bytes 就是filePath對應的位元組陣列
byte[] bytes = StreamUtils.streamToByteArray(bis);
//透過socket獲取到輸出流, 將bytes資料傳送給服務端
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
bos.write(bytes); //將檔案對應的位元組陣列的內容,寫入到資料通道
bis.close();
socket.shutdownOutput(); //設定寫入資料的結束標記
//=====接收從服務端回覆的訊息=====
InputStream inputStream = socket.getInputStream();
//使用StreamUtils 的方法,直接將 inputStream 讀取到的內容 轉成字串
String s = StreamUtils.streamToString(inputStream);
System.out.println(s);
//關閉相關的流
inputStream.close();
bos.close();
socket.close();
}
}
package com.hspedu.upload;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 檔案上傳的服務端
*/
public class TCPFileUploadServer {
public static void main(String[] args) throws Exception {
//1. 服務端在本機監聽8888埠
ServerSocket serverSocket = new ServerSocket(8888);
System.out.println("服務端在8888埠監聽....");
//2. 等待連線
Socket socket = serverSocket.accept();
//3. 讀取客戶端傳送的資料
// 透過Socket得到輸入流
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis); // 已然讀到陣列中了
//4. 將得到 bytes 陣列,寫入到指定的路徑,就得到一個檔案了
String destFilePath = "src\\abc.mp4";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(destFilePath));
bos.write(bytes);
bos.close();
// 向客戶端回覆 "收到圖片"
// 透過socket 獲取到輸出流(字元)
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write("收到圖片");
writer.flush();//把內容重新整理到資料通道
socket.shutdownOutput();//設定寫入結束標記
//關閉其他資源
writer.close();
bis.close();
socket.close();
serverSocket.close();
}
}
package com.hspedu.upload;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 此類用於演示關於流的讀寫方法
*
*/
public class StreamUtils {
/**
* 功能:將輸入流轉換成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.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){ //當讀取到 null時,就表示結束
builder.append(line+"\r\n");
}
return builder.toString();
}
}
netstat 指令
netstat -an
可以檢視當前主機網路情況,包括項口監聽情況和網路連線情況netstat -an | more
可以分頁顯示netstat -anb
(在管理員狀態下執行)可以檢視是哪些應用監聽該埠。- 外部地址就是 連線 該本地地址和埠號的客戶端的IP和埠號。
說明:
(1) Listening表示某個埠在監聽 Established 表示連線已經建立
(2)如果有一個外部程式(客戶端)連線到該埠,就會顯示一條連線資訊
TCP 網路通訊
- 當客戶端連線到服務端後,實際上客戶端也是透過一個埠和服務端進行通訊的,這個埠是TCP/IP來分配的,是不確定的,是隨機的。(客戶端的埠)
- 程式驗證+netstat
UDP 網路通訊程式設計
基本介紹
-
類
DatagramSocket
和DatagramPacket
[資料包/資料包]實現了基於UDP
協議網路程式。 -
UDP資料包透過資料包套接字
DatagramSocket
傳送和接收,系統不保證UDP資料包一定能夠安全送到目的地,也不能確定什麼時候可以抵達。 -
DatagramPacket
物件封裝了UDP資料包,在資料包中包含了傳送端的IP地址和
埠號以及接收端的IP地址和埠號。 -
UDP協議中每個資料包都給出了完整的地址資訊,因此無須建立傳送方和接收方
的連線
基本流程
- 核心的兩個類/物件 DatagramSocket與DatagramPacket
- 建立傳送端,接收端(沒有服務端和客戶端概念)
- 傳送資料前,建立資料包/報 DatagramPacket物件
- 呼叫DatagramSocket的傳送、接收方法
- 關閉DatagramSocket
應用案例
- 編寫一個接收端A,和一個傳送端B
- 接收端 A在 9999埠等待接收資料(receive)
- 傳送端B向接收端A傳送資料“hello,明天吃火鍋~"
- 接收端A接收到傳送端B傳送的資料,回覆"好的,明天見"再退出
- 傳送端接收回復的資料,再退出
package com.hspedu.udp;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
/**
* UDP接收端
*/
public class UDPReceiverA {
public static void main(String[] args) throws IOException {
//1. 建立一個 DatagramSocket 物件,準備在9999接收資料
DatagramSocket socket = new DatagramSocket(9999);
//2. 構建一個 DatagramPacket 物件,準備接收資料
// 在前面講解UDP 協議時,老師說過一個資料包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
// 填充到 packet物件
//老師提示: 當有資料包傳送到 本機的9999埠時,就會接收到資料
// 如果沒有資料包傳送到 本機的9999埠, 就會阻塞等待.
System.out.println("接收端A 等待接收資料..");
socket.receive(packet);
//4. 可以把packet 進行拆包,取出資料,並顯示.
int length = packet.getLength();//實際接收到的資料位元組長度
byte[] data = packet.getData();//接收到資料
String s = new String(data, 0, length);
System.out.println(s);
//===回覆資訊給B端
//將需要傳送的資料,封裝到 DatagramPacket物件
data = "好的, 明天見".getBytes();
//說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);
socket.send(packet);//傳送
//5. 關閉資源
socket.close();
System.out.println("A端退出...");
}
}
package com.hspedu.udp;
import java.io.IOException;
import java.net.*;
/**
* 傳送端B ====> 也可以接收資料
*/
@SuppressWarnings({"all"})
public class UDPSenderB {
public static void main(String[] args) throws IOException {
//1.建立 DatagramSocket 物件,準備在9998埠 接收資料
DatagramSocket socket = new DatagramSocket(9998);
//2. 將需要傳送的資料,封裝到 DatagramPacket物件
byte[] data = "hello 明天吃火鍋~".getBytes(); //
//說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9999);
socket.send(packet);
//3.=== 接收從A端回覆的資訊
//(1) 構建一個 DatagramPacket 物件,準備接收資料
// 在前面講解UDP 協議時,老師說過一個資料包最大 64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//(2) 呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
// 填充到 packet物件
//老師提示: 當有資料包傳送到 本機的9998埠時,就會接收到資料
// 如果沒有資料包傳送到 本機的9998埠, 就會阻塞等待.
socket.receive(packet);
//(3) 可以把packet 進行拆包,取出資料,並顯示.
int length = packet.getLength();//實際接收到的資料位元組長度
data = packet.getData();//接收到資料
String s = new String(data, 0, length);
System.out.println(s);
//關閉資源
socket.close();
System.out.println("B端退出");
}
}
本章作業
1.程式設計題
(1)使用字元流的方式,編寫一個客戶端程式和伺服器端程式,
(2)客戶端傳送“name"伺服器端接收到後,返回“我是nova ", nova是你自己的名字.
(3)客戶端傳送"hobby",伺服器端接收到後,返回“編寫java程式”
(4)不是這兩個問題,回覆“你說啥呢"
問題:目前,我們只能問一次,就退出了,怎麼可以問多次?->while ->聊天
package com.hspedu.homework;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.util.Scanner;
/**
* 客戶端,傳送 "hello, server" 給服務端, 使用字元流
*/
@SuppressWarnings({"all"})
public class Homework01Client {
public static void main(String[] args) throws IOException {
//思路
//1. 連線服務端 (ip , 埠)
//解讀: 連線本機的 9999埠, 如果連線成功,返回Socket物件
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
//2. 連線上後,生成Socket, 透過socket.getOutputStream()
// 得到 和 socket物件關聯的輸出流物件
OutputStream outputStream = socket.getOutputStream();
//3. 透過輸出流,寫入資料到 資料通道, 使用字元流
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
//從鍵盤讀取使用者的問題
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入你的問題");
String question = scanner.next();
bufferedWriter.write(question);
bufferedWriter.newLine();//插入一個換行符,表示寫入的內容結束, 注意,要求對方使用readLine()!!!!
bufferedWriter.flush();// 如果使用的字元流,需要手動重新整理,否則資料不會寫入資料通道
//4. 獲取和socket關聯的輸入流. 讀取資料(字元),並顯示
InputStream inputStream = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
System.out.println(s);
//5. 關閉流物件和socket, 必須關閉
bufferedReader.close();//關閉外層流
bufferedWriter.close();
socket.close();
System.out.println("客戶端退出.....");
}
}
package com.hspedu.homework;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 服務端, 使用字元流方式讀寫
*/
@SuppressWarnings({"all"})
public class Homework01Server {
public static void main(String[] args) throws IOException {
//思路
//1. 在本機 的9999埠監聽, 等待連線
// 細節: 要求在本機沒有其它服務在監聽9999
// 細節:這個 ServerSocket 可以透過 accept() 返回多個Socket[多個客戶端連線伺服器的併發]
ServerSocket serverSocket = new ServerSocket(9999);
System.out.println("服務端,在9999埠監聽,等待連線..");
//2. 當沒有客戶端連線9999埠時,程式會 阻塞, 等待連線
// 如果有客戶端連線,則會返回Socket物件,程式繼續
Socket socket = serverSocket.accept();
//
//3. 透過socket.getInputStream() 讀取客戶端寫入到資料通道的資料, 顯示
InputStream inputStream = socket.getInputStream();
//4. IO讀取, 使用字元流, 老師使用 InputStreamReader 將 inputStream 轉成字元流
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
String s = bufferedReader.readLine();
String answer = "";
if ("name".equals(s)) {
answer = "我是韓順平";
} else if("hobby".equals(s)) {
answer = "編寫java程式";
} else {
answer = "你說的啥子";
}
//5. 獲取socket相關聯的輸出流
OutputStream outputStream = socket.getOutputStream();
// 使用字元輸出流的方式回覆資訊
BufferedWriter bufferedWriter = new BufferedWriter(new OutputStreamWriter(outputStream));
bufferedWriter.write(answer);
bufferedWriter.newLine();// 插入一個換行符,表示回覆內容的結束
bufferedWriter.flush();//注意需要手動的flush
//6.關閉流和socket
bufferedWriter.close();
bufferedReader.close();
socket.close();
serverSocket.close();//關閉
}
}
2.程式設計題
(1)編寫一個接收端A,和一個傳送端B,使用UDP協議完成
(2)接收端在8888埠等待接收資料(receive)
(3)傳送端向接收端傳送資料“四大名著是哪些”
(4)接收端接收到傳送端傳送的問題後,返回“四大名著是<<紅樓夢>>.….",否則返回what?
(5)接收端和傳送端程式退出
package com.hspedu.homework;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;
/**
* 傳送端B ====> 也可以接收資料
*/
@SuppressWarnings({"all"})
public class Homework02SenderB {
public static void main(String[] args) throws IOException {
//1.建立 DatagramSocket 物件,準備在9998埠 接收資料
DatagramSocket socket = new DatagramSocket(9998);
//2. 將需要傳送的資料,封裝到 DatagramPacket物件
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入你的問題: ");
String question = scanner.next();
byte[] data = question.getBytes(); //
//說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
DatagramPacket packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 8888);
socket.send(packet);
//3.=== 接收從A端回覆的資訊
//(1) 構建一個 DatagramPacket 物件,準備接收資料
// 在前面講解UDP 協議時,老師說過一個資料包最大 64k
byte[] buf = new byte[1024];
packet = new DatagramPacket(buf, buf.length);
//(2) 呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
// 填充到 packet物件
//老師提示: 當有資料包傳送到 本機的9998埠時,就會接收到資料
// 如果沒有資料包傳送到 本機的9998埠, 就會阻塞等待.
socket.receive(packet);
//(3) 可以把packet 進行拆包,取出資料,並顯示.
int length = packet.getLength();//實際接收到的資料位元組長度
data = packet.getData();//接收到資料
String s = new String(data, 0, length);
System.out.println(s);
//關閉資源
socket.close();
System.out.println("B端退出");
}
}
package com.hspedu.homework;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
/**
* UDP接收端
*/
@SuppressWarnings({"all"})
public class Homework02ReceiverA {
public static void main(String[] args) throws IOException {
//1. 建立一個 DatagramSocket 物件,準備在8888接收資料
DatagramSocket socket = new DatagramSocket(8888);
//2. 構建一個 DatagramPacket 物件,準備接收資料
// 在前面講解UDP 協議時,老師說過一個資料包最大 64k
byte[] buf = new byte[1024];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
//3. 呼叫 接收方法, 將透過網路傳輸的 DatagramPacket 物件
// 填充到 packet物件
System.out.println("接收端 等待接收問題 ");
socket.receive(packet);
//4. 可以把packet 進行拆包,取出資料,並顯示.
int length = packet.getLength();//實際接收到的資料位元組長度
byte[] data = packet.getData();//接收到資料
String s = new String(data, 0, length); // 將位元組陣列轉為string!!!!
//判斷接收到的資訊是什麼
String answer = "";
if("四大名著是哪些".equals(s)) {
answer = "四大名著 <<紅樓夢>> <<三國演示>> <<西遊記>> <<水滸傳>>";
} else {
answer = "what?";
}
//===回覆資訊給B端
//將需要傳送的資料,封裝到 DatagramPacket物件
data = answer.getBytes();
//說明: 封裝的 DatagramPacket物件 data 內容位元組陣列 , data.length , 主機(IP) , 埠
packet =
new DatagramPacket(data, data.length, InetAddress.getByName("192.168.12.1"), 9998);
socket.send(packet);//傳送
//5. 關閉資源
socket.close();
System.out.println("A端退出...");
}
}
3.程式設計題
(1)編寫客戶端程式和伺服器端程式
(2)客戶端可以輸入一個音樂檔名,比如高山流水, 服務端收到音樂名後,可以給客戶端返回這個音樂檔案,如果伺服器沒有這個檔案,返回一個預設的音樂即可。
(3)客戶端收到檔案後,儲存到本地e:\\
(4)提示:該程式可以使用 StreamUtils.java
package com.hspedu.homework;
import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
/**
* 檔案下載的客戶端
*/
public class Homework03Client {
public static void main(String[] args) throws Exception {
//1. 接收使用者輸入,指定下載檔名
Scanner scanner = new Scanner(System.in);
System.out.println("請輸入下載檔名");
String downloadFileName = scanner.next();
//2. 客戶端連線服務端,準備傳送
Socket socket = new Socket(InetAddress.getLocalHost(), 9999);
//3. 獲取和Socket關聯的輸出流
OutputStream outputStream = socket.getOutputStream();
outputStream.write(downloadFileName.getBytes());
//設定寫入結束的標誌
socket.shutdownOutput();
//4. 讀取服務端返回的檔案(位元組資料)
BufferedInputStream bis = new BufferedInputStream(socket.getInputStream());
byte[] bytes = StreamUtils.streamToByteArray(bis);
//5. 得到一個輸出流,準備將 bytes 寫入到磁碟檔案
//比如你下載的是 高山流水 => 下載的就是 高山流水.mp3
// 你下載的是 無名 => 下載的就是 無名.mp3
String filePath = "e:\\" + downloadFileName + ".mp3";
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(filePath));
bos.write(bytes);
//6. 關閉相關的資源
bos.close();
bis.close();
outputStream.close();
socket.close();
System.out.println("客戶端下載完畢,正確退出..");
}
}
package com.hspedu.homework;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
/**
* 先寫檔案下載的服務端
*/
public class Homework03Server {
public static void main(String[] args) throws Exception {
//1 監聽 9999埠
ServerSocket serverSocket = new ServerSocket(9999);
//2.等待客戶端連線
System.out.println("服務端,在9999埠監聽,等待下載檔案");
Socket socket = serverSocket.accept();
//3.讀取 客戶端傳送要下載的檔名
// 這裡老師使用了while讀取檔名,時考慮將來客戶端傳送的資料較大的情況
InputStream inputStream = socket.getInputStream();
byte[] b = new byte[1024];
int len = 0;
String downLoadFileName = "";
while ((len = inputStream.read(b)) != -1) {
downLoadFileName += new String(b, 0 , len);
}
System.out.println("客戶端希望下載檔名=" + downLoadFileName);
//老師在伺服器上有兩個檔案, 無名.mp3 高山流水.mp3
//如果客戶下載的是 高山流水 我們就返回該檔案,否則一律返回 無名.mp3
String resFileName = "";
if("高山流水".equals(downLoadFileName)) {
resFileName = "src\\高山流水.mp3";
} else {
resFileName = "src\\無名.mp3";
}
//4. 建立一個輸入流,讀取檔案
BufferedInputStream bis =
new BufferedInputStream(new FileInputStream(resFileName));
//5. 使用工具類StreamUtils ,讀取檔案到一個位元組陣列
byte[] bytes = StreamUtils.streamToByteArray(bis);
//6. 得到Socket關聯的輸出流
BufferedOutputStream bos =
new BufferedOutputStream(socket.getOutputStream());
//7. 寫入到資料通道,返回給客戶端
bos.write(bytes);
socket.shutdownOutput();//很關鍵.
//8 關閉相關的資源
bis.close();
inputStream.close();
socket.close();
serverSocket.close();
System.out.println("服務端退出...");
}
}
package com.hspedu.homework;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* 此類用於演示關於流的讀寫方法
*
*/
public class StreamUtils {
/**
* 功能:將輸入流轉換成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);
}
byte[] array = bos.toByteArray();
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();
}
}