WebServer

Programming Assignment 1: Building a Multi-Threaded Web Server

一、什么是Web服務(wù)器

網(wǎng)頁服務(wù)器(Web server)一詞有兩個意思:一臺負(fù)責(zé)提供網(wǎng)頁的電腦揪漩,主要是各種編程語言構(gòu)建而成旋恼,通過HTTP協(xié)議傳給客戶端(一般是指網(wǎng)頁瀏覽器)。一個提供網(wǎng)頁的服務(wù)器程序奄容。
  雖然每個網(wǎng)頁服務(wù)器程序有很多不同冰更,但有一些共同的特點(diǎn):每一個網(wǎng)頁服務(wù)器程序都需要從網(wǎng)絡(luò)接受HTTP request,然后提供HTTP response給請求者昂勒。HTTP回復(fù)一般包含一個HTML文件蜀细,有時也可以包含純文本文件、圖像或其他類型的文件戈盈。

二奠衔、HTTP協(xié)議

簡介
本試驗(yàn)中我們將通過兩個階段來開發(fā)一個web服務(wù)器,最后完成一個能夠并行服務(wù)與多個請求的多線程Web服務(wù)器塘娶。
我們將實(shí)現(xiàn)在RFC 1945定義的HTTP1.0归斤。根據(jù)定義,每個Web page中的對象將通過單獨(dú)的HTTP消息來獲取刁岸。所實(shí)現(xiàn)Web服務(wù)器將能夠并發(fā)地服務(wù)于多個請求脏里,這意味著Web服務(wù)器是多線程的。 Web服務(wù)器的主線程負(fù)責(zé)偵聽某個端口虹曙,當(dāng)收到TCP連接請求時迫横,將創(chuàng)建一個新的socket負(fù)責(zé)與該TCP連接,并創(chuàng)建新的線程具體負(fù)責(zé)通過該連接的消息傳遞酝碳。為了簡化程序設(shè)計(jì)任務(wù)矾踱,我們分兩階段來設(shè)計(jì)Web服務(wù)器。

第一階段:編寫僅僅顯示所收到HTTP Request消息所有頭部行的一個多線程Web服務(wù)器疏哗。當(dāng)該程序運(yùn)行正確后呛讲,將添加適當(dāng)?shù)拇a以實(shí)現(xiàn)對Request消息的適當(dāng)響應(yīng)。

開發(fā)Web服務(wù)器時沃斤,可以通過Web瀏覽器來測試它圣蝎。不過,所編寫的Web服務(wù)器通常并不工作于80端口衡瓶,因此徘公,測試時在瀏覽器的地址欄中需要指定Web服務(wù)器的工作端口。例如:假設(shè)Web服務(wù)器運(yùn)行在域名為host.someschool.edu的主機(jī)上哮针,監(jiān)聽端口6789关面,我們想獲取文件index.html坦袍。需要在瀏覽器的地址欄中輸入如下的URL:
http://host.someschool.edu:6789/index.html
如果忽略了 ":6789", 瀏覽器則默認(rèn)地認(rèn)為Web服務(wù)器監(jiān)聽80端口。
當(dāng)Web服務(wù)器遇到問題等太,將向?yàn)g覽器發(fā)送包含適當(dāng)響應(yīng)消息的HTML頁面捂齐,以便在瀏覽器中顯示錯誤信息。

Web Server in Java: Part A

下面缩抡,我們將實(shí)現(xiàn)第一階段的編程任務(wù)奠宜。當(dāng)看到"?"時,你需要在該處添加相應(yīng)的代碼瞻想。
我們的第一個Web服務(wù)器將是多線程的压真,所收到的每個Request消息將交由單獨(dú)的線程進(jìn)行處理。這使得服務(wù)器可以并發(fā)地為多個客戶服務(wù), 或者是并發(fā)地服務(wù)于一個客戶的多個請求.當(dāng)創(chuàng)建一個新線程時蘑险,需要向線程的構(gòu)造函數(shù)傳遞實(shí)現(xiàn)了Runnable 接口的類的一個實(shí)例(即通過實(shí)現(xiàn)接口Runnable來實(shí)現(xiàn)多線程)滴肿。這正是我們定義單獨(dú)的類HttpRequest的原因。Web服務(wù)器的結(jié)構(gòu)如下:


import java.io.* ;
import java.net.* ;
import java.util.* ;
 
public final class WebServer
{
        public static void main(String argv[]) throws Exception
        {
               . . .
        }
}
 
final class HttpRequest implements Runnable
{
        . . .
}

通常佃迄,Web服務(wù)器為通過周知(well known)端口80收到的請求提供服務(wù)泼差。可以選擇大于1024的任意端口作為Web服務(wù)器的監(jiān)聽端口呵俏,但需要記著在瀏覽器地址欄中輸入URL時指定Web服務(wù)器的動作端口堆缘。


public static void main(String argv[]) throws Exception
{
        // Set the port number.
        int port = 6789;
 
        . . .
}

下面,創(chuàng)建監(jiān)聽端口以等待TCP連接請求普碎。由于Web服務(wù)器將不間斷地提供服務(wù)套啤,我們將偵聽操作放在一個無窮循環(huán)的循環(huán)體中。這意味著需要通過在鍵盤上輸入^C來結(jié)束Web服務(wù)器的運(yùn)行随常。


// Establish the listen socket.
       ?
 
// Process HTTP service requests in an infinite loop.
while (true) {
        // Listen for a TCP connection request.
        ?
 
        . . .
}

當(dāng)收到請求后,我們創(chuàng)建一個HttpRequest 對象萄涯,將標(biāo)征著所建立TCP連接的Socket作為參數(shù)傳遞到它的構(gòu)造函數(shù)中绪氛。


// Construct an object to process the HTTP request message.
HttpRequest request = new HttpRequest( ? );
 
// Create a new thread to process the request.
Thread thread = new Thread(request);
 
// Start the thread.
thread.start();

為了讓HttpRequest對象在一個單獨(dú)的線程中處理隨后的HTTP請求,我們首先創(chuàng)建一個Thread對象,將HttpRequest對象作為參數(shù)傳遞給Thread的構(gòu)造函數(shù)涝影,然后調(diào)用Thread的start()方法啟動線程枣察。
當(dāng)一個Thread創(chuàng)建并啟動后,主線程回到了循環(huán)體的首部燃逻。主線程將被阻塞(block)在accept處等待另一個TCP 連接請求的到達(dá)序目。此時,剛剛創(chuàng)建的線程正在運(yùn)行伯襟。當(dāng)另一個TCP連接請求到達(dá)時猿涨,主線程將不管前面創(chuàng)建的線程是否結(jié)束,重復(fù)上面的操作姆怪,創(chuàng)建新線程負(fù)責(zé)新連接的請求處理叛赚。
到這為止澡绩,主線程的工作就完成了,后面我們將集中精力設(shè)計(jì)類 HttpRequest俺附。
我們聲明HttpRequest類中的兩個變量: CRLF and socket肥卡。根據(jù)HTTP規(guī)范, 我們需要用”回車換行”作為Response消息頭部行的結(jié)束。因此事镣,為了使用方便步鉴,我們定義了一個CRLR字符串變量。變量socket用作connection socket, 它將被類HttpRequest的構(gòu)造函數(shù)初始化璃哟。

final class HttpRequest implements Runnable
{
        final static String CRLF = "\r\n";
        Socket socket;
 
        // Constructor
        public HttpRequest(Socket socket) throws Exception 
        {
               this.socket = socket;
        }
 
        // Implement the run() method of the Runnable interface.
        public void run()
        {
               . . .
        }
 
        private void processRequest() throws Exception
        {
               . . .
        }
}

為了將類HttpRequest的實(shí)例作為參數(shù)傳輸傳遞到Thread的構(gòu)造函數(shù)中氛琢,HttpRequest必須實(shí)現(xiàn)Runnable接口。因此沮稚,必須定義HttpRequest的public方法run()艺沼,其返回值類型為void。我們在run()中調(diào)用實(shí)現(xiàn)Request消息處理絕大部分操作的方法 processRequest()蕴掏。
直到現(xiàn)在障般,我們其實(shí)一直在拋出異常, 而不是catching他們。不過盛杰,我們不能從方法run()中拋出異常挽荡,因?yàn)槲覀儽仨殗?yán)格遵守Runnable接口對run()的聲明。Runnable接口的run()方法不拋出任何異常即供。我們將在processRequest中放置處理代碼定拟,并從此在run方法中利用try/catch塊處理異常。
// Implement the run() method of the Runnable interface.
public void run()
{
try {
processRequest();
} catch (Exception e) {
System.out.println(e);
}
}
現(xiàn)在逗嫡,設(shè)計(jì)processRequest()中的代碼青自。首先獲得socket的輸入/出流的reference引用。然后驱证,我們給input stream包裝過濾器(filters)延窜。但是,輸出流無須包裝任何過濾器抹锄,主要原因是我們將向輸出流直接寫入bytes逆瑞。

private void processRequest() throws Exception
{
        // Get a reference to the socket's input and output streams.
        InputStream is = ?;
        DataOutputStream os = ?;
 
        // Set up input stream filters.
        ? 
        BufferedReader br = ?;
 
        . . .
}

現(xiàn)在我們已經(jīng)準(zhǔn)備好來獲得客戶發(fā)來的HTTP Request消息了(通過從socket的輸入流讀取消息)。類BufferedReader的方法readLine()方法將從輸入流中讀取字符伙单,直到遇到CRLF為止(也就是從input stream中讀取一行获高,行的結(jié)束符為CRLF)。
從input stream中讀出的第一行為HTTP Request消息的請求行 (參看教材2.2吻育,了解請求行的定義)念秧。

// Get the request line of the HTTP request message.
String requestLine = ?;
 
// Display the request line.
System.out.println();
System.out.println(requestLine);

讀取消息的請求行后,讀取消息的其它頭部行扫沼。由于我們并不知道客戶發(fā)送消息中有多少頭部行出爹,必須利用一個循環(huán)操作來獲取Request消息的所有頭部行庄吼。

// Get and display the header lines.
String headerLine = null;
while ((headerLine = br.readLine()).length() != 0) {
        System.out.println(headerLine);
}

由于除了需要將頭部行中的內(nèi)容顯示在屏幕上外,現(xiàn)階段無須針對頭部行做其它的處理严就,我們僅僅利用臨時變量headerLine來保存頭部行的信息总寻。循環(huán)操作直到下面的表達(dá)式值等于0時停止。也就是讀取的 頭部行的長度如果為零梢为,表示讀出了一個空行渐行,意味著所有的頭部行已經(jīng)全部讀出(參看教材的2.2 部分,頭部行和entity body之間利用一個空行作為分割)铸董。

(headerLine = br.readLine()).length()

后面我們將添加分析客戶Request消息的代碼祟印,并發(fā)送Response消息 。在進(jìn)行后面的程序設(shè)計(jì)前粟害,我們先完成第一階段的任務(wù)蕴忆,并通過瀏覽器來測試它。添加如下代碼以關(guān)閉輸入/出流和connection socket悲幅。

// Close streams and socket.
os.close();
br.close();
socket.close();

當(dāng)程序編譯成功后套鹅,以適當(dāng)?shù)亩丝谧鳛閰?shù)運(yùn)行Web服務(wù)器,并利用瀏覽器訪問它汰具。在瀏覽器地址欄中輸入下面的示例:
http://host.someschool.edu:6789/
Web服務(wù)器將顯示HTTP Request消息的內(nèi)容卓鹿。檢查請求消息的格式是否與教材2.2中描述的HTTP Request消息格式相符。

Web Server in Java: Part B

Web服務(wù)器不能僅僅顯示收到的Request消息的內(nèi)容留荔,而是應(yīng)該分析收到的Request消息并產(chǎn)生適當(dāng)?shù)腞esponse消息吟孙。我們將忽略Request消息頭部行中包含的信息,僅僅關(guān)注Request消息的請求行中包含的文件名字聚蝶。我們將假設(shè)客戶發(fā)送的Request消息中的Request行總是使用GET方法杰妓,實(shí)際上,一個瀏覽器可能使用GET碘勉、POST和HEAD方法(HTTP1.0)
利用類StringTokenizer從Request行中解析出文件名字稚失。

首先,創(chuàng)建一個 StringTokenizer對象來容納Request行恰聘;

第二步:跳過Method字段(因?yàn)榭偸荊ET方法);

第三步吸占,解析出文件名字晴叨。

// Extract the filename from the request line.
StringTokenizer tokens = new StringTokenizer(requestLine);
tokens.nextToken();  // skip over the method, which should be "GET"
String fileName = tokens.nextToken();
 
// Prepend a "." so that file request is within the current directory.
fileName = "." + fileName;

由于瀏覽器在文件名字前加了一個“/“,我們在它前面加上一個字符 ”.”矾屯,從而限定從當(dāng)前目錄開始獲取文件兼蕊。
現(xiàn)在有了客戶請求的文件名字,我們可以打開該文件作為向客戶發(fā)送該文件的第一步件蚕。如果文件不存在孙技,構(gòu)造函數(shù) FileInputStream() 將拋出異常FileNotFoundException产禾,為了在拋出此可能的異常后不終止線程的執(zhí)行,利用一個try/catch塊將布爾型變量fileExists設(shè)置為false诽嘉。后面我們將使用該變量來構(gòu)建一個錯誤響應(yīng)消息煌抒,而不是發(fā)送一個根本不存在的文件昙沦。

// Open the requested file.
FileInputStream fis = null;
boolean fileExists = true;
try {
        fis = new FileInputStream(fileName);
} catch (FileNotFoundException e) {
        fileExists = false;
}

Response消息有三部分: the status line, the response headers, 和entity body。狀態(tài)行楞件、頭部行以CRLF作為結(jié)束。利用變量statusLine 來保存響應(yīng)消息的statusline裳瘪、contentTypeLine保存Content-Type頭部行信息土浸。當(dāng)文件不存在時,Web服務(wù)器將返回狀態(tài)行為“404 Not Found“彭羹,entity body中保存利用HTML創(chuàng)建的錯誤消息黄伊。

// Construct the response message.
String statusLine = null;
String contentTypeLine = null;
String entityBody = null;
if (fileExists) {
        statusLine = ?;
        contentTypeLine = "Content-type: " + 
               contentType( fileName ) + CRLF;
} else {
        statusLine = ?;
        contentTypeLine = ?;
        entityBody = "<HTML>" + 
               "<HEAD><TITLE>Not Found</TITLE></HEAD>" +
               "<BODY>Not Found</BODY></HTML>";
}

當(dāng)文件存在,需要確定文件的MIME類型和發(fā)送適當(dāng)?shù)腗IME-Type指示符派殷,利用private方法contentType()實(shí)現(xiàn)該上述任務(wù)还最。該方法將返回包含在Conten-Type頭部行的信息(字符串)。
現(xiàn)在愈腾,我們可以通過向socket的輸出流寫入status line 和唯一的一個header line來向客戶瀏覽器發(fā)送信息憋活。


// Send the status line.
os.writeBytes(statusLine);
 
// Send the content type line.
os.writeBytes(?);
 
// Send a blank line to indicate the end of the header lines.
os.writeBytes(CRLF);

下面需要發(fā)送消息的entity body了。如果請求的文件存在虱黄,我們調(diào)用另一個方法來發(fā)送文件悦即;如果請求的文件不存在,我們向客戶發(fā)送一個HTML編碼的錯誤消息(前面已經(jīng)準(zhǔn)備好橱乱,即在變量entityBody中辜梳。

// Send the entity body.
if (fileExists) {
        sendBytes(fis, os);
        fis.close();
} else {
        os.writeBytes(?);
}

發(fā)送完entitybody后,線程的任務(wù)已經(jīng)全部完成泳叠,在結(jié)束線程前需要關(guān)閉流和socket.
我們還需要實(shí)現(xiàn)前面提到的兩個方法:contentType()和
sendBytes()作瞄。

private static void sendBytes(FileInputStream fis, OutputStream os) 
throws Exception
{
   // Construct a 1K buffer to hold bytes on their way to the socket.
   byte[] buffer = new byte[1024];
   int bytes = 0;
 
   // Copy requested file into the socket's output stream.
   while((bytes = fis.read(buffer)) != -1 ) {
      os.write(buffer, 0, bytes);
   }
}

read()和write()均拋出異常,我們在sendBytes中并不處理這些異常危纫,而是將異常處理的任務(wù)交給調(diào)用sendBytes的方法宗挥。
變量buffer,用于作為文件和輸出流之間的中間存儲空間种蝶。當(dāng)從FileInputStream中讀取字節(jié)時契耿,,檢查讀取的字節(jié)是否為-1(即文件結(jié)束標(biāo)識 EOF)。如果讀到了EOF螃征,read()返回已經(jīng)放入buffer的字節(jié)數(shù)搪桂。利用方法類OutputStream 的方法write() 將保存在buffer中的字節(jié)數(shù)據(jù)發(fā)送到輸出流, write的參數(shù)buffer、0盯滚、bytes分別為byte數(shù)組的名字踢械、第一個字節(jié)的位置酗电、需要寫出的字節(jié)數(shù)。
Web Server中需要完成最后一部分代碼為contentType内列,實(shí)現(xiàn)根據(jù)文件的擴(kuò)展名來確定所代表的MIME 類型撵术。如果文件擴(kuò)展名未知,則方法返回application/octet-stream.


private static String contentType(String fileName)
{
        if(fileName.endsWith(".htm") || fileName.endsWith(".html")) {
               return "text/html";
        }
        if(?) {
               ?;
        }
        if(?) {
               ?;
        }
        return "application/octet-stream";
}

到現(xiàn)在為止德绿,我們完成了Web Server的第二階段任務(wù)荷荤。嘗試從保存有homepage的目錄運(yùn)行Web服務(wù)器,記住在URL中包含Web服務(wù)器的工作端口移稳。

整段代碼


import java.net.ServerSocket;
import java.net.Socket;
import java.awt.im.InputContext;
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.Socket;
import java.util.StringTokenizer;

public final class Webserver {
    public static void main(String[] args) throws Exception {
        int port = 6666;

        //server創(chuàng)立接聽端口
        ServerSocket welcomeSocket = new ServerSocket(port);

        //處理一個死循環(huán)中的 HTTP 服務(wù)請求
        while(true)
        {
            //client監(jiān)聽一個 TCP 連接請求
            Socket connectionSocket = welcomeSocket.accept();

            // 構(gòu)造一個對象來處理 HTTP 請求消息
            HttpRequest request = new HttpRequest(connectionSocket);

            //創(chuàng)建一個新的線程來處理請求
            Thread thread = new Thread(request);

            //開始新線程
            thread.start();
        }
    }
}

final class HttpRequest implements Runnable {

    //http空白行結(jié)束標(biāo)志
    final static String CRLF = "\r\n";
    Socket socket;
//構(gòu)造函數(shù)
    public HttpRequest(Socket socket) {
        this.socket = socket;
    }

    private void processRequest() throws Exception {
        //  獲取套接字的輸入和輸出流的引用
        InputStream is = socket.getInputStream();
        DataOutputStream os = new DataOutputStream(socket.getOutputStream());
        //設(shè)置輸入流的緩沖
        BufferedReader br = new BufferedReader(new InputStreamReader(is));

        //獲取請求的 HTTP 請求消息的行
        String requestline = br.readLine();
        //顯示請求行
        System.out.println();
        System.out.println(requestline);

        //得到且顯示獲取的頭部
        String headerline = null;
        while ((headerline = br.readLine()).length() != 0) {
            System.out.println(headerline);
        }
        //從請求行中提取文件名蕴纳。
        StringTokenizer tokens = new StringTokenizer(requestline);
        tokens.nextToken();
        String fileName = tokens.nextToken();
        //前面加上“.”所以,在當(dāng)前目錄下的文件的請求
        fileName = '.' + fileName;
//打開文件流和文件信息
        FileInputStream fis = null;
        boolean fileExists = true;
        try {
            fis = new FileInputStream(fileName);
        } catch (FileNotFoundException e) {
            fileExists = false;
        }

        //構(gòu)建相應(yīng)信息
        String statusLine = null;
        String contentTypeLine = null;
        String entityBody = null;
        if (fileExists) {
            statusLine = "HTTP/1.0 200 OK";
            contentTypeLine = "Content-type:" + contentType(fileName) + CRLF;
        } else {
            statusLine = "HTTP/1.0 404 Not Found";
            contentTypeLine = "Content-type: text/html" + CRLF;
            entityBody = "<HTML>" + "<HEAD><TITLE>Not Found</TITLE></HEAD>" + "<BODY>Not Found</BODY></BODY>";
        }
        //發(fā)送狀態(tài)線
        os.writeBytes(statusLine);
        //發(fā)送鏈接類型
        os.writeBytes(contentTypeLine);
        ///發(fā)送一個空白行个粱,以指示頭行的結(jié)束
        os.writeBytes(CRLF);

        if (fileExists) {
            sendBytes(fis, os);
            fis.close();
        } else {
            os.writeBytes(entityBody);
        }
        os.close();
        br.close();
        socket.close();
    }

    private void sendBytes(FileInputStream fis, DataOutputStream os) throws IOException {

        //構(gòu)建1K緩沖的方式字節(jié)
        byte[] buffer = new byte[1024];
        int bytes = 0;//桶裝為0

        //將請求的文件復(fù)制到套接字的輸出流中
        while ((bytes = fis.read(buffer)) != -1) {
            os.write(buffer, 0, bytes);
        }
    }

    private static String contentType(String fileName) {
        if (fileName.endsWith(".htm") || fileName.endsWith(".html")) {
            return "text/html";
        }
        if (fileName.endsWith(".jpg")) {
            return "text/jpg";
        }
        if (fileName.endsWith(".gif")) {
            return "text/gif";
        }

        if (fileName.endsWith(".mp3")) {
            return "audio/mp3";
        }
        if (fileName.endsWith(".mp4")) {
            return "video/mpeg4";
            }
        return "application/octet-stram";
    }
//實(shí)現(xiàn)runnable接口的run函數(shù)
    @Override
    public void run() {
        try {
            processRequest();
        } catch (Exception e) {
            System.out.println(e);
        }

    }

}

實(shí)驗(yàn)結(jié)果

test-1:

test-qilixiang.jpg

test-2:

![test-xingkong.gif . . .]


Paste_Image.png

test-3

Paste_Image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末古毛,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子都许,更是在濱河造成了極大的恐慌稻薇,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件胶征,死亡現(xiàn)場離奇詭異塞椎,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)睛低,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門案狠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人钱雷,你說我怎么就攤上這事骂铁。” “怎么了罩抗?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵拉庵,是天一觀的道長。 經(jīng)常有香客問我套蒂,道長钞支,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任操刀,我火速辦了婚禮伸辟,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘馍刮。我一直安慰自己,他們只是感情好窃蹋,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布卡啰。 她就那樣靜靜地躺著静稻,像睡著了一般。 火紅的嫁衣襯著肌膚如雪匈辱。 梳的紋絲不亂的頭發(fā)上振湾,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天,我揣著相機(jī)與錄音亡脸,去河邊找鬼押搪。 笑死,一個胖子當(dāng)著我的面吹牛浅碾,可吹牛的內(nèi)容都是我干的大州。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼垂谢,長吁一口氣:“原來是場噩夢啊……” “哼厦画!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起滥朱,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤根暑,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徙邻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體排嫌,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年缰犁,在試婚紗的時候發(fā)現(xiàn)自己被綠了淳地。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡民鼓,死狀恐怖薇芝,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情丰嘉,我是刑警寧澤夯到,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站饮亏,受9級特大地震影響耍贾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜路幸,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一荐开、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧简肴,春花似錦晃听、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佣渴。三九已至,卻和暖如春初斑,著一層夾襖步出監(jiān)牢的瞬間辛润,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工见秤, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留砂竖,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓鹃答,卻偏偏與公主長得像乎澄,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子挣跋,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理三圆,服務(wù)發(fā)現(xiàn),斷路器避咆,智...
    卡卡羅2017閱讀 134,657評論 18 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在舟肉,面了一些公司,掛了不少查库,但最終還是拿到小米路媚、百度、阿里樊销、京東整慎、新浪、CVTE围苫、樂視家的研發(fā)崗...
    時芥藍(lán)閱讀 42,253評論 11 349
  • HTTP概述 超文本傳輸協(xié)議(HTTP裤园,HyperText Transfer Protocol) 是互聯(lián)網(wǎng)上應(yīng)用最...
    曹淵說創(chuàng)業(yè)閱讀 3,853評論 2 61
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,156評論 25 707
  • 生活中的跌跌撞撞,不斷試錯剂府,在工作中修行----最終是為了找到愉悅的靈魂拧揽! 郭相麟
    郭相麟閱讀 122評論 0 0