注:本文并沒有實現完整的http服務器和http網絡請求,主要是提供思路
先上一個簡單的get請求和響應的代碼,代碼使用idea測試過,使用時先運行服務端,然后再運行客戶端測試,服務端測試也可以通過使用瀏覽器輸入localhost:5050
進行測試
服務端
public class LiteHttpServerTest {
public static void main(String[] args) {
listen(5050);
}
public static void listen(int port) {
new Thread(() -> {
try {
ServerSocket serverSocket = new ServerSocket(port);
while (true) {
Socket accept = serverSocket.accept();
new ServerSocketHandler(accept).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
public static class ServerSocketHandler extends Thread {
private final Socket socket;
public ServerSocketHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
super.run();
try {
InputStream is = socket.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(is));
String line = bufferedReader.readLine();
while (line != null && !"".equals(line)) {
System.out.println(line);
line = bufferedReader.readLine();
}
String body = "hello word";
StringBuilder response = new StringBuilder();
response.append("HTTP/1.1 200 OK\r\n")
.append("Content-Length: ").append(body.getBytes().length).append("\r\n")
.append("Content-Type: text/plain; charset-utf-8\r\n")
.append("\r\n")
.append(body).append("\r\n");
OutputStream outputStream = socket.getOutputStream();
outputStream.write(response.toString().getBytes());
outputStream.flush();
//注意這里并沒有將socket關閉
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客戶端
public class LiteHttpClientTest {
public static void main(String[] args) {
sendGet(5050);
}
public static void sendGet(int port) {
new Thread(() -> {
Socket socket = new Socket();
try {
socket.connect(new InetSocketAddress("localhost", port));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write("GET / HTTP/1.1\r\n");
bw.write("Host: 127.0.0.1\r\n");
bw.write("\r\n");
bw.flush();
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = null;
while ((line = br.readLine()) != null) {//由于服務器端沒有關閉socket,會一直阻塞在這里
System.out.println(line);
}
br.close();
bw.close();
socket.close();
System.out.println("close");//發(fā)現代碼沒有走到這里
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
好了,代碼很簡單,但是這里有兩個問題
- 為什么服務端返回響應后沒有關閉socket連接?
- 客戶端讀取響應一直阻塞住的問題怎么解決(如何知道響應結束)?
1捧灰、為什么服務端返回響應后沒有關閉socket連接?
我們學http協議的時候是不是記得http協議是無連接
碍讯、無狀態(tài)
的,所以總是以為http服務器響應完后就會關閉連接.
無連接:無連接是限制每個連接只有一個請求的意思。在服務器處理完客戶的請求,并收到客戶的反應技健,即斷開。通過這種方式可以節(jié)省傳輸時間变汪。
在HTTP1.0默認確實是這樣的,但是HTTP請求實際上是有Keep-Alive模式的
什么是Keep-Alive模式?
我們知道HTTP協議采用“請求-應答”模式淮逊,當使用普通模式催首,即非Keep-Alive模式時,每個請求/應答客戶和服務器都要新建一個連接泄鹏,完成之后立即斷開連接(HTTP協議為無連接的協議)郎任;當使用Keep-Alive模式(又稱持久連接、連接重用)時备籽,Keep-Alive功能使客戶端到服務器端的連接持續(xù)有效舶治,當出現對服務器的后繼請求時,Keep-Alive功能避免了建立或者重新建立連接。
http 1.0中默認是關閉的霉猛,需要在http頭加入"Connection: Keep-Alive"尺锚,才能啟用Keep-Alive;http 1.1中默認啟用Keep-Alive惜浅,如果加入"Connection: close "瘫辩,才關閉。目前大部分瀏覽器都是用http1.1協議坛悉,也就是說默認都會發(fā)起Keep-Alive的連接請求了,所以我提供的服務端代碼是沒有立馬關閉的.
啟用Keep-Alive模式的有點很明顯,假設一個網頁有100張小圖標,那么用普通模式就要建立100+個連接,而Keep-Alive只要一個,效率提升明顯.
Keep-Alive模式下服務端什么時候關閉socket?
雖然是Keep-Alive模式,但是服務端也不會一直保持長連接,它會有超時機制和請求次數限制.在HTTP首部的Connection: Keep-alive
中伐厌,Keep-Alive: timeout=20
,表示這個TCP通道可以保持20秒吹散。max=XXX
弧械,表示這個長連接最多接收XXX次請求就斷開。如果在客戶端空民,即發(fā)請求的時候刃唐,沒有定義超時時間。服務端也會發(fā)起四次揮手的界轩。TCP還有心跳檢查機制來當前連接是否活著画饥。
2、客戶端讀取響應一直阻塞住的問題怎么解決(如何知道響應結束)?
在解決這個問題前我們先來看看HTTP響應的結構
HTTP響應有4個部分,從上到下分別是
狀態(tài)行
浊猾、響應頭
抖甘、空白行
、響應體
1.狀態(tài)行:描述了響應的狀態(tài)葫慎。
- 響應頭:它們包含了更多關于響應的信息衔彻。比如:頭部可以指定認為響應過期的過期日期,或者是指定用來給用戶安全的傳輸實體內容的編碼格式偷办。
- 空白行:一般文章都沒有提到這個空白行艰额,但是實際上不可缺少,標志著響應頭的結束椒涯。
- 響應體:它包含了響應的內容柄沮。它可以包含HTML代碼、圖片等等废岂。
回到我們的問題祖搓,如何知道響應結束了?這里分為兩種情況
1.Content-Length
我們可以通過響應頭中的Content-Length
來獲取響應體的長度湖苞,當我們讀取到空白行
后讀取Content-Length
個字節(jié)長度就說明讀完了響應了拯欧。
2.分塊編碼
當響應頭里面沒有寶行Content-Length
時怎么辦?這個時候就要采取分塊編碼了财骨,服務器必須返回兩種之一哈扮,當響應頭中包含Transfer-Encoding: chunked
即表示是分塊編碼纬纪。
分塊編碼的報文是這樣的:
每個分塊包含一個長度值(十六進制,字節(jié)數)和該分塊的數據滑肉。 <CR><LF>用于區(qū)隔長度值和數據包各。長度值不包含分塊中的任何 <CR><LF>序列。最后一個分塊靶庙,用長度值0來表示結束问畅。注意報文首部包含一個 Trailer:Content-MD5, 所以在緊跟著最后一個報文結束之后,就是一個拖掛六荒。其他如护姆, Content-Length, Trailer, Transfer-Encoding也可以作為拖掛。
好了掏击,最后推薦一個http服務器和http請求實現的庫AndroidAsync