<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          手寫一個(gè)web服務(wù)器 !

          共 7087字,需瀏覽 15分鐘

           ·

          2020-12-11 11:44

          前言

          作為后端開發(fā)人員,在實(shí)際的工作中我們會(huì)非常高頻地使用到web服務(wù)器。而tomcat作為web服務(wù)器領(lǐng)域中舉足輕重的一個(gè)web框架,又是不能不學(xué)習(xí)和了解的。

          tomcat其實(shí)是一個(gè)web框架,那么其內(nèi)部是怎么實(shí)現(xiàn)的呢?如果不用tomcat我們能自己實(shí)現(xiàn)一個(gè)web服務(wù)器嗎?

          首先,tomcat內(nèi)部的實(shí)現(xiàn)是非常復(fù)雜的,也有非常多的各類組件,我們?cè)诤罄m(xù)章節(jié)會(huì)深入地了解。其次,本章我們將自己實(shí)現(xiàn)一個(gè)web服務(wù)器的。

          http協(xié)議簡(jiǎn)介

          http是一種協(xié)議(超文本傳輸協(xié)議),允許web服務(wù)器和瀏覽器通過Internet來發(fā)送和接受數(shù)據(jù),是一種請(qǐng)求/響應(yīng)協(xié)議。http底層使用TCP來進(jìn)行通信。目前,http已經(jīng)迭代到了2.x版本,從最初的0.9、1.0、1.1到現(xiàn)在的2.x,每個(gè)迭代都加了很多功能。

          在http中,始終都是客戶端發(fā)起一個(gè)請(qǐng)求,服務(wù)器接受到請(qǐng)求之后,然后處理邏輯,處理完成之后再發(fā)送響應(yīng)數(shù)據(jù),客戶端收到響應(yīng)數(shù)據(jù),然后請(qǐng)求結(jié)束。在這個(gè)過程中,客戶端和服務(wù)器都可以對(duì)建立的連接進(jìn)行中斷操作。比如可以通過瀏覽器的停止按鈕。

          http協(xié)議-請(qǐng)求

          一個(gè)http協(xié)議的請(qǐng)求包含三部分:

          1. 方法 URI 協(xié)議/版本
          2. 請(qǐng)求的頭部
          3. 主體內(nèi)容

          舉個(gè)例子:

          POST?/examples/default.jsp?HTTP/1.1
          Accept:?text/plain;?text/html
          Accept-Language:?en-gb
          Connection:?Keep-Alive
          Host:?localhost
          User-Agent:?Mozilla/4.0?(compatible;?MSIE?4.01;?Windows?98)
          Content-Length:?33
          Content-Type:?application/x-www-form-urlencoded
          Accept-Encoding:?gzip,?deflate

          lastName=Franks&firstName=Michael

          數(shù)據(jù)的第一行包括:方法、URI、協(xié)議和版本。在這個(gè)例子里,方法為POST,URI為/examples/default.jsp,協(xié)議為HTTP/1.1,協(xié)議版本號(hào)為1.1。他們之間通過空格來分離。

          請(qǐng)求頭部從第二行開始,使用英文冒號(hào)(:)來分離鍵和值。

          請(qǐng)求頭部和主體內(nèi)容之間通過空行來分離,例子中的請(qǐng)求體為表單數(shù)據(jù)。

          http協(xié)議-響應(yīng)

          類似于http協(xié)議的請(qǐng)求,響應(yīng)也包含三個(gè)部分。

          1. 協(xié)議 狀態(tài) 狀態(tài)描述
          2. 響應(yīng)的頭部
          3. 主體內(nèi)容

          舉個(gè)例子:

          HTTP/1.1?200?OK
          Server:?Microsoft-IIS/4.0
          Date:?Mon,?5?Jan?2004?13:13:33?GMT
          Content-Type:?text/html
          Last-Modified:?Mon,?5?Jan?2004?13:13:12?GMT
          Content-Length:?112



          HTTP?Response?Example?

          Welcome?to?Brainy?Software


          第一行,HTTP/1.1 200 OK表示協(xié)議、狀態(tài)和狀態(tài)描述。

          之后表示響應(yīng)頭部。

          響應(yīng)頭部和主體內(nèi)容之間使用空行來分離。

          Socket

          Socket,又叫套接字,是網(wǎng)絡(luò)連接的一個(gè)端點(diǎn)(end point)。套接字允許應(yīng)用程序從網(wǎng)絡(luò)中讀取和寫入數(shù)據(jù)。兩個(gè)不同計(jì)算機(jī)的不同進(jìn)程之間可以通過連接來發(fā)送和接受數(shù)據(jù)。A應(yīng)用要向B應(yīng)用發(fā)送數(shù)據(jù),A應(yīng)用需要知道B應(yīng)用所在的IP地址和B應(yīng)用開放的套接字端口。java里面使用java.net.Socket來表示一個(gè)套接字。

          java.net.Socket最常用的一個(gè)構(gòu)造方法為:public Socket(String host, int port);,host表示主機(jī)名或ip地址,port表示套接字端口。我們來看一個(gè)例子:

          Socket?socket?=?new?Socket("127.0.0.1",?"8080");
          OutputStream?os?=?socket.getOutputStream();?
          boolean?autoflush?=?true;
          PrintWriter?out?=?new?PrintWriter(?socket.getOutputStream(),?autoflush);
          BufferedReader?in?=?new?BufferedReader(new?InputStreamReader(socket.getInputstream()));

          //?send?an?HTTP?request?to?the?web?server?
          out.println("GET?/index.jsp?HTTP/1.1");?
          out.println("Host:?localhost:8080");?
          out.println("Connection:?Close");
          out.println();

          //?read?the?response
          boolean?loop?=?true;
          StringBuffer?sb?=?new?StringBuffer(8096);?
          while?(loop)?{
          ????if?(in.ready())?{?
          ????????int?i=0;
          ????????while?(i?!=?-1)?{
          ????????????i?=?in.read();
          ????????????sb.append((char)?i);?
          ????????}
          ????????loop?=?false;
          ????}
          ????Thread.currentThread().sleep(50L);
          }

          這兒通過socket.getOutputStream()來發(fā)送數(shù)據(jù),使用socket.getInputstream()來讀取數(shù)據(jù)。

          ServerSocket

          Socket表示一個(gè)客戶端套接字,任何時(shí)候如果你想發(fā)送或接受數(shù)據(jù),都需要構(gòu)造創(chuàng)建一個(gè)Socket。現(xiàn)在假如我們需要一個(gè)服務(wù)器端的應(yīng)用程序,我們需要額外考慮更多的東西。因?yàn)榉?wù)器需要隨時(shí)待命,它不清楚什么時(shí)候一個(gè)客戶端會(huì)連接到它。在java里面,我們可以通過java.net.ServerSocket來表示一個(gè)服務(wù)器套接字。

          ServerSocket和Socket不同,它需要等待來自客戶端的連接。一旦有客戶端和其建立了連接,ServerSocket需要?jiǎng)?chuàng)建一個(gè)Socket來和客戶端進(jìn)行通信。

          ServerSocket有很多的構(gòu)造方法,我們拿其中的一個(gè)來舉例子。

          public?ServerSocket(int?port,?int?backlog,?InetAddress?bindAddr)?throws?IOException;
          new?ServerSocket(8080,?1,?InetAddress.getByName("127.0.0.1"));

          參數(shù)如下:

          1. port 表示端口
          2. backlog 表示隊(duì)列的長(zhǎng)度
          3. bindAddr 表示地址

          HttpServer

          HttpServer表示一個(gè)服務(wù)器端入口,提供了一個(gè)main方法,并一直在8080端口等待,直到客戶端建立一個(gè)連接。這時(shí),服務(wù)器通過生成一個(gè)Socket來對(duì)此連接進(jìn)行處理。

          public?class?HttpServer?{

          ??/**?WEB_ROOT?is?the?directory?where?our?HTML?and?other?files?reside.
          ???*??For?this?package,?WEB_ROOT?is?the?"webroot"?directory?under?the?working
          ???*??directory.
          ???*??The?working?directory?is?the?location?in?the?file?system
          ???*??from?where?the?java?command?was?invoked.
          ???*/

          ??public?static?final?String?WEB_ROOT?=
          ????System.getProperty("user.dir")?+?File.separator??+?"webroot";

          ??//?shutdown?command
          ??private?static?final?String?SHUTDOWN_COMMAND?=?"/SHUTDOWN";

          ??//?the?shutdown?command?received
          ??private?boolean?shutdown?=?false;

          ??public?static?void?main(String[]?args)?{
          ????HttpServer?server?=?new?HttpServer();
          ????server.await();
          ??}

          ??public?void?await()?{
          ????ServerSocket?serverSocket?=?null;
          ????int?port?=?8080;
          ????try?{
          ??????serverSocket?=??new?ServerSocket(port,?1,?InetAddress.getByName("127.0.0.1"));
          ????}
          ????catch?(IOException?e)?{
          ??????e.printStackTrace();
          ??????System.exit(1);
          ????}

          ????//?Loop?waiting?for?a?request
          ????while?(!shutdown)?{
          ??????Socket?socket?=?null;
          ??????InputStream?input?=?null;
          ??????OutputStream?output?=?null;
          ??????try?{
          ????????socket?=?serverSocket.accept();
          ????????input?=?socket.getInputStream();
          ????????output?=?socket.getOutputStream();

          ????????//?create?Request?object?and?parse
          ????????Request?request?=?new?Request(input);
          ????????request.parse();

          ????????//?create?Response?object
          ????????Response?response?=?new?Response(output);
          ????????response.setRequest(request);
          ????????response.sendStaticResource();

          ????????//?Close?the?socket
          ????????socket.close();

          ????????//check?if?the?previous?URI?is?a?shutdown?command
          ????????shutdown?=?request.getUri().equals(SHUTDOWN_COMMAND);
          ??????}
          ??????catch?(Exception?e)?{
          ????????e.printStackTrace();
          ????????continue;
          ??????}
          ????}
          ??}
          }

          Request對(duì)象主要完成幾件事情:

          1. 解析請(qǐng)求數(shù)據(jù)
          2. 解析uri(請(qǐng)求數(shù)據(jù)第一行)
          public?class?Request?{

          ??private?InputStream?input;
          ??private?String?uri;

          ??public?Request(InputStream?input)?{
          ????this.input?=?input;
          ??}

          ??public?void?parse()?{
          ????//?Read?a?set?of?characters?from?the?socket
          ????StringBuffer?request?=?new?StringBuffer(2048);
          ????int?i;
          ????byte[]?buffer?=?new?byte[2048];
          ????try?{
          ??????i?=?input.read(buffer);
          ????}
          ????catch?(IOException?e)?{
          ??????e.printStackTrace();
          ??????i?=?-1;
          ????}
          ????for?(int?j=0;?j??????request.append((char)?buffer[j]);
          ????}
          ????System.out.print(request.toString());
          ????uri?=?parseUri(request.toString());
          ??}

          ??private?String?parseUri(String?requestString)?{
          ????int?index1,?index2;
          ????index1?=?requestString.indexOf('?');
          ????if?(index1?!=?-1)?{
          ??????index2?=?requestString.indexOf('?',?index1?+?1);
          ??????if?(index2?>?index1)
          ????????return?requestString.substring(index1?+?1,?index2);
          ????}
          ????return?null;
          ??}

          ??public?String?getUri()?{
          ????return?uri;
          ??}

          }

          Response主要是向客戶端發(fā)送文件內(nèi)容(如果請(qǐng)求的uri指向的文件存在)。

          public?class?Response?{

          ??private?static?final?int?BUFFER_SIZE?=?1024;
          ??Request?request;
          ??OutputStream?output;

          ??public?Response(OutputStream?output)?{
          ????this.output?=?output;
          ??}

          ??public?void?setRequest(Request?request)?{
          ????this.request?=?request;
          ??}

          ??public?void?sendStaticResource()?throws?IOException?{
          ????byte[]?bytes?=?new?byte[BUFFER_SIZE];
          ????FileInputStream?fis?=?null;
          ????try?{
          ??????File?file?=?new?File(HttpServer.WEB_ROOT,?request.getUri());
          ??????if?(file.exists())?{
          ????????fis?=?new?FileInputStream(file);
          ????????int?ch?=?fis.read(bytes,?0,?BUFFER_SIZE);
          ????????while?(ch!=-1)?{
          ??????????output.write(bytes,?0,?ch);
          ??????????ch?=?fis.read(bytes,?0,?BUFFER_SIZE);
          ????????}
          ??????}
          ??????else?{
          ????????//?file?not?found
          ????????String?errorMessage?=?"HTTP/1.1?404?File?Not?Found\r\n"?+
          ??????????"Content-Type:?text/html\r\n"?+
          ??????????"Content-Length:?23\r\n"?+
          ??????????"\r\n"?+
          ??????????"

          File?Not?Found

          "
          ;
          ????????output.write(errorMessage.getBytes());
          ??????}
          ????}
          ????catch?(Exception?e)?{
          ??????//?thrown?if?cannot?instantiate?a?File?object
          ??????System.out.println(e.toString()?);
          ????}
          ????finally?{
          ??????if?(fis!=null)
          ????????fis.close();
          ????}
          ??}
          }

          總結(jié)

          在看了上面的例子之后,我們驚奇地發(fā)現(xiàn),在Java里面實(shí)現(xiàn)一個(gè)web服務(wù)器真容易,代碼也非常簡(jiǎn)單和清晰!

          題外話: 目前小哈正在個(gè)人博客(新搭建的網(wǎng)站,域名就是犬小哈的拼音)?www.quanxiaoha.com?上更新《Go語言教程》、《Gin Web框架教程》,畢竟Go自帶天然的并發(fā)優(yōu)勢(shì),后端的同學(xué)還是要學(xué)一下的,這個(gè)教程系列小哈會(huì)一直更新下去,歡迎小伙伴們?cè)L問哦~

          END


          有熱門推薦?

          1.?扔掉okhttp、httpClient,來試試這款輕量級(jí)HTTP客戶端神器?

          2.?SpringCloud中Zuul網(wǎng)關(guān)原理及其配置,看它就夠了!

          3.?一個(gè)基于 SpringBoot + Mybatis + Vue 的代碼生成器

          4.?面試官問:BitMap了解么?在什么場(chǎng)景下用過?碰到過什么問題?

          最近面試BAT,整理一份面試資料Java面試BATJ通關(guān)手冊(cè),覆蓋了Java核心技術(shù)、JVM、Java并發(fā)、SSM、微服務(wù)、數(shù)據(jù)庫、數(shù)據(jù)結(jié)構(gòu)等等。

          獲取方式:點(diǎn)“在看”,關(guān)注公眾號(hào)并回復(fù)?Java?領(lǐng)取,更多內(nèi)容陸續(xù)奉上。

          文章有幫助的話,在看,轉(zhuǎn)發(fā)吧。

          謝謝支持喲 (*^__^*)


          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  婷婷五月伊人 | 日韩欧美黄色电影 | 波多野结衣成人在线 | 日韩Aⅴ视屏 | 做爱网站免费观看 |