【網(wǎng)絡編程】
主要內(nèi)容
- 軟件架構CS/BS
- 網(wǎng)絡通信三要素
- TCP通信
- Socket套接字
- ServerSocket
目標
- 能夠辨別UDP和TCP協(xié)議特點
- 能夠說出TCP協(xié)議下兩個常用類名稱
- 能夠編寫TCP協(xié)議下字符串數(shù)據(jù)傳輸程序
- 能夠理解TCP協(xié)議下文件上傳案例
- 能夠理解TCP協(xié)議下案例2
第一章 網(wǎng)絡編程入門
1.1軟件結(jié)構
-
C/S結(jié)構 :全稱為Client/Server結(jié)構,是指客戶端和服務器結(jié)構。常見程序有QQ、迅雷等軟件。
1_cs.jpg
B/S結(jié)構 :全稱為Browser/Server結(jié)構,是指瀏覽器和服務器結(jié)構。常見瀏覽器有谷歌、火狐等。

兩種架構各有優(yōu)勢,但是無論哪種架構,都離不開網(wǎng)絡的支持。網(wǎng)絡編程,就是在一定的協(xié)議下,實現(xiàn)兩臺計算機的通信的程序。
1.2 網(wǎng)絡通信協(xié)議
- 網(wǎng)絡通信協(xié)議:通過計算機網(wǎng)絡可以使多臺計算機實現(xiàn)連接,位于同一個網(wǎng)絡中的計算機在進行連接和通信時需要遵守一定的規(guī)則,這就好比在道路中行駛的汽車一定要遵守交通規(guī)則一樣。在計算機網(wǎng)絡中,這些連接和通信的規(guī)則被稱為網(wǎng)絡通信協(xié)議,它對數(shù)據(jù)的傳輸格式、傳輸速率、傳輸步驟等做了統(tǒng)一規(guī)定,通信雙方必須同時遵守才能完成數(shù)據(jù)交換。
-
TCP/IP協(xié)議: 傳輸控制協(xié)議/因特網(wǎng)互聯(lián)協(xié)議( Transmission Control Protocol/Internet Protocol),是Internet最基本、最廣泛的協(xié)議。它定義了計算機如何連入因特網(wǎng),以及數(shù)據(jù)如何在它們之間傳輸?shù)臉藴?。它的?nèi)部包含一系列的用于處理數(shù)據(jù)通信的協(xié)議,并采用了4層的分層模型,每一層都呼叫它的下一層所提供的協(xié)議來完成自己的需求。
3_tcp_ip.jpg
上圖中,TCP/IP協(xié)議中的四層分別是應用層、傳輸層、網(wǎng)絡層和鏈路層,每層分別負責不同的通信功能。
鏈路層:鏈路層是用于定義物理傳輸通道,通常是對某些網(wǎng)絡連接設備的驅(qū)動協(xié)議,例如針對光纖、網(wǎng)線提供的驅(qū)動。
網(wǎng)絡層:網(wǎng)絡層是整個TCP/IP協(xié)議的核心,它主要用于將傳輸?shù)臄?shù)據(jù)進行分組,將分組數(shù)據(jù)發(fā)送到目標計算機或者網(wǎng)絡。
運輸層:主要使網(wǎng)絡程序進行通信,在進行網(wǎng)絡通信時,可以采用TCP協(xié)議,也可以采用UDP協(xié)議。
應用層:主要負責應用程序的協(xié)議,例如HTTP協(xié)議、FTP協(xié)議等。
1.3 協(xié)議分類
通信的協(xié)議還是比較復雜的,java.net 包中包含的類和接口,它們提供低層次的通信細節(jié)。我們可以直接使用這些類和接口,來專注于網(wǎng)絡程序開發(fā),而不用考慮通信的細節(jié)。
java.net 包中提供了兩種常見的網(wǎng)絡協(xié)議的支持:
-
UDP:用戶數(shù)據(jù)報協(xié)議(User Datagram Protocol)。UDP是無連接通信協(xié)議,即在數(shù)據(jù)傳輸時,數(shù)據(jù)的發(fā)送端和接收端不建立邏輯連接。簡單來說,當一臺計算機向另外一臺計算機發(fā)送數(shù)據(jù)時,發(fā)送端不會確認接收端是否存在,就會發(fā)出數(shù)據(jù),同樣接收端在收到數(shù)據(jù)時,也不會向發(fā)送端反饋是否收到數(shù)據(jù)。
由于使用UDP協(xié)議消耗資源小,通信效率高,所以通常都會用于音頻、視頻和普通數(shù)據(jù)的傳輸例如視頻會議都使用UDP協(xié)議,因為這種情況即使偶爾丟失一兩個數(shù)據(jù)包,也不會對接收結(jié)果產(chǎn)生太大影響。
但是在使用UDP協(xié)議傳送數(shù)據(jù)時,由于UDP的面向無連接性,不能保證數(shù)據(jù)的完整性,因此在傳輸重要數(shù)據(jù)時不建議使用UDP協(xié)議。UDP的交換過程如下圖所示。
[圖片上傳失敗...(image-13157e-1614705530638)]
特點:數(shù)據(jù)被限制在64kb以內(nèi),超出這個范圍就不能發(fā)送了。
數(shù)據(jù)報(Datagram):網(wǎng)絡傳輸?shù)幕締挝?/p>
-
TCP:傳輸控制協(xié)議 (Transmission Control Protocol)。TCP協(xié)議是面向連接的通信協(xié)議,即傳輸數(shù)據(jù)之前,在發(fā)送端和接收端建立邏輯連接,然后再傳輸數(shù)據(jù),它提供了兩臺計算機之間可靠無差錯的數(shù)據(jù)傳輸。
在TCP連接中必須要明確客戶端與服務器端,由客戶端向服務端發(fā)出連接請求,每次連接的創(chuàng)建都需要經(jīng)過“三次握手”。
-
三次握手:TCP協(xié)議中,在發(fā)送數(shù)據(jù)的準備階段,客戶端與服務器之間的三次交互,以保證連接的可靠。
- 第一次握手,客戶端向服務器端發(fā)出連接請求,等待服務器確認。
- 第二次握手,服務器端向客戶端回送一個響應,通知客戶端收到了連接請求。
-
第三次握手,客戶端再次向服務器端發(fā)送確認信息,確認連接。整個交互過程如下圖所示。
4_tcp.jpg
完成三次握手,連接建立后,客戶端和服務器就可以開始進行數(shù)據(jù)傳輸了。由于這種面向連接的特性,TCP協(xié)議可以保證傳輸數(shù)據(jù)的安全,所以應用十分廣泛,例如下載文件、瀏覽網(wǎng)頁等。
-
1.4 網(wǎng)絡編程三要素
協(xié)議
- 協(xié)議:計算機網(wǎng)絡通信必須遵守的規(guī)則,已經(jīng)介紹過了,不再贅述。
IP地址
- IP地址:指互聯(lián)網(wǎng)協(xié)議地址(Internet Protocol Address),俗稱IP。IP地址用來給一個網(wǎng)絡中的計算機設備做唯一的編號。假如我們把“個人電腦”比作“一臺電話”的話,那么“IP地址”就相當于“電話號碼”。
IP地址分類
IPv4:是一個32位的二進制數(shù),通常被分為4個字節(jié),表示成
a.b.c.d的形式,例如192.168.65.100。其中a、b、c、d都是0~255之間的十進制整數(shù),那么最多可以表示42億個。-
IPv6:由于互聯(lián)網(wǎng)的蓬勃發(fā)展,IP地址的需求量愈來愈大,但是網(wǎng)絡地址資源有限,使得IP的分配越發(fā)緊張。
為了擴大地址空間,擬通過IPv6重新定義地址空間,采用128位地址長度,每16個字節(jié)一組,分成8組十六進制數(shù),表示成
ABCD:EF01:2345:6789:ABCD:EF01:2345:6789,號稱可以為全世界的每一粒沙子編上一個網(wǎng)址,這樣就解決了網(wǎng)絡地址資源數(shù)量不夠的問題。
常用命令
- 查看本機IP地址,在控制臺輸入:
ipconfig
- 檢查網(wǎng)絡是否連通,在控制臺輸入:
ping 空格 IP地址
ping 220.181.57.216
特殊的IP地址
- 本機IP地址:
127.0.0.1、localhost。
端口號
網(wǎng)絡的通信,本質(zhì)上是兩個進程(應用程序)的通信。每臺計算機都有很多的進程,那么在網(wǎng)絡通信時,如何區(qū)分這些進程呢?
如果說IP地址可以唯一標識網(wǎng)絡中的設備,那么端口號就可以唯一標識設備中的進程(應用程序)了。
- 端口號:用兩個字節(jié)表示的整數(shù),它的取值范圍是0~65535。其中,0~1023之間的端口號用于一些知名的網(wǎng)絡服務和應用,普通的應用程序需要使用1024以上的端口號。如果端口號被另外一個服務或應用所占用,會導致當前程序啟動失敗。
利用協(xié)議+IP地址+端口號 三元組合,就可以標識網(wǎng)絡中的進程了,那么進程間的通信就可以利用這個標識與其它進程進行交互。
第二章 TCP通信程序
2.1 概述
TCP通信能實現(xiàn)兩臺計算機之間的數(shù)據(jù)交互,通信的兩端,要嚴格區(qū)分為客戶端(Client)與服務端(Server)。
兩端通信時步驟:
- 服務端程序,需要事先啟動,等待客戶端的連接。
- 客戶端主動連接服務器端,連接成功才能通信。服務端不可以主動連接客戶端。
在Java中,提供了兩個類用于實現(xiàn)TCP通信程序:
- 客戶端:
java.net.Socket類表示。創(chuàng)建Socket對象,向服務端發(fā)出連接請求,服務端響應請求,兩者建立連接開始通信。 - 服務端:
java.net.ServerSocket類表示。創(chuàng)建ServerSocket對象,相當于開啟一個服務,并等待客戶端的連接。
2.2 Socket類
Socket 類:該類實現(xiàn)客戶端套接字,套接字指的是兩臺設備之間通訊的端點。
構造方法
-
public Socket(String host, int port):創(chuàng)建套接字對象并將其連接到指定主機上的指定端口號。如果指定的host是null ,則相當于指定地址為回送地址。小貼士:回送地址(127.x.x.x) 是本機回送地址(Loopback Address),主要用于網(wǎng)絡軟件測試以及本地機進程間通信,無論什么程序,一旦使用回送地址發(fā)送數(shù)據(jù),立即返回,不進行任何網(wǎng)絡傳輸。
構造舉例,代碼如下:
Socket client = new Socket("127.0.0.1", 6666);
成員方法
-
public InputStream getInputStream(): 返回此套接字的輸入流。- 如果此Scoket具有相關聯(lián)的通道,則生成的InputStream 的所有操作也關聯(lián)該通道。
- 關閉生成的InputStream也將關閉相關的Socket。
-
public OutputStream getOutputStream(): 返回此套接字的輸出流。- 如果此Scoket具有相關聯(lián)的通道,則生成的OutputStream 的所有操作也關聯(lián)該通道。
- 關閉生成的OutputStream也將關閉相關的Socket。
-
public void close():關閉此套接字。- 一旦一個socket被關閉,它不可再使用。
- 關閉此socket也將關閉相關的InputStream和OutputStream 。
-
public void shutdownOutput(): 禁用此套接字的輸出流。- 任何先前寫出的數(shù)據(jù)將被發(fā)送,隨后終止輸出流。
2.3 ServerSocket類
ServerSocket類:這個類實現(xiàn)了服務器套接字,該對象等待通過網(wǎng)絡的請求。
構造方法
-
public ServerSocket(int port):使用該構造方法在創(chuàng)建ServerSocket對象時,就可以將其綁定到一個指定的端口號上,參數(shù)port就是端口號。
構造舉例,代碼如下:
ServerSocket server = new ServerSocket(6666);
成員方法
-
public Socket accept():偵聽并接受連接,返回一個新的Socket對象,用于和客戶端實現(xiàn)通信。該方法會一直阻塞直到建立連接。
2.4 簡單的TCP網(wǎng)絡程序
TCP通信分析圖解
- 【服務端】啟動,創(chuàng)建ServerSocket對象,等待連接。
- 【客戶端】啟動,創(chuàng)建Socket對象,請求連接。
- 【服務端】接收連接,調(diào)用accept方法,并返回一個Socket對象。
- 【客戶端】Socket對象,獲取OutputStream,向服務端寫出數(shù)據(jù)。
- 【服務端】Scoket對象,獲取InputStream,讀取客戶端發(fā)送的數(shù)據(jù)。
到此,客戶端向服務端發(fā)送數(shù)據(jù)成功。
5_簡單通信.jpg
自此,服務端向客戶端回寫數(shù)據(jù)。
- 【服務端】Socket對象,獲取OutputStream,向客戶端回寫數(shù)據(jù)。
- 【客戶端】Scoket對象,獲取InputStream,解析回寫數(shù)據(jù)。
- 【客戶端】釋放資源,斷開連接。
客戶端向服務器發(fā)送數(shù)據(jù)
服務端實現(xiàn):
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服務端啟動 , 等待連接 .... ");
// 1.創(chuàng)建 ServerSocket對象,綁定端口,開始等待連接
ServerSocket ss = new ServerSocket(6666);
// 2.接收連接 accept 方法, 返回 socket 對象.
Socket server = ss.accept();
// 3.通過socket 獲取輸入流
InputStream is = server.getInputStream();
// 4.一次性讀取數(shù)據(jù)
// 4.1 創(chuàng)建字節(jié)數(shù)組
byte[] b = new byte[1024];
// 4.2 據(jù)讀取到字節(jié)數(shù)組中.
int len = is.read(b);
// 4.3 解析數(shù)組,打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
//5.關閉資源.
is.close();
server.close();
}
}
客戶端實現(xiàn):
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客戶端 發(fā)送數(shù)據(jù)");
// 1.創(chuàng)建 Socket ( ip , port ) , 確定連接到哪里.
Socket client = new Socket("localhost", 6666);
// 2.獲取流對象 . 輸出流
OutputStream os = client.getOutputStream();
// 3.寫出數(shù)據(jù).
os.write("你好么? tcp ,我來了".getBytes());
// 4. 關閉資源 .
os.close();
client.close();
}
}
服務器向客戶端回寫數(shù)據(jù)
服務端實現(xiàn):
public class ServerTCP {
public static void main(String[] args) throws IOException {
System.out.println("服務端啟動 , 等待連接 .... ");
// 1.創(chuàng)建 ServerSocket對象,綁定端口,開始等待連接
ServerSocket ss = new ServerSocket(6666);
// 2.接收連接 accept 方法, 返回 socket 對象.
Socket server = ss.accept();
// 3.通過socket 獲取輸入流
InputStream is = server.getInputStream();
// 4.一次性讀取數(shù)據(jù)
// 4.1 創(chuàng)建字節(jié)數(shù)組
byte[] b = new byte[1024];
// 4.2 據(jù)讀取到字節(jié)數(shù)組中.
int len = is.read(b);
// 4.3 解析數(shù)組,打印字符串信息
String msg = new String(b, 0, len);
System.out.println(msg);
// =================回寫數(shù)據(jù)=======================
// 5. 通過 socket 獲取輸出流
OutputStream out = server.getOutputStream();
// 6. 回寫數(shù)據(jù)
out.write("我很好,謝謝你".getBytes());
// 7.關閉資源.
out.close();
is.close();
server.close();
}
}
客戶端實現(xiàn):
public class ClientTCP {
public static void main(String[] args) throws Exception {
System.out.println("客戶端 發(fā)送數(shù)據(jù)");
// 1.創(chuàng)建 Socket ( ip , port ) , 確定連接到哪里.
Socket client = new Socket("localhost", 6666);
// 2.通過Scoket,獲取輸出流對象
OutputStream os = client.getOutputStream();
// 3.寫出數(shù)據(jù).
os.write("你好么? tcp ,我來了".getBytes());
// ==============解析回寫=========================
// 4. 通過Scoket,獲取 輸入流對象
InputStream in = client.getInputStream();
// 5. 讀取數(shù)據(jù)數(shù)據(jù)
byte[] b = new byte[100];
int len = in.read(b);
System.out.println(new String(b, 0, len));
// 6. 關閉資源 .
in.close();
os.close();
client.close();
}
}
第三章 綜合案例
3.1 文件上傳案例
文件上傳分析圖解
- 【客戶端】輸入流,從硬盤讀取文件數(shù)據(jù)到程序中。
- 【客戶端】輸出流,寫出文件數(shù)據(jù)到服務端。
- 【服務端】輸入流,讀取文件數(shù)據(jù)到服務端程序。
-
【服務端】輸出流,寫出文件數(shù)據(jù)到服務器硬盤中。
6_upload.jpg
基本實現(xiàn)
服務端實現(xiàn):
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服務器 啟動..... ");
// 1. 創(chuàng)建服務端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 建立連接
Socket accept = serverSocket.accept();
// 3. 創(chuàng)建流對象
// 3.1 獲取輸入流,讀取文件數(shù)據(jù)
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
// 3.2 創(chuàng)建輸出流,保存到本地 .
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy.jpg"));
// 4. 讀寫數(shù)據(jù)
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
//5. 關閉 資源
bos.close();
bis.close();
accept.close();
System.out.println("文件上傳已保存");
}
}
客戶端實現(xiàn):
public class FileUPload_Client {
public static void main(String[] args) throws IOException {
// 1.創(chuàng)建流對象
// 1.1 創(chuàng)建輸入流,讀取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 創(chuàng)建輸出流,寫到服務端
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.寫出數(shù)據(jù).
byte[] b = new byte[1024 * 8 ];
int len ;
while (( len = bis.read(b))!=-1) {
bos.write(b, 0, len);
bos.flush();
}
System.out.println("文件發(fā)送完畢");
// 3.釋放資源
bos.close();
socket.close();
bis.close();
System.out.println("文件上傳完畢 ");
}
}
文件上傳優(yōu)化分析
-
文件名稱寫死的問題
服務端,保存文件的名稱如果寫死,那么最終導致服務器硬盤,只會保留一個文件,建議使用系統(tǒng)時間優(yōu)化,保證文件名稱唯一,代碼如下:
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis()+".jpg") // 文件名稱
BufferedOutputStream bos = new BufferedOutputStream(fis);
-
循環(huán)接收的問題
服務端,指保存一個文件就關閉了,之后的用戶無法再上傳,這是不符合實際的,使用循環(huán)改進,可以不斷的接收不同用戶的文件,代碼如下:
// 每次接收新的連接,創(chuàng)建一個Socket
while(true){
Socket accept = serverSocket.accept();
......
}
-
效率問題
服務端,在接收大文件時,可能耗費幾秒鐘的時間,此時不能接收其他用戶上傳,所以,使用多線程技術優(yōu)化,代碼如下:
while(true){
Socket accept = serverSocket.accept();
// accept 交給子線程處理.
new Thread(() -> {
......
InputStream bis = accept.getInputStream();
......
}).start();
}
優(yōu)化實現(xiàn)
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服務器 啟動..... ");
// 1. 創(chuàng)建服務端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循環(huán)接收,建立連接
while (true) {
Socket accept = serverSocket.accept();
/*
3. socket對象交給子線程處理,進行讀寫操作
Runnable接口中,只有一個run方法,使用lambda表達式簡化格式
*/
new Thread(() -> {
try (
//3.1 獲取輸入流對象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 創(chuàng)建輸出流對象, 保存到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);) {
// 3.3 讀寫數(shù)據(jù)
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
//4. 關閉 資源
bos.close();
bis.close();
accept.close();
System.out.println("文件上傳已保存");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
信息回寫分析圖解
前四步與基本文件上傳一致.
- 【服務端】獲取輸出流,回寫數(shù)據(jù)。
-
【客戶端】獲取輸入流,解析回寫數(shù)據(jù)。
6_upload2.jpg
回寫實現(xiàn)
public class FileUpload_Server {
public static void main(String[] args) throws IOException {
System.out.println("服務器 啟動..... ");
// 1. 創(chuàng)建服務端ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
// 2. 循環(huán)接收,建立連接
while (true) {
Socket accept = serverSocket.accept();
/*
3. socket對象交給子線程處理,進行讀寫操作
Runnable接口中,只有一個run方法,使用lambda表達式簡化格式
*/
new Thread(() -> {
try (
//3.1 獲取輸入流對象
BufferedInputStream bis = new BufferedInputStream(accept.getInputStream());
//3.2 創(chuàng)建輸出流對象, 保存到本地 .
FileOutputStream fis = new FileOutputStream(System.currentTimeMillis() + ".jpg");
BufferedOutputStream bos = new BufferedOutputStream(fis);
) {
// 3.3 讀寫數(shù)據(jù)
byte[] b = new byte[1024 * 8];
int len;
while ((len = bis.read(b)) != -1) {
bos.write(b, 0, len);
}
// 4.=======信息回寫===========================
System.out.println("back ........");
OutputStream out = accept.getOutputStream();
out.write("上傳成功".getBytes());
out.close();
//================================
//5. 關閉 資源
bos.close();
bis.close();
accept.close();
System.out.println("文件上傳已保存");
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
}
客戶端實現(xiàn):
public class FileUpload_Client {
public static void main(String[] args) throws IOException {
// 1.創(chuàng)建流對象
// 1.1 創(chuàng)建輸入流,讀取本地文件
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("test.jpg"));
// 1.2 創(chuàng)建輸出流,寫到服務端
Socket socket = new Socket("localhost", 6666);
BufferedOutputStream bos = new BufferedOutputStream(socket.getOutputStream());
//2.寫出數(shù)據(jù).
byte[] b = new byte[1024 * 8 ];
int len ;
while (( len = bis.read(b))!=-1) {
bos.write(b, 0, len);
}
// 關閉輸出流,通知服務端,寫出數(shù)據(jù)完畢
socket.shutdownOutput();
System.out.println("文件發(fā)送完畢");
// 3. =====解析回寫============
InputStream in = socket.getInputStream();
byte[] back = new byte[20];
in.read(back);
System.out.println(new String(back));
in.close();
// ============================
// 4.釋放資源
socket.close();
bis.close();
}
}
3.2 模擬B\S服務器(擴展知識點)
模擬網(wǎng)站服務器,使用瀏覽器訪問自己編寫的服務端程序,查看網(wǎng)頁效果。
案例分析
-
準備頁面數(shù)據(jù),web文件夾。
復制到我們Module中,比如復制到day08中
復制.png
-
我們模擬服務器端,ServerSocket類監(jiān)聽端口,使用瀏覽器訪問
public static void main(String[] args) throws IOException { ServerSocket server = new ServerSocket(8000); Socket socket = server.accept(); InputStream in = socket.getInputStream(); byte[] bytes = new byte[1024]; int len = in.read(bytes); System.out.println(new String(bytes,0,len)); socket.close(); server.close(); }

-
服務器程序中字節(jié)輸入流可以讀取到瀏覽器發(fā)來的請求信息
讀取訪問信息.jpg
GET/web/index.html HTTP/1.1是瀏覽器的請求消息。/web/index.html為瀏覽器想要請求的服務器端的資源,使用字符串切割方式獲取到請求的資源。
//轉(zhuǎn)換流,讀取瀏覽器請求第一行
BufferedReader readWb = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出請求資源的路徑
String[] strArr = requst.split(" ");
//去掉web前面的/
String path = strArr[1].substring(1);
System.out.println(path);
案例實現(xiàn)
服務端實現(xiàn):
public class SerDemo {
public static void main(String[] args) throws IOException {
System.out.println("服務端 啟動 , 等待連接 .... ");
// 創(chuàng)建ServerSocket 對象
ServerSocket server = new ServerSocket(8888);
Socket socket = server.accept();
// 轉(zhuǎn)換流讀取瀏覽器的請求消息
BufferedReader readWb = new
BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
// 取出請求資源的路徑
String[] strArr = requst.split(" ");
// 去掉web前面的/
String path = strArr[1].substring(1);
// 讀取客戶端請求的資源文件
FileInputStream fis = new FileInputStream(path);
byte[] bytes= new byte[1024];
int len = 0 ;
// 字節(jié)輸出流,將文件寫會客戶端
OutputStream out = socket.getOutputStream();
// 寫入HTTP協(xié)議響應頭,固定寫法
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:text/html\r\n".getBytes());
// 必須要寫入空行,否則瀏覽器不解析
out.write("\r\n".getBytes());
while((len = fis.read(bytes))!=-1){
out.write(bytes,0,len);
}
fis.close();
out.close();
readWb.close();
socket.close();
server.close();
}
}
訪問效果
-
火狐
效果圖1.png
小貼士:不同的瀏覽器,內(nèi)核不一樣,解析效果有可能不一樣。
發(fā)現(xiàn)瀏覽器中出現(xiàn)很多的叉子,說明瀏覽器沒有讀取到圖片信息導致。
瀏覽器工作原理是遇到圖片會開啟一個線程進行單獨的訪問,因此在服務器端加入線程技術。
public class ServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(8888);
while(true){
Socket socket = server.accept();
new Thread(new Web(socket)).start();
}
}
static class Web implements Runnable{
private Socket socket;
public Web(Socket socket){
this.socket=socket;
}
public void run() {
try{
//轉(zhuǎn)換流,讀取瀏覽器請求第一行
BufferedReader readWb = new
BufferedReader(new InputStreamReader(socket.getInputStream()));
String requst = readWb.readLine();
//取出請求資源的路徑
String[] strArr = requst.split(" ");
System.out.println(Arrays.toString(strArr));
String path = strArr[1].substring(1);
System.out.println(path);
FileInputStream fis = new FileInputStream(path);
System.out.println(fis);
byte[] bytes= new byte[1024];
int len = 0 ;
//向瀏覽器 回寫數(shù)據(jù)
OutputStream out = socket.getOutputStream();
out.write("HTTP/1.1 200 OK\r\n".getBytes());
out.write("Content-Type:text/html\r\n".getBytes());
out.write("\r\n".getBytes());
while((len = fis.read(bytes))!=-1){
out.write(bytes,0,len);
}
fis.close();
out.close();
readWb.close();
socket.close();
}catch(Exception ex){
}
}
}
}








