java 網(wǎng)絡(luò)編程

  • java.io中最為核心的一個概念是流(Stream),面向流的編程谎倔。java中,一個流要么是輸入流猿推,要么是輸出流片习,不可能既是輸入流又是輸出流。
  • Socket又稱"套接字"蹬叭,應(yīng)用程序通常通過"套接字"向網(wǎng)絡(luò)發(fā)出請求或者應(yīng)答網(wǎng)絡(luò)請求藕咏。
    Socket和ServerSocket類庫位于java.net包中。ServerSocket用于服務(wù)器端秽五,Socket是創(chuàng)建網(wǎng)絡(luò)連接時使用的孽查。在連接成功時,應(yīng)用程序兩端都會產(chǎn)生一個Socket實列坦喘,操作這個實例盲再,完成所需的會話。對于一個網(wǎng)絡(luò)連接來說瓣铣,套接字是平等的答朋,
    不因為在服務(wù)器端或在客戶端而產(chǎn)生不同的級別。不管是Socket還是ServerSocket它們的工作都是通過SocketImpl類及其子類完成的棠笑。
  • 套接字之間的連接過程可以分為四個步驟:服務(wù)器監(jiān)聽梦碗,客戶端請求服務(wù)器,服務(wù)器確認(rèn),客戶端確認(rèn)洪规,進(jìn)行通信印屁。
  • 服務(wù)器監(jiān)聽:是服務(wù)器端套接字并不定位具體的客戶端套接字,而是處于等待連接的狀態(tài)斩例,實時監(jiān)控網(wǎng)絡(luò)狀態(tài)库车。
  • 客戶端請求:是指由客戶端的套接字提出連接請求,要連接的目標(biāo)是服務(wù)器端的套接字樱拴。為此,客戶端的套接字必須首先描述它要連接的服務(wù)器的套接字洋满,指出服務(wù)器端套接字的地址和端口號晶乔,然后就向服務(wù)器端套接字提出連接請求。
  • 服務(wù)器端連接確認(rèn):是指當(dāng)服務(wù)器端套接字監(jiān)聽到或者說接收到客戶端套接字的連接請求牺勾,它就響應(yīng)客戶端套接字的請求正罢,建立一個新的線程,把服務(wù)器段套接字的描述發(fā)給客戶端驻民。
  • 客戶端連接確認(rèn):一旦客戶端確認(rèn)了此描述翻具,連接就建立好了。雙方開始進(jìn)行通信回还。而服務(wù)器端套接字繼續(xù)處于監(jiān)聽狀態(tài)裆泳,繼續(xù)接收其他客戶端套接字的連接請求。

簡單的demo

客戶端:

public static void main(String[] args) {

        Socket socket = null;
        BufferedReader in = null;
        PrintWriter out = null;

        try {
            socket = new Socket("localhost", 8899);
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(), true);

            //向服務(wù)器端發(fā)送數(shù)據(jù)
            out.println("hi server柠硕!");
            out.println("我是客戶端");
            String response = in.readLine();
            System.out.println("Client: " + response);

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}

服務(wù)器端:

public class Server {
    public static void main(String[] args) {

        ServerSocket server = null;
        try {
            server = new ServerSocket(8899);
            System.out.println("server start .. ");
            //進(jìn)行阻塞
            Socket socket = server.accept();
            //新建一個線程執(zhí)行客戶端的任務(wù)
            new Thread(new ServerHandler(socket)).start();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(server != null){
                try {
                    server.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            server = null;
        }
    }
}

服務(wù)端handler:

public class ServerHandler implements Runnable{

    private Socket socket ;

    public ServerHandler(Socket socket){
        this.socket = socket;
    }

    @Override
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
            out = new PrintWriter(this.socket.getOutputStream(), true);
            String body;
            while(true){
                body = in.readLine();
                if(body == null) break;
                System.out.println("Server :" + body);
                out.println("服務(wù)器端回送響的應(yīng)數(shù)據(jù).");
            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(in != null){
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if(out != null){
                try {
                    out.close();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            if(socket != null){
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            socket = null;
        }
    }
}
BIO

弊端

  • 第一工禾,只能同時處理一個連接,要管理多個并發(fā)客戶端蝗柔,需要為每個新的客戶端Socket創(chuàng)建一個Thread闻葵。在這種情況下,任何時候都可能有大量的線程處于休眠狀態(tài)癣丧,只是等待輸入或者輸出數(shù)據(jù)就緒槽畔,這算是一種資源浪費(fèi)。
  • 第二胁编,需要為每個線程的調(diào)用棧都分配內(nèi)存厢钧,其默認(rèn)值大小區(qū)間為64kb到1Mb,具體取決于操作系統(tǒng)掏呼。
  • 即使java虛擬機(jī)(jvm)在物理上可以支持非常大數(shù)量的線程坏快,但是遠(yuǎn)在達(dá)到該極限之前,上下文切換所帶來的開銷就會帶來麻煩憎夷,例如莽鸿,在達(dá)到10000個連接的時候。

1.1.1 Java NIO

  • NIO開始的時候是新的輸入/輸出(New Input/Output)的英文縮寫,也可以被稱作非阻塞(Non-blocking I/O)祥得。
  • 阻塞:應(yīng)用程序在獲取網(wǎng)絡(luò)數(shù)據(jù)的時候兔沃,如果網(wǎng)絡(luò)傳輸數(shù)據(jù)很慢,那么程序就一直等著级及,直到傳輸完畢為止乒疏。
  • 非阻塞:應(yīng)用程序直接可以獲取已經(jīng)準(zhǔn)備就緒好的數(shù)據(jù),無需等待饮焦。
NIO架構(gòu)圖
  • 我們都是把數(shù)據(jù)從Channel讀到Buffer中怕吴,然后再從Buffer中讀到程序中,絕對不可能數(shù)據(jù)從Channel直接讀到程序中县踢。
    IO中一個流不可能既是輸入流又是輸出流转绷,Channel把數(shù)據(jù)讀到Buffer,buffer將數(shù)據(jù)讀到程序中硼啤,程序也可以將數(shù)據(jù)寫到buffer中议经。

  • 除了數(shù)組之外,Buffer還提供了對于數(shù)據(jù)的結(jié)構(gòu)化訪問方式谴返,并且可以追蹤到系統(tǒng)的讀寫過程煞肾。

  • Java中的7種原生數(shù)據(jù)類型都有各自對應(yīng)的Buffer類型,如IntBuffer嗓袱,LongBuffer籍救,ByteBuffer,CharBuffer等等,并沒有BooleanBuffer類型渠抹。

  • Channel指的是可以向其寫入數(shù)據(jù)或是從中讀取數(shù)據(jù)的對象钧忽,它類似于java.io中的stream。

  • 所有數(shù)據(jù)的讀寫都是通過Buffer來進(jìn)行的逼肯,永遠(yuǎn)不會出現(xiàn)直接向Channel寫入數(shù)據(jù)的情況耸黑,或是直接從Channel讀取數(shù)據(jù)的情況。

  • 與Stream不同的是篮幢,Channel是雙向的大刊,一個流只可能是InputStream或是OutputStream,Channel打開后則可以進(jìn)行讀寫三椿,寫入或是讀寫缺菌。

  • 由于Channel是雙向的,因此它能更好的反映出底層操作系統(tǒng)的真實情況搜锰,在Linux系統(tǒng)中伴郁,底層操作系統(tǒng)的通道就是雙向的。

NIO三個最重要的組件
Buffer(緩沖區(qū))蛋叼,Channer(管道焊傅,通道) Selector(選擇器剂陡,多路復(fù)用器)

  • Buffer:Buffer是一個對象,它包含一些要寫入或者要讀取的數(shù)據(jù)狐胎。在NIO類庫中加入Buffer對象鸭栖,體現(xiàn)了NIO與原IO的一個重要的區(qū)別。在面向流的IO中握巢,可以將數(shù)據(jù)直接寫入或讀取到Stream對象中晕鹊。在NIO庫中,所有數(shù)據(jù)都是用緩存區(qū)處理的(讀寫)暴浦。緩沖區(qū)實質(zhì)上是一個數(shù)組溅话,通常它是一個字節(jié)數(shù)組(ByteBuffer),也可以使用其它類型的數(shù)組歌焦。這個數(shù)組為緩沖區(qū)提供了數(shù)組的訪問讀寫等操作屬性公荧,如position,capacity同规,limit等概念。
    Buffer類型:我們最常用的就是ByteBuffer窟社,實際上每一種java基本類型都對于了一種緩存區(qū)(除了Boolean型),ByteBuffer,CharBuffer,ShortBuffer,IntBuffer
    ,LongBuffer,FloatBuffer,DoubleBuffer

  • Channel:
    通道(Channel)券勺,網(wǎng)絡(luò)數(shù)據(jù)通過Channel讀取和寫入,通道與流不同之處在于通道是雙向的灿里,而流只是一個方向上移動(一個流必須是InputStream或者OutputStream的子類)关炼,而通道可以用于讀,寫或者二者同時進(jìn)行匣吊,最關(guān)鍵的是可以與多路復(fù)用器結(jié)合起來儒拂,有多種的狀態(tài)位,方便多路復(fù)用器去識別色鸳。事實上通道分為兩大類社痛,一類是網(wǎng)絡(luò)讀寫的(SelectableChannel),一類是用于文件操作的(FileChannel),我們使用的SocketChannel和ServerSocketChannel都是SelectableChannel的子類命雀。

  • Selector:

  • 多路復(fù)用器(Selector)蒜哀,他是NIO編程的基礎(chǔ),非常重要吏砂。多路復(fù)用器提供選擇已經(jīng)就緒的任務(wù)的能力撵儿。

  • 簡單來說,就是Selector會不斷的輪詢注冊在其上的通道(Channel),如果某個通道發(fā)生了讀寫操作狐血,這個通道就處于就緒狀態(tài)淀歇,會被Selector輪詢出來,然后通過SelectKey可以取得就緒的Channel集合匈织,從而進(jìn)行后續(xù)的IO操作浪默。

  • 一個多路復(fù)用器(Selector)可以負(fù)責(zé)成千上萬Channel通道,沒有上限,這也是JDK使用了epoll代替了傳統(tǒng)的select實現(xiàn)浴鸿,獲得連接句柄沒有限制井氢。這也就意味著我們只要一個線程負(fù)責(zé)Selector的輪詢,就可以接入成千上萬個客戶端岳链,這是JDK NIO庫的巨大進(jìn)步花竞。

  • Selector線程就類似一個管理者(Master),管理了成千上萬個管理掸哑,然后輪詢那個通道的數(shù)據(jù)已經(jīng)準(zhǔn)備好约急,通知cpu執(zhí)行io的讀取或?qū)懭氩僮鳌?/p>

  • Selector模式:當(dāng)IO事件(管道)注冊到選擇器以后,selector會分配給每個管道一個key值苗分,相當(dāng)于標(biāo)簽厌蔽。selector選擇器是以輪詢的方式進(jìn)行查找注冊的所有IO事件(管道),當(dāng)我們的IO時間(管道)準(zhǔn)備就緒后摔癣,select就會識別奴饮,會通過key值來找到相應(yīng)的管道,進(jìn)行相關(guān)的數(shù)據(jù)處理操作(從管道里讀或?qū)憯?shù)據(jù)择浊,寫到我們的數(shù)據(jù)緩沖區(qū)中)戴卜。

Demo1

了解一下Buffer概念

import java.nio.IntBuffer;
import java.security.SecureRandom;

public class NioTest1 {
    public static void main(String[] args) {
        //IntBuffer緩沖區(qū)分配10個長度
        IntBuffer buffer = IntBuffer.allocate(10);

        //將數(shù)據(jù)寫到buffer中
        for(int i=0;i<buffer.capacity();i++){
            int randomNumber = new SecureRandom().nextInt(20);
            buffer.put(randomNumber);
        }

        //使用flip實現(xiàn)讀寫的切換
        buffer.flip();

        //將buffer數(shù)據(jù)讀出來
        while (buffer.hasRemaining()){
            System.out.println(buffer.get());
        }
    }
}

Demo2

管道與Buffer結(jié)合使用,我們都是將數(shù)據(jù)從Channel讀到Buffer中(涉及到Buffer就是寫動作)琢岩,然后Buffer讀到程序中投剥,絕對不可能數(shù)據(jù)直接從Channel直接讀到程序中。

import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class NioTest2 {
    public static void main(String[] args) throws Exception{
        FileInputStream fileInputStream = new FileInputStream("NioTest2.txt");
        FileChannel fileChannel = fileInputStream.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(512);
        fileChannel.read(byteBuffer);

        //定義程序的反轉(zhuǎn)担孔,由讀到寫
        byteBuffer.flip();

        while(byteBuffer.remaining() > 0){
            byte b = byteBuffer.get();
            System.out.println("Character: "+(char)b);
        }

        fileInputStream.close();
    }
}

Demo3

import java.nio.IntBuffer;
import java.security.SecureRandom;

public class NioTest1 {
    public static void main(String[] args) {
        //IntBuffer緩沖區(qū)分配10個長度
        IntBuffer buffer = IntBuffer.allocate(10);

        System.out.println("capacity:"+buffer.capacity());

        //將數(shù)據(jù)寫到buffer中
        for(int i=0;i<5;i++){
            int randomNumber = new SecureRandom().nextInt(20);
            buffer.put(randomNumber);
        }

        System.out.println("before flip limit: "+buffer.limit());  //10

        //使用flip實現(xiàn)讀寫的切換
        buffer.flip();

        System.out.println("after flip limit: "+buffer.limit()); //5

        System.out.println("enter while loop");

        //將buffer數(shù)據(jù)讀出來
        while (buffer.hasRemaining()){
            System.out.println("position: "+buffer.position());
            System.out.println("limit: "+buffer.limit()); //5
            System.out.println("capacity: "+buffer.capacity()); //10
            System.out.println(buffer.get());
        }
    }
}

關(guān)于NIO Buffer中的3個重要狀態(tài)屬性的含義:position江锨,limit與capacity。flip方法為什么在讀寫轉(zhuǎn)換的時候調(diào)用糕篇。

java的api對其的描述:
A buffer is a linear, finite sequence of elements of a specific primitive type. Aside from its content, the essential properties of a buffer are its capacity, limit, and position:
一個buffer是線性啄育,有限的特性原生類型的序列。除了其內(nèi)容外拌消,一個buffer最基礎(chǔ)的屬性是capacity灸撰,limit,和position:

A buffer's capacity is the number of elements it contains. The capacity of a buffer is never negative and never changes.
一個buffer的capacity就是其包含的元素的數(shù)量拼坎。一個buffer的capacity不可能是負(fù)數(shù)并且不會被改變浮毯。

A buffer's limit is the index of the first element that should not be read or written. A buffer's limit is never negative and is never greater than its capacity.
一個buffer的limit指的是無法再去讀或者寫的下一個元素的索引。一個buffer的limit不可能是負(fù)數(shù)并且不可能大于它的capacity泰鸡。

A buffer's position is the index of the next element to be read or written. A buffer's position is never negative and is never greater than its limit.
一個buffer的position是下個能被讀或者寫的元素的索引债蓝。一個buffer的position不可能是負(fù)數(shù)并且不可能大于它的limit。

定義一個容量是6的buffer
讀寫過程中capacity盛龄,limit饰迹,position位置的變化
  • 調(diào)用buffer.flip()方法設(shè)置了當(dāng)前的limit的值等于當(dāng)前的position芳誓,并且將position的值設(shè)為0.
  • 調(diào)用buffer.clear()方法設(shè)置limit的值等于capacity并且position的值設(shè)為0。

Demo4

寫一個關(guān)于三個組件都使用的程序

public class NioTest4 {
    public static void main(String[] args) throws Exception{
        int[] ports = new int[5];

        ports[0]= 5000;
        ports[1]= 5001;
        ports[2]= 5002;
        ports[3]= 5003;
        ports[4]= 5004;

        //Selector對象最常見的構(gòu)建方式
        Selector selector = Selector.open();

        for (int i = 0; i <ports.length ; i++) {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            //設(shè)置channel為非阻塞模式
            serverSocketChannel.configureBlocking(false);
            //得到與channel相關(guān)聯(lián)的Socket
            ServerSocket serverSocket = serverSocketChannel.socket();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(ports[i]);
            serverSocket.bind(inetSocketAddress);

            //將channel注冊到selector中啊鸭,并且監(jiān)聽的是接收連接事件锹淌,這邊只能監(jiān)聽客戶端連接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

            System.out.println("監(jiān)聽端口:" + ports[i]);
        }


        while(true){
            int number = selector.select();
            System.out.println("number: "+number);

            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            System.out.println("selectedKeys: "+selectionKeys);
            Iterator<SelectionKey> iterator = selectionKeys.iterator();

            while(iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();

                //接收到連接,我們將selector注冊到一個個的ServerChannel當(dāng)中赠制,我們可以拿到每一個與之連接的Channel
                if(selectionKey.isAcceptable()){
                    ServerSocketChannel serverSocketChannel = (ServerSocketChannel)selectionKey.channel();
                    //得到連接這個channel的SocketChannel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    socketChannel.configureBlocking(false);

                    //向selector注冊可讀事件
                    socketChannel.register(selector,SelectionKey.OP_READ);

                    //處理完selectkey之后一定要remove掉
                    iterator.remove();

                    System.out.println("獲得客戶端的連接: "+socketChannel);
                }else if(selectionKey.isReadable()){
                    SocketChannel socketChannel= (SocketChannel)selectionKey.channel();

                    int bytesRead = 0;

                    while(true){
                        ByteBuffer byteBuffer = ByteBuffer.allocate(512);

                        byteBuffer.clear();

                        int read = socketChannel.read(byteBuffer);

                        if(read <= 0){
                            break;
                        }

                        byteBuffer.flip();

                        socketChannel.write(byteBuffer);

                        bytesRead +=read;
                    }

                    System.out.println("讀嚷赴凇: "+ bytesRead+",來自于: "+socketChannel);

                    iterator.remove();
                }
            }

        }
    }
}

通過nc localhost 5050nc localhost 5051钟些,nc localhost 5052等可以去測試.

Selector維護(hù)著三個set集合:
第一個就是所有當(dāng)前注冊到selector上的key的集合烟号。這個set可以通過調(diào)用keys()方法來獲得。
第二個就是確定的一種感興趣的集合比如連接的動作政恍,可讀可寫的動作汪拥,這個set是通過selectedKeys方法返回的,這個已經(jīng)被選擇的key集合總是上面的keys()方法返回的子集篙耗。
第三個就是取消的key的集合是已經(jīng)被取消的集合但是其channels還沒有被取消注冊迫筑。這個set集合是不能直接訪問的。這個已經(jīng)取消的key的集合也是上面的key set集合的一個子集宗弯。一般取消的集合都要在當(dāng)前的遍歷中刪除掉脯燃。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市罕伯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叽讳,老刑警劉巖追他,帶你破解...
    沈念sama閱讀 212,080評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異岛蚤,居然都是意外死亡邑狸,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,422評論 3 385
  • 文/潘曉璐 我一進(jìn)店門涤妒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來单雾,“玉大人,你說我怎么就攤上這事她紫」瓒眩” “怎么了?”我有些...
    開封第一講書人閱讀 157,630評論 0 348
  • 文/不壞的土叔 我叫張陵贿讹,是天一觀的道長渐逃。 經(jīng)常有香客問我,道長民褂,這世上最難降的妖魔是什么茄菊? 我笑而不...
    開封第一講書人閱讀 56,554評論 1 284
  • 正文 為了忘掉前任疯潭,我火速辦了婚禮,結(jié)果婚禮上面殖,老公的妹妹穿的比我還像新娘竖哩。我一直安慰自己,他們只是感情好脊僚,可當(dāng)我...
    茶點故事閱讀 65,662評論 6 386
  • 文/花漫 我一把揭開白布相叁。 她就那樣靜靜地躺著,像睡著了一般吃挑。 火紅的嫁衣襯著肌膚如雪钝荡。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,856評論 1 290
  • 那天舶衬,我揣著相機(jī)與錄音埠通,去河邊找鬼。 笑死逛犹,一個胖子當(dāng)著我的面吹牛端辱,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播虽画,決...
    沈念sama閱讀 39,014評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼舞蔽,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了码撰?” 一聲冷哼從身側(cè)響起渗柿,我...
    開封第一講書人閱讀 37,752評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎脖岛,沒想到半個月后朵栖,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,212評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡柴梆,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,541評論 2 327
  • 正文 我和宋清朗相戀三年陨溅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片绍在。...
    茶點故事閱讀 38,687評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡门扇,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出偿渡,到底是詐尸還是另有隱情臼寄,我是刑警寧澤,帶...
    沈念sama閱讀 34,347評論 4 331
  • 正文 年R本政府宣布溜宽,位于F島的核電站脯厨,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏坑质。R本人自食惡果不足惜合武,卻給世界環(huán)境...
    茶點故事閱讀 39,973評論 3 315
  • 文/蒙蒙 一临梗、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧稼跳,春花似錦盟庞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,777評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至红淡,卻和暖如春不狮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背在旱。 一陣腳步聲響...
    開封第一講書人閱讀 32,006評論 1 266
  • 我被黑心中介騙來泰國打工摇零, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桶蝎。 一個月前我還...
    沈念sama閱讀 46,406評論 2 360
  • 正文 我出身青樓驻仅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親登渣。 傳聞我的和親對象是個殘疾皇子貌矿,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,576評論 2 349

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