使用socket實現http服務器和發(fā)送http請求

注:本文并沒有實現完整的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();
    }
}

好了,代碼很簡單,但是這里有兩個問題

  1. 為什么服務端返回響應后沒有關閉socket連接?
  2. 客戶端讀取響應一直阻塞住的問題怎么解決(如何知道響應結束)?

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功能避免了建立或者重新建立連接。

normal和keep-alive對比

http 1.0中默認是關閉的霉猛,需要在http頭加入"Connection: Keep-Alive"尺锚,才能啟用Keep-Alive;http 1.1中默認啟用Keep-Alive惜浅,如果加入"Connection: close "瘫辩,才關閉。目前大部分瀏覽器都是用http1.1協議坛悉,也就是說默認都會發(fā)起Keep-Alive的連接請求了,所以我提供的服務端代碼是沒有立馬關閉的.

P:引用自HTTP長連接 && 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)葫慎。

  1. 響應頭:它們包含了更多關于響應的信息衔彻。比如:頭部可以指定認為響應過期的過期日期,或者是指定用來給用戶安全的傳輸實體內容的編碼格式偷办。
  2. 空白行:一般文章都沒有提到這個空白行艰额,但是實際上不可缺少,標志著響應頭的結束椒涯。
  3. 響應體:它包含了響應的內容柄沮。它可以包含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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末卵皂,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子砚亭,更是在濱河造成了極大的恐慌灯变,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,198評論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件捅膘,死亡現場離奇詭異添祸,居然都是意外死亡,警方通過查閱死者的電腦和手機寻仗,發(fā)現死者居然都...
    沈念sama閱讀 94,334評論 3 398
  • 文/潘曉璐 我一進店門刃泌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人署尤,你說我怎么就攤上這事耙替。” “怎么了曹体?”我有些...
    開封第一講書人閱讀 167,643評論 0 360
  • 文/不壞的土叔 我叫張陵林艘,是天一觀的道長。 經常有香客問我混坞,道長,這世上最難降的妖魔是什么钢坦? 我笑而不...
    開封第一講書人閱讀 59,495評論 1 296
  • 正文 為了忘掉前任究孕,我火速辦了婚禮,結果婚禮上爹凹,老公的妹妹穿的比我還像新娘厨诸。我一直安慰自己,他們只是感情好禾酱,可當我...
    茶點故事閱讀 68,502評論 6 397
  • 文/花漫 我一把揭開白布微酬。 她就那樣靜靜地躺著绘趋,像睡著了一般。 火紅的嫁衣襯著肌膚如雪颗管。 梳的紋絲不亂的頭發(fā)上陷遮,一...
    開封第一講書人閱讀 52,156評論 1 308
  • 那天,我揣著相機與錄音垦江,去河邊找鬼帽馋。 笑死,一個胖子當著我的面吹牛比吭,可吹牛的內容都是我干的绽族。 我是一名探鬼主播,決...
    沈念sama閱讀 40,743評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼衩藤,長吁一口氣:“原來是場噩夢啊……” “哼吧慢!你這毒婦竟也來了?” 一聲冷哼從身側響起赏表,我...
    開封第一講書人閱讀 39,659評論 0 276
  • 序言:老撾萬榮一對情侶失蹤检诗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后底哗,有當地人在樹林里發(fā)現了一具尸體岁诉,經...
    沈念sama閱讀 46,200評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 38,282評論 3 340
  • 正文 我和宋清朗相戀三年跋选,在試婚紗的時候發(fā)現自己被綠了涕癣。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,424評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡前标,死狀恐怖坠韩,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情炼列,我是刑警寧澤只搁,帶...
    沈念sama閱讀 36,107評論 5 349
  • 正文 年R本政府宣布,位于F島的核電站俭尖,受9級特大地震影響氢惋,放射性物質發(fā)生泄漏。R本人自食惡果不足惜稽犁,卻給世界環(huán)境...
    茶點故事閱讀 41,789評論 3 333
  • 文/蒙蒙 一焰望、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧已亥,春花似錦熊赖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,264評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俱笛。三九已至,卻和暖如春传趾,著一層夾襖步出監(jiān)牢的瞬間迎膜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,390評論 1 271
  • 我被黑心中介騙來泰國打工墨缘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留星虹,地道東北人。 一個月前我還...
    沈念sama閱讀 48,798評論 3 376
  • 正文 我出身青樓镊讼,卻偏偏與公主長得像宽涌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子蝶棋,可洞房花燭夜當晚...
    茶點故事閱讀 45,435評論 2 359

推薦閱讀更多精彩內容

  • 本文依照 知識共享許可協議(署名-非商業(yè)性使用-禁止演繹) 發(fā)布卸亮。 編寫HTTP 服務器與客戶端 Vert.x讓編...
    半枚荔枝閱讀 3,960評論 0 5
  • 本文整理自MIN飛翔博客 [1] 1. 概念 協議是指計算機通信網絡中兩臺計算機之間進行通信所必須共同遵守的規(guī)定或...
    HoyaWhite閱讀 2,677評論 2 20
  • 工作流程 一次HTTP操作稱為一個事務,其工作過程可分為四步: 1)首先客戶機與服務器需要建立連接玩裙。只要單擊某個超...
    保川閱讀 4,615評論 2 14
  • 鄭睿翔 著 閩清教師進修附屬小學 五年3班 2018年...
    巖山白玉泉閱讀 1,374評論 0 2
  • 作為一名設計師兼贸,最害怕的就是缺乏素材,有了好的素材再加上設計師的優(yōu)化設計吃溅,不僅作品看起來更加生動形象溶诞,吸引眼球,...
    設計描閱讀 15,039評論 0 81