Socket和ServerSocket學習筆記

對于即時類應用或者即時類的游戲,HTTP協議很多時候無法滿足于我們的需求。這會,Socket對于我們來說就非常實用了。下面是本次學習的筆記。主要分異常類型、交互原理、Socket、ServerSocket、多線程這幾個方面闡述。


異常類型

在了解Socket的內容之前,先要了解一下涉及到的一些異常類型。以下四種類型都是繼承于IOException,所以很多之后直接彈出IOException即可。

UnkownHostException:? ?   主機名字或IP錯誤

ConnectException:? ?     服務器拒絕連接、服務器沒有啟動、(超出隊列數,拒絕連接)

SocketTimeoutException:? ? ??連接超時

BindException:? ?       Socket對象無法與制定的本地IP地址或端口綁定


交互過程

Socket與ServerSocket的交互,下面的圖片我覺得已經說的很詳細很清楚了。


在客戶/服務器通信模式中,服務器端需要創建監聽特定端口的ServerSocket,ServerSocket負責接收客戶連接請求。本章首先介紹ServerSocket類的各個構造方法,以及成員方法的用法,接著介紹服務器如何用多線程來處理與多個客戶的通信任務。

本章提供線程池的一種實現方式。線程池包括一個工作隊列和若干工作線程。服務器程序向工作隊列中加入與客戶通信的任務,工作線程不斷從工作隊列中取出任務并執行它。本章還介紹了Java.util.concurrent包中的線程池類的用法,在服務器程序中可以直接使用它們。

3.1? 構造ServerSocket

ServerSocket的構造方法有以下幾種重載形式:

◆ServerSocket()throws IOException

◆ServerSocket(int port) throws IOException

◆ServerSocket(int port, int backlog) throws IOException

◆ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException

在以上構造方法中,參數port指定服務器要綁定的端口(服務器要監聽的端口),參數backlog指定客戶連接請求隊列的長度,參數bindAddr指定服務器要綁定的IP地址。

3.1.1? 綁定端口

除了第一個不帶參數的構造方法以外,其他構造方法都會使服務器與特定端口綁定,該端口由參數port指定。例如,以下代碼創建了一個與80端口綁定的服務器:

[java]view plaincopy?

?

ServerSocket?serverSocket=new?ServerSocket(80);??

◆端口已經被其他服務器進程占用;如果運行時無法綁定到80端口,以上代碼會拋出IOException,更確切地說,是拋出BindException,它是IOException的子類。BindException一般是由以下原因造成的:

◆在某些操作系統中,如果沒有以超級用戶的身份來運行服務器程序,那么操作系統不允許服務器綁定到1~1023之間的端口。

如果把參數port設為0,表示由操作系統來為服務器分配一個任意可用的端口。由操作系統分配的端口也稱為匿名端口。對于多數服務器,會使用明確的端口,而不會使用匿名端口,因為客戶程序需要事先知道服務器的端口,才能方便地訪問服務器。在某些場合,匿名端口有著特殊的用途,本章3.4節會對此作介紹。

3.1.2? 設定客戶連接請求隊列的長度

當服務器進程運行時,可能會同時監聽到多個客戶的連接請求。例如,每當一個客戶進程執行以下代碼:

[java]view plaincopy?

?

Socket?socket=new?Socket(www.javathinker.org,80);??

就意味著在遠程www.javathinker.org主機的80端口上,監聽到了一個客戶的連接請求。管理客戶連接請求的任務是由操作系統來完成的。操作系統把這些連接請求存儲在一個先進先出的隊列中。許多操作系統限定了隊列的最大長度,一般為50。當隊列中的連接請求達到了隊列的最大容量時,服務器進程所在的主機會拒絕新的連接請求。只有當服務器進程通過ServerSocket的accept()方法從隊列中取出連接請求,使隊列騰出空位時,隊列才能繼續加入新的連接請求。

對于客戶進程,如果它發出的連接請求被加入到服務器的隊列中,就意味著客戶與服務器的連接建立成功,客戶進程從Socket構造方法中正常返回。如果客戶進程發出的連接請求被服務器拒絕,Socket構造方法就會拋出ConnectionException。

ServerSocket構造方法的backlog參數用來顯式設置連接請求隊列的長度,它將覆蓋操作系統限定的隊列的最大長度。值得注意的是,在以下幾種情況中,仍然會采用操作系統限定的隊列的最大長度:

◆backlog參數的值大于操作系統限定的隊列的最大長度;

◆backlog參數的值小于或等于0;

◆在ServerSocket構造方法中沒有設置backlog參數。

以下例程3-1的Client.java和例程3-2的Server.java用來演示服務器的連接請求隊列的特性。

例程3-1? Client.java

[java]view plaincopy?

?

import?java.net.*;??

public?class?Client?{??

public?static?void?main(String?args[])throws?Exception{??

final?int?length=100;??

String?host="localhost";??

int?port=8000;??

Socket[]?sockets=new?Socket[length];??

for(int?i=0;i

sockets[i]=new?Socket(host,?port);??

System.out.println("第"+(i+1)+"次連接成功");??

????}??

Thread.sleep(3000);??

for(int?i=0;i

sockets[i].close();//斷開連接??

????}???

??}??

}??

[java]view plaincopy?

?

import?java.io.*;??

import?java.net.*;??

public?class?Server?{??

private?int?port=8000;??

private?ServerSocket?serverSocket;??

public?Server()?throws?IOException?{??

serverSocket?=new?ServerSocket(port,3);????//連接請求隊列的長度為3??

System.out.println("服務器啟動");??

??}??

public?void?service()?{??

while?(true)?{??

Socket?socket=null;??

try?{??

socket?=?serverSocket.accept();//從連接請求隊列中取出一個連接???????????

System.out.println("New?connection?accepted?"?+??

socket.getInetAddress()?+":"?+socket.getPort());??

}catch?(IOException?e)?{??

?????????e.printStackTrace();??

}finally?{??

try{??

if(socket!=null)socket.close();??

}catch?(IOException?e)?{e.printStackTrace();}??

??????}??

????}??

??}??

public?static?void?main(String?args[])throws?Exception?{??

Server?server=new?Server();??

Thread.sleep(60000*10);??????//睡眠10分鐘??

//server.service();??

??}??

}??

例程3-2? Server.java

Client試圖與Server進行100次連接。在Server類中,把連接請求隊列的長度設為3。這意味著當隊列中有了3個連接請求時,如果Client再請求連接,就會被Server拒絕。下面按照以下步驟運行Server和Client程序。

(1)把Server類的main()方法中的“server.service();”這行程序代碼注釋掉。這使得服務器與8000端口綁定后,永遠不會執行serverSocket.accept()方法。這意味著隊列中的連接請求永遠不會被取出。先運行Server程序,然后再運行Client程序,Client程序的打印結果如下:

[java]view plaincopy?

?

第1次連接成功??

第2次連接成功??

第3次連接成功??

Exception?in?thread"main"?java.net.ConnectException:?Connection?refused:?connect??????????

????????at?java.net.PlainSocketImpl.socketConnect(Native?Method)??

????????at?java.net.PlainSocketImpl.doConnect(Unknown?Source)??

????????at?java.net.PlainSocketImpl.connectToAddress(Unknown?Source)??

????????at?java.net.PlainSocketImpl.connect(Unknown?Source)??

????????at?java.net.SocksSocketImpl.connect(Unknown?Source)??

????????at?java.net.Socket.connect(Unknown?Source)??

????????at?java.net.Socket.connect(Unknown?Source)??

????????at?java.net.Socket.(Unknown?Source)??

????????at?java.net.Socket.(Unknown?Source)??

at?Client.main(Client.java:10)??

(2)把Server類的main()方法按如下方式修改:從以上打印結果可以看出,Client與Server在成功地建立了3個連接后,就無法再創建其余的連接了,因為服務器的隊列已經滿了。

[java]view plaincopy?

?

public?static?void?main(String?args[])throws?Exception?{???????????????????

Server?server=new?Server();??

//Thread.sleep(60000*10);??//睡眠10分鐘??

????server.service();??

??}??

作了以上修改,服務器與8 000端口綁定后,就會在一個while循環中不斷執行serverSocket.accept()方法,該方法從隊列中取出連接請求,使得隊列能及時騰出空位,以容納新的連接請求。先運行Server程序,然后再運行Client程序,Client程序的打印結果如下:

[java]view plaincopy?

?

第1次連接成功??

第2次連接成功??

第3次連接成功??

…??

第100次連接成功??

從以上打印結果可以看出,此時Client能順利與Server建立100次連接。

3.1.3? 設定綁定的IP地址

如果主機只有一個IP地址,那么默認情況下,服務器程序就與該IP地址綁定。ServerSocket的第4個構造方法ServerSocket(int port, int backlog, InetAddress bindAddr)有一個bindAddr參數,它顯式指定服務器要綁定的IP地址,該構造方法適用于具有多個IP地址的主機。假定一個主機有兩個網卡,一個網卡用于連接到Internet, IP地址為222.67.5.94,還有一個網卡用于連接到本地局域網,IP地址為192.168.3.4。如果服務器僅僅被本地局域網中的客戶訪問,那么可以按如下方式創建ServerSocket:

[java]view plaincopy?

?

ServerSocket?serverSocket=new?ServerSocket(8000,10,InetAddress.getByName?("192.168.3.4"));??

3.1.4? 默認構造方法的作用

ServerSocket有一個不帶參數的默認構造方法。通過該方法創建的ServerSocket不與任何端口綁定,接下來還需要通過bind()方法與特定端口綁定。

這個默認構造方法的用途是,允許服務器在綁定到特定端口之前,先設置ServerSocket的一些選項。因為一旦服務器與特定端口綁定,有些選項就不能再改變了。

在以下代碼中,先把ServerSocket的SO_REUSEADDR選項設為true,然后再把它與8000端口綁定:

[java]view plaincopy?

?

ServerSocket?serverSocket=new?ServerSocket();??

serverSocket.setReuseAddress(true);??????//設置ServerSocket的選項??

serverSocket.bind(new?InetSocketAddress(8000));???//與8000端口綁定??

如果把以上程序代碼改為:

[java]view plaincopy?

?

ServerSocket?serverSocket=new?ServerSocket(8000);??

serverSocket.setReuseAddress(true);??????//設置ServerSocket的選項??

那么serverSocket.setReuseAddress(true)方法就不起任何作用了,因為SO_ REUSEADDR選項必須在服務器綁定端口之前設置才有效。

3.2? 接收和關閉與客戶的連接

ServerSocket的accept()方法從連接請求隊列中取出一個客戶的連接請求,然后創建與客戶連接的Socket對象,并將它返回。如果隊列中沒有連接請求,accept()方法就會一直等待,直到接收到了連接請求才返回。

接下來,服務器從Socket對象中獲得輸入流和輸出流,就能與客戶交換數據。當服務器正在進行發送數據的操作時,如果客戶端斷開了連接,那么服務器端會拋出一個IOException的子類SocketException異常:

[java]view plaincopy?

?

java.net.SocketException:?Connection?reset?by?peer??

這只是服務器與單個客戶通信中出現的異常,這種異常應該被捕獲,使得服務器能繼續與其他客戶通信。

以下程序顯示了單線程服務器采用的通信流程:

[java]view plaincopy?

?

public?void?service()?{??

while?(true)?{??

Socket?socket=null;??

try?{??

socket?=?serverSocket.accept();//從連接請求隊列中取出一個連接??

System.out.println("New?connection?accepted?"?+??

socket.getInetAddress()?+":"?+socket.getPort());??

//接收和發送數據??

??????…??

}catch?(IOException?e)?{??

//這只是與單個客戶通信時遇到的異常,可能是由于客戶端過早斷開連接引起的?????

//這種異常不應該中斷整個while循環??

???????e.printStackTrace();??

}finally?{??

try{??

if(socket!=null)socket.close();????//與一個客戶通信結束后,要關閉Socket????????????

}catch?(IOException?e)?{e.printStackTrace();}??

????}??

??}??

}??

與單個客戶通信的代碼放在一個try代碼塊中,如果遇到異常,該異常被catch代碼塊捕獲。try代碼塊后面還有一個finally代碼塊,它保證不管與客戶通信正常結束還是異常結束,最后都會關閉Socket,斷開與這個客戶的連接。

3.3? 關閉ServerSocket

ServerSocket的close()方法使服務器釋放占用的端口,并且斷開與所有客戶的連接。當一個服務器程序運行結束時,即使沒有執行ServerSocket的close()方法,操作系統也會釋放這個服務器占用的端口。因此,服務器程序并不一定要在結束之前執行ServerSocket的close()方法。

在某些情況下,如果希望及時釋放服務器的端口,以便讓其他程序能占用該端口,則可以顯式調用ServerSocket的close()方法。例如,以下代碼用于掃描1~65535之間的端口號。如果ServerSocket成功創建,意味著該端口未被其他服務器進程綁定,否者說明該端口已經被其他進程占用:

[java]view plaincopy?

?

for(int?port=1;port<=65535;port++){??

try{??

ServerSocket?serverSocket=new?ServerSocket(port);??

serverSocket.close();//及時關閉ServerSocket??

}catch(IOException?e){??

System.out.println("端口"+port+"?已經被其他服務器進程占用");??

}??

}??

以上程序代碼創建了一個ServerSocket對象后,就馬上關閉它,以便及時釋放它占用的端口,從而避免程序臨時占用系統的大多數端口。

ServerSocket的isClosed()方法判斷ServerSocket是否關閉,只有執行了ServerSocket的close()方法,isClosed()方法才返回true;否則,即使ServerSocket還沒有和特定端口綁定,isClosed()方法也會返回false。

ServerSocket的isBound()方法判斷ServerSocket是否已經與一個端口綁定,只要ServerSocket已經與一個端口綁定,即使它已經被關閉,isBound()方法也會返回true。

如果需要確定一個ServerSocket已經與特定端口綁定,并且還沒有被關閉,則可以采用以下方式:

[java]view plaincopy?

?

boolean?isOpen=serverSocket.isBound()?&&?!serverSocket.isClosed();??

3.4? 獲取ServerSocket的信息

ServerSocket的以下兩個get方法可分別獲得服務器綁定的IP地址,以及綁定的端口:

◆public InetAddress getInetAddress()

◆public int getLocalPort()

前面已經講到,在構造ServerSocket時,如果把端口設為0,那么將由操作系統為服務器分配一個端口(稱為匿名端口),程序只要調用getLocalPort()方法就能獲知這個端口號。如例程3-3所示的RandomPort創建了一個ServerSocket,它使用的就是匿名端口。

#p#

例程3-3? RandomPort.java

[java]view plaincopy?

?

import?java.io.*;??

import?java.net.*;??

public?class?RandomPort{??

public?static?void?main(String?args[])throws?IOException{??

ServerSocket?serverSocket=new?ServerSocket(0);??

System.out.println("監聽的端口為:"+serverSocket.getLocalPort());???????

??}??

}??

多次運行RandomPort程序,可能會得到如下運行結果:

[java]view plaincopy?

?

C:\chapter03\classes>java?RandomPort??

監聽的端口為:3000??

C:\chapter03\classes>java?RandomPort??

監聽的端口為:3004??

C:\chapter03\classes>java?RandomPort??

監聽的端口為:3005??

多數服務器會監聽固定的端口,這樣才便于客戶程序訪問服務器。匿名端口一般適用于服務器與客戶之間的臨時通信,通信結束,就斷開連接,并且ServerSocket占用的臨時端口也被釋放。

FTP(文件傳輸)協議就使用了匿名端口。如圖3-1所示,FTP協議用于在本地文件系統與遠程文件系統之間傳送文件。

圖3-1? FTP協議用于在本地文件系統與遠程文件系統之間傳送文件

FTP使用兩個并行的TCP連接:一個是控制連接,一個是數據連接。控制連接用于在客戶和服務器之間發送控制信息,如用戶名和口令、改變遠程目錄的命令或上傳和下載文件的命令。數據連接用于傳送文件。TCP服務器在21端口上監聽控制連接,如果有客戶要求上傳或下載文件,就另外建立一個數據連接,通過它來傳送文件。數據連接的建立有兩種方式。

(1)如圖3-2所示,TCP服務器在20端口上監聽數據連接,TCP客戶主動請求建立與該端口的連接。

圖3-2? TCP服務器在20端口上監聽數據連接

(2)如圖3-3所示,首先由TCP客戶創建一個監聽匿名端口的ServerSocket,再把這個ServerSocket監聽的端口號(調用ServerSocket的getLocalPort()方法就能得到端口號)發送給TCP服務器,然后由TCP服務器主動請求建立與客戶端的連接。

圖3-3? TCP客戶在匿名端口上監聽數據連接

以上第二種方式就使用了匿名端口,并且是在客戶端使用的,用于和服務器建立臨時的數據連接。在實際應用中,在服務器端也可以使用匿名端口。

3.5? ServerSocket選項

ServerSocket有以下3個選項。

◆SO_TIMEOUT:表示等待客戶連接的超時時間。

◆SO_REUSEADDR:表示是否允許重用服務器所綁定的地址。

◆SO_RCVBUF:表示接收數據的緩沖區的大小。

3.5.1? SO_TIMEOUT選項

◆設置該選項:public void setSoTimeout(int timeout) throws SocketException

◆讀取該選項:public int getSoTimeout () throws IOException

SO_TIMEOUT表示ServerSocket的accept()方法等待客戶連接的超時時間,以毫秒為單位。如果SO_TIMEOUT的值為0,表示永遠不會超時,這是SO_TIMEOUT的默認值。

當服務器執行ServerSocket的accept()方法時,如果連接請求隊列為空,服務器就會一直等待,直到接收到了客戶連接才從accept()方法返回。如果設定了超時時間,那么當服務器等待的時間超過了超時時間,就會拋出SocketTimeoutException,它是InterruptedException的子類。

如例程3-4所示的TimeoutTester把超時時間設為6秒鐘。

#p#

例程3-4? TimeoutTester.java

[java]view plaincopy?

?

import?java.io.*;??

import?java.net.*;??

public?class?TimeoutTester{??

public?static?void?main(String?args[])throws?IOException{??

ServerSocket?serverSocket=new?ServerSocket(8000);??

serverSocket.setSoTimeout(6000);?//等待客戶連接的時間不超過6秒???????????

????Socket?socket=serverSocket.accept();???

????socket.close();??

System.out.println("服務器關閉");??

??}??

}??

運行以上程序,過6秒鐘后,程序會從serverSocket.accept()方法中拋出Socket- TimeoutException:

[java]view plaincopy?

?

C:\chapter03\classes>java?TimeoutTester??

Exception?in?thread"main"?java.net.SocketTimeoutException:?Accept?timed?out????????

????????at?java.net.PlainSocketImpl.socketAccept(Native?Method)??

????????at?java.net.PlainSocketImpl.accept(Unknown?Source)??

????????at?java.net.ServerSocket.implAccept(Unknown?Source)??

????????at?java.net.ServerSocket.accept(Unknown?Source)??

at?TimeoutTester.main(TimeoutTester.java:8)??

如果把程序中的“serverSocket.setSoTimeout(6000)”注釋掉,那么serverSocket. accept()方法永遠不會超時,它會一直等待下去,直到接收到了客戶的連接,才會從accept()方法返回。

Tips:服務器執行serverSocket.accept()方法時,等待客戶連接的過程也稱為阻塞。本書第4章的4.1節(線程阻塞的概念)詳細介紹了阻塞的概念。

3.5.2? SO_REUSEADDR選項

◆設置該選項:public void setResuseAddress(boolean on) throws SocketException

◆讀取該選項:public boolean getResuseAddress() throws SocketException

這個選項與Socket的SO_REUSEADDR選項相同,用于決定如果網絡上仍然有數據向舊的ServerSocket傳輸數據,是否允許新的ServerSocket綁定到與舊的ServerSocket同樣的端口上。SO_REUSEADDR選項的默認值與操作系統有關,在某些操作系統中,允許重用端口,而在某些操作系統中不允許重用端口。

當ServerSocket關閉時,如果網絡上還有發送到這個ServerSocket的數據,這個ServerSocket不會立刻釋放本地端口,而是會等待一段時間,確保接收到了網絡上發送過來的延遲數據,然后再釋放端口。

許多服務器程序都使用固定的端口。當服務器程序關閉后,有可能它的端口還會被占用一段時間,如果此時立刻在同一個主機上重啟服務器程序,由于端口已經被占用,使得服務器程序無法綁定到該端口,服務器啟動失敗,并拋出BindException:

[java]view plaincopy?

?

Exception?in?thread?"main"?java.net.BindException:?Address?already?in?use:?JVM_Bind??

為了確保一個進程關閉了ServerSocket后,即使操作系統還沒釋放端口,同一個主機上的其他進程還可以立刻重用該端口,可以調用ServerSocket的setResuse- Address(true)方法:

[java]view plaincopy?

?

if(!serverSocket.getResuseAddress())serverSocket.setResuseAddress(true);??

值得注意的是,serverSocket.setResuseAddress(true)方法必須在ServerSocket還沒有綁定到一個本地端口之前調用,否則執行serverSocket.setResuseAddress(true)方法無效。此外,兩個共用同一個端口的進程必須都調用serverSocket.setResuseAddress(true)方法,才能使得一個進程關閉ServerSocket后,另一個進程的ServerSocket還能夠立刻重用相同端口。

3.5.3? SO_RCVBUF選項

◆設置該選項:public void setReceiveBufferSize(int size) throws SocketException

◆讀取該選項:public int getReceiveBufferSize() throws SocketException

SO_RCVBUF表示服務器端的用于接收數據的緩沖區的大小,以字節為單位。一般說來,傳輸大的連續的數據塊(基于HTTP或FTP協議的數據傳輸)可以使用較大的緩沖區,這可以減少傳輸數據的次數,從而提高傳輸數據的效率。而對于交互式的通信(Telnet和網絡游戲),則應該采用小的緩沖區,確保能及時把小批量的數據發送給對方。

SO_RCVBUF的默認值與操作系統有關。例如,在Windows 2000中運行以下代碼時,顯示SO_RCVBUF的默認值為8192:

[java]view plaincopy?

?

ServerSocket?serverSocket=new?ServerSocket(8000);??

System.out.println(serverSocket.getReceiveBufferSize());//打印8192??????

無論在ServerSocket綁定到特定端口之前或之后,調用setReceiveBufferSize()方法都有效。例外情況下是如果要設置大于64K的緩沖區,則必須在ServerSocket綁定到特定端口之前進行設置才有效。例如,以下代碼把緩沖區設為128K:

[java]view plaincopy?

?

ServerSocket?serverSocket=new?ServerSocket();??

int?size=serverSocket.getReceiveBufferSize();??

if(size<131072)?serverSocket.setReceiveBufferSize(131072);??//把緩沖區的大小設為128K????????

serverSocket.bind(new?InetSocketAddress(8000));?????//與8000端口綁定??

3.5.4? 設定連接時間、延遲和帶寬的相對重要性執行serverSocket.setReceiveBufferSize()方法,相當于對所有由serverSocket.accept()方法返回的Socket設置接收數據的緩沖區的大小。

◆public void setPerformancePreferences(int connectionTime,int latency,int bandwidth)

該方法的作用與Socket的setPerformancePreferences()方法的作用相同,用于設定連接時間、延遲和帶寬的相對重要性。

轉載自http://www.51cto.com/specbook/11/40196.htm

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,606評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,582評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,540評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,028評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,801評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,223評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,294評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,442評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,976評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,800評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,996評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,543評論 5 360
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,233評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,926評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,702評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,785評論 18 139
  • JAVA套接字之TCP編程 1 TCP協議 TCP是面向諒解的協議。所謂連接,就是兩個對等實體為進行數據通信而進行...
    yanzhelee閱讀 1,165評論 0 3
  • 2.19 哀公問曰:“何為則民服。”孔子對曰:“舉直錯諸枉,則民服。舉枉錯諸直,則民不服。” 注釋: 哀公:魯君,...
    鳴斐最愛閱讀 191評論 0 0
  • 昨天中午午睡結束,迷迷糊糊間看到微信上有人加我——陳希。名字好熟哦,我同學?我同學好像叫陳夢希啊。接受之后,翻看相...
    老孟頭家有只豬閱讀 327評論 0 0