epoll原理詳解

該博客內(nèi)容大部分參考借鑒了以下博客,寫(xiě)該文章的主要目的是幫助自己梳理知識(shí)。希望也能對(duì)你有幫助

epoll原理詳解及epoll反應(yīng)堆模型
Go netpoller 網(wǎng)絡(luò)模型之源碼全面解析

設(shè)想一個(gè)場(chǎng)景:有100萬(wàn)用戶同時(shí)與一個(gè)進(jìn)程保持著TCP連接孟害,而每一時(shí)刻只有幾十個(gè)或幾百個(gè)TCP連接是活躍的(接收TCP包)袋倔,也就是說(shuō)在每一時(shí)刻進(jìn)程只需要處理這100萬(wàn)連接中的一小部分連接祠饺。那么,如何才能高效的處理這種場(chǎng)景呢邪锌?

進(jìn)程是否在每次詢問(wèn)操作系統(tǒng)收集有事件發(fā)生的TCP連接時(shí),把這100萬(wàn)個(gè)連接告訴操作系統(tǒng)癌瘾,然后由操作系統(tǒng)找出其中有事件發(fā)生的幾百個(gè)連接呢觅丰?

使用select或者poll事件驅(qū)動(dòng)方式解決

#include <sys/select.h>
 
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
 
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 
// 和 select 緊密結(jié)合的四個(gè)宏:
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);

理解 select 的關(guān)鍵在于理解 fd_set,為說(shuō)明方便妨退,取 fd_set 長(zhǎng)度為 1 字節(jié)舶胀,fd_set 中的每一 bit 可以對(duì)應(yīng)一個(gè)文件描述符 fd,則 1 字節(jié)長(zhǎng)的 fd_set 最大可以對(duì)應(yīng) 8 個(gè) fd碧注。select 的調(diào)用過(guò)程如下:

  1. 執(zhí)行 FD_ZERO(&set), 則 set 用位表示是 0000,0000
  2. 若 fd=5, 執(zhí)行 FD_SET(fd, &set); 后 set 變?yōu)?0001,0000(第 5 位置為 1)
  3. 再加入 fd=2, fd=1嚣伐,則 set 變?yōu)?0001,0011
  4. 執(zhí)行 select(6, &set, 0, 0, 0) 阻塞等待
  5. 若 fd=1, fd=2 上都發(fā)生可讀事件,則 select 返回萍丐,此時(shí) set 變?yōu)?0000,0011 (注意:沒(méi)有事件發(fā)生的 fd=5 被清空)

基于上面的調(diào)用過(guò)程轩端,可以得出 select 的特點(diǎn):

  • 可監(jiān)控的文件描述符個(gè)數(shù)取決于 sizeof(fd_set) 的值。假設(shè)服務(wù)器上 sizeof(fd_set)=512逝变,每 bit 表示一個(gè)文件描述符基茵,則服務(wù)器上支持的最大文件描述符是 512*8=4096。fd_set 的大小調(diào)整可參考 【原創(chuàng)】技術(shù)系列之 網(wǎng)絡(luò)模型(二) 中的模型 2壳影,可以有效突破 select 可監(jiān)控的文件描述符上限

  • 將 fd 加入 select 監(jiān)控集的同時(shí)拱层,還要再使用一個(gè)數(shù)據(jù)結(jié)構(gòu) array 保存放到 select 監(jiān)控集中的 fd,一是用于在 select 返回后宴咧,array 作為源數(shù)據(jù)和 fd_set 進(jìn)行 FD_ISSET 判斷根灯。二是 select 返回后會(huì)把以前加入的但并無(wú)事件發(fā)生的 fd 清空,則每次開(kāi)始 select 前都要重新從 array 取得 fd 逐一加入(FD_ZERO 最先),掃描 array 的同時(shí)取得 fd 最大值 maxfd烙肺,用于 select 的第一個(gè)參數(shù)

  • 可見(jiàn) select 模型必須在 select 前循環(huán) array(加 fd纳猪,取 maxfd),select 返回后循環(huán) array(FD_ISSET 判斷是否有事件發(fā)生)

所以桃笙,select 有如下的缺點(diǎn):

  1. 最大并發(fā)數(shù)限制:使用 32 個(gè)整數(shù)的 32 位氏堤,即 32*32=1024 來(lái)標(biāo)識(shí) fd,雖然可修改搏明,但是有以下第 2, 3 點(diǎn)的瓶頸

  2. 每次調(diào)用 select鼠锈,都需要把 fd 集合從用戶態(tài)拷貝到內(nèi)核態(tài),這個(gè)開(kāi)銷在 fd 很多時(shí)會(huì)很大

  3. 性能衰減嚴(yán)重:每次 kernel 都需要線性掃描整個(gè) fd_set星著,所以隨著監(jiān)控的描述符 fd 數(shù)量增長(zhǎng)购笆,其 I/O 性能會(huì)線性下降

poll 的實(shí)現(xiàn)和 select 非常相似,只是描述 fd 集合的方式不同强饮,poll 使用 pollfd 結(jié)構(gòu)而不是 select 的 fd_set 結(jié)構(gòu)由桌,poll 解決了最大文件描述符數(shù)量限制的問(wèn)題,但是同樣需要從用戶態(tài)拷貝所有的 fd 到內(nèi)核態(tài)邮丰,也需要線性遍歷所有的 fd 集合行您,所以它和 select 只是實(shí)現(xiàn)細(xì)節(jié)上的區(qū)分,并沒(méi)有本質(zhì)上的區(qū)別

epoll

epoll 是 Linux kernel 2.6 之后引入的新 I/O 事件驅(qū)動(dòng)技術(shù)剪廉,I/O 多路復(fù)用的核心設(shè)計(jì)是 1 個(gè)線程處理所有連接的I/O 事件娃循,這一點(diǎn)上 epoll 和 select&poll 是大同小異的。但 select&poll 錯(cuò)誤預(yù)估了一件事斗蒋,當(dāng)數(shù)十萬(wàn)并發(fā)連接存在時(shí)捌斧,可能每一毫秒只有數(shù)百個(gè)活躍的連接,同時(shí)其余數(shù)十萬(wàn)連接在這一毫秒是非活躍的泉沾。select&poll 的使用方法是這樣的:返回的活躍連接 == select(全部待監(jiān)控的連接)

什么時(shí)候會(huì)調(diào)用 select&poll 呢捞蚂?在你認(rèn)為需要找出有報(bào)文到達(dá)的活躍連接時(shí),就應(yīng)該調(diào)用跷究。所以姓迅,select&poll 在高并發(fā)時(shí)是會(huì)被頻繁調(diào)用的。這樣俊马,這個(gè)頻繁調(diào)用的方法就很有必要看看它是否有效率丁存,因?yàn)椋妮p微效率損失都會(huì)被 高頻 二字所放大柴我。它有效率損失嗎解寝?顯而易見(jiàn),全部待監(jiān)控連接是數(shù)以十萬(wàn)計(jì)的艘儒,返回的只是數(shù)百個(gè)活躍連接聋伦,這本身就是無(wú)效率的表現(xiàn)夫偶。被放大后就會(huì)發(fā)現(xiàn),處理并發(fā)上萬(wàn)個(gè)連接時(shí)嘉抓,select&poll 就完全力不從心了索守。這個(gè)時(shí)候就該 epoll 上場(chǎng)了晕窑,epoll 通過(guò)一些新的設(shè)計(jì)和優(yōu)化抑片,基本上解決了 select&poll 的問(wèn)題

int epoll_create(int size);  
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);  
  1. 調(diào)用epoll_create建立一個(gè)epoll對(duì)象(在epoll文件系統(tǒng)中給這個(gè)句柄分配資源);
  2. 調(diào)用epoll_ctl向epoll對(duì)象中添加這100萬(wàn)個(gè)連接的套接字杨赤;
  3. 調(diào)用epoll_wait收集發(fā)生事件的連接敞斋。

其中,epoll_create 創(chuàng)建一個(gè) epoll 實(shí)例并返回 epollfd疾牲;epoll_ctl 注冊(cè) file descriptor 等待的 I/O 事件(比如 EPOLLIN植捎、EPOLLOUT 等) 到 epoll 實(shí)例上;epoll_wait 則是阻塞監(jiān)聽(tīng) epoll 實(shí)例上所有的 file descriptor 的 I/O 事件阳柔,它接收一個(gè)用戶空間上的一塊內(nèi)存地址 (events 數(shù)組)焰枢,kernel 會(huì)在有 I/O 事件發(fā)生的時(shí)候把文件描述符列表復(fù)制到這塊內(nèi)存地址上,然后 epoll_wait 解除阻塞并返回舌剂,最后用戶空間上的程序就可以對(duì)相應(yīng)的 fd 進(jìn)行讀寫(xiě)了济锄。

epoll原理詳解

當(dāng)某一進(jìn)程調(diào)用epoll_create方法時(shí),Linux內(nèi)核會(huì)創(chuàng)建一個(gè)eventpoll結(jié)構(gòu)體霍转,這個(gè)結(jié)構(gòu)體中有兩個(gè)成員與epoll的使用方式密切相關(guān)荐绝,如下所示:

struct eventpoll {
  ...
  /*紅黑樹(shù)的根節(jié)點(diǎn),這棵樹(shù)中存儲(chǔ)著所有添加到epoll中的事件避消,
  也就是這個(gè)epoll監(jiān)控的事件*/
  struct rb_root rbr;
  /*雙向鏈表rdllist保存著將要通過(guò)epoll_wait返回給用戶的低滩、滿足條件的事件*/
  struct list_head rdllist;
  ...
};

我們?cè)谡{(diào)用epoll_create時(shí),內(nèi)核除了幫我們?cè)趀poll文件系統(tǒng)里建了個(gè)file結(jié)點(diǎn)岩喷,在內(nèi)核cache里建了個(gè)紅黑樹(shù)用于存儲(chǔ)以后epoll_ctl傳來(lái)的socket外恕沫,還會(huì)再建立一個(gè)rdllist雙向鏈表,用于存儲(chǔ)準(zhǔn)備就緒的事件纱意。當(dāng)epoll_wait調(diào)用時(shí)婶溯,僅僅觀察這個(gè)rdllist雙向鏈表里有沒(méi)有數(shù)據(jù)即可。有數(shù)據(jù)就返回妇穴,沒(méi)有數(shù)據(jù)就sleep爬虱,等到timeout時(shí)間到后即使鏈表沒(méi)數(shù)據(jù)也返回。所以腾它,epoll_wait非常高效跑筝。

那么rdllist雙向鏈表數(shù)據(jù)哪里來(lái)呢?所有添加到epoll中的等待事件都會(huì)與設(shè)備(如網(wǎng)卡)驅(qū)動(dòng)程序建立回調(diào)關(guān)系瞒滴,每當(dāng)相應(yīng)事件的發(fā)生時(shí)曲梗,就會(huì)調(diào)用回調(diào)方法赞警。這個(gè)回調(diào)方法在內(nèi)核中叫做ep_poll_callback,它會(huì)把該事件放到上面的rdllist雙向鏈表中虏两。

struct epitem {
  ...
  //紅黑樹(shù)節(jié)點(diǎn)
  struct rb_node rbn;
  //雙向鏈表節(jié)點(diǎn)
  struct list_head rdllink;
  //事件句柄等信息
  struct epoll_filefd ffd;
  //指向其所屬的eventepoll對(duì)象
  struct eventpoll *ep;
  //期待的事件類型
  struct epoll_event event;
  ...
}; // 這里包含每一個(gè)事件對(duì)應(yīng)著的信息愧旦。

當(dāng)調(diào)用epoll_wait檢查是否有發(fā)生事件的連接時(shí),只是檢查eventpoll對(duì)象中的rdllist雙向鏈表是否有epitem元素而已定罢,如果rdllist鏈表不為空笤虫,則這里的事件復(fù)制到用戶態(tài)內(nèi)存(使用共享內(nèi)存提高效率)中,同時(shí)將事件數(shù)量返回給用戶祖凫。因此epoll_wait效率非常高琼蚯。epoll_ctl在向epoll對(duì)象中添加、修改惠况、刪除事件時(shí)遭庶,從rbr紅黑樹(shù)中查找事件也非常快稠屠,也就是說(shuō)epoll是非常高效的峦睡,它可以輕易地處理百萬(wàn)級(jí)別的并發(fā)連接。

image.png

【總結(jié)】

  1. 執(zhí)行epoll_create()時(shí)权埠,創(chuàng)建了紅黑樹(shù)和就緒鏈表榨了;
  2. 執(zhí)行epoll_ctl()時(shí),如果增加socket句柄弊知,則檢查在紅黑樹(shù)中是否存在阻逮,存在立即返回,不存在則添加到紅黑樹(shù)干上秩彤,然后向內(nèi)核注冊(cè)回調(diào)函數(shù)叔扼,用于當(dāng)中斷事件來(lái)臨時(shí)向準(zhǔn)備就緒鏈表中插入數(shù)據(jù);
  3. 執(zhí)行epoll_wait()時(shí)立刻返回準(zhǔn)備就緒鏈表里的數(shù)據(jù)即可

epoll的兩種觸發(fā)模式

epoll有EPOLLLT和EPOLLET兩種觸發(fā)模式漫雷,LT(水平觸發(fā))是默認(rèn)的模式瓜富,ET(邊緣觸發(fā))是“高速”模式。

LT模式下降盹,內(nèi)核告訴你一個(gè)文件描述符是否就緒了与柑,然后你可以對(duì)這個(gè)就緒的fd進(jìn)行IO操作。如果你不作任何操作蓄坏,內(nèi)核還是會(huì)繼續(xù)通知你的价捧,所以,這種模式編程出錯(cuò)誤可能性要小一點(diǎn)涡戳。傳統(tǒng)的select/poll都是這種模型的代表结蟋。

ET模式下,在它檢測(cè)到有 I/O 事件時(shí)渔彰,通過(guò) epoll_wait 調(diào)用會(huì)得到有事件通知的文件描述符嵌屎,對(duì)于每一個(gè)被通知的文件描述符推正,如可讀,則必須將該文件描述符一直讀到空宝惰,讓 errno 返回 EAGAIN 為止植榕,否則下次的 epoll_wait 不會(huì)返回余下的數(shù)據(jù),會(huì)丟掉事件尼夺。如果ET模式不是非阻塞的尊残,那這個(gè)一直讀或一直寫(xiě)勢(shì)必會(huì)在最后一次阻塞。

還有一個(gè)特點(diǎn)是汞斧,epoll使用“事件”的就緒通知方式夜郁,通過(guò)epoll_ctl注冊(cè)fd什燕,一旦該fd就緒粘勒,內(nèi)核就會(huì)采用類似callback的回調(diào)機(jī)制來(lái)激活該fd,epoll_wait便可以收到通知屎即。


image.png
為什么ET模式更高效

如果采用LT模式的話庙睡,系統(tǒng)中一旦有大量你不需要讀寫(xiě)的就緒文件描述符,它們每次調(diào)用epoll_wait都會(huì)返回技俐,這樣會(huì)大大降低處理程序檢索自己關(guān)心的就緒文件描述符的效率.乘陪。而采用ET這種邊緣觸發(fā)模式的話,當(dāng)被監(jiān)控的文件描述符上有可讀寫(xiě)事件發(fā)生時(shí)雕擂,epoll_wait()會(huì)通知處理程序去讀寫(xiě)啡邑。如果這次沒(méi)有把數(shù)據(jù)全部讀寫(xiě)完(如讀寫(xiě)緩沖區(qū)太小),那么下次調(diào)用epoll_wait()時(shí)井赌,它不會(huì)通知你谤逼,也就是它只會(huì)通知你一次,直到該文件描述符上出現(xiàn)第二次可讀寫(xiě)事件才會(huì)通知你3鹚搿A鞑俊!這種模式比水平觸發(fā)效率高纹坐,系統(tǒng)不會(huì)充斥大量你不關(guān)心的就緒文件描述符枝冀。

ET模式為什么需要非阻塞socket

ET模式(邊緣觸發(fā))存在的注意點(diǎn):

1、sockfd 的邊緣觸發(fā):高并發(fā)時(shí)耘子,有多個(gè)連接同時(shí)到達(dá)果漾,服務(wù)器的 TCP 就緒隊(duì)列瞬間積累多個(gè)就緒連接,由于是邊緣觸發(fā)模式谷誓,epoll 只會(huì)通知一次绒障,accept 只處理一個(gè)連接,導(dǎo)致 TCP 就緒隊(duì)列中剩下的連接都得不到處理片林。如果沒(méi)有一次處理全部請(qǐng)求端盆,則會(huì)出現(xiàn)客戶端連接不上的問(wèn)題怀骤。不需要討論 sockfd 是否阻塞,因?yàn)閑poll_wait() 返回的必定是已經(jīng)就緒的連接焕妙,所以不管是阻塞還是非阻塞蒋伦,accept() 都會(huì)立即返回。

2焚鹊、阻塞 connfd 的邊緣觸發(fā):如果不一次性讀取一個(gè)事件上的數(shù)據(jù)痕届,會(huì)干擾下一個(gè)事件,所以必須在讀取數(shù)據(jù)的外部套一層循環(huán)末患,這樣才能完整的處理數(shù)據(jù)研叫。但是外層套循環(huán)之后會(huì)導(dǎo)致另外一個(gè)問(wèn)題:處理完數(shù)據(jù)之后,程序會(huì)一直卡在 recv() 函數(shù)上璧针,因?yàn)槭亲枞?IO嚷炉,如果沒(méi)數(shù)據(jù)可讀,它會(huì)一直等在那里探橱,直到有數(shù)據(jù)可讀申屹。但是這個(gè)時(shí)候,如果用另一個(gè)客戶端去連接服務(wù)器隧膏,服務(wù)器就不能受理這個(gè)新的客戶端了哗讥。

3、非阻塞 connfd 的邊緣觸發(fā):和阻塞版本一樣胞枕,必須在讀取數(shù)據(jù)的外部套一層循環(huán)杆煞,這樣才能完整的處理數(shù)據(jù)。因?yàn)榉亲枞?IO 如果沒(méi)有數(shù)據(jù)可讀時(shí)腐泻,會(huì)立即返回决乎,并設(shè)置 errno。這里我們根據(jù) EAGAIN 和 EWOULDBLOCK 來(lái)判斷數(shù)據(jù)是否全部讀取完畢了贫悄,如果讀取完畢瑞驱,就會(huì)正常退出循環(huán)了。

總結(jié)一下:

1窄坦、對(duì)于監(jiān)聽(tīng)的 sockfd唤反,最好使用水平觸發(fā)模式,邊緣觸發(fā)模式會(huì)導(dǎo)致高并發(fā)情況下鸭津,有的客戶端會(huì)連接不上彤侍。如果非要使用邊緣觸發(fā),可以用 while 來(lái)循環(huán) accept()逆趋。
2盏阶、對(duì)于讀寫(xiě)的 connfd,水平觸發(fā)模式下闻书,阻塞和非阻塞效果都一樣名斟,建議設(shè)置非阻塞脑慧。
3、對(duì)于讀寫(xiě)的 connfd砰盐,邊緣觸發(fā)模式下闷袒,必須使用非阻塞 IO,并要求一次性地完整讀寫(xiě)全部數(shù)據(jù)岩梳。
所以在用EPOLL的時(shí)候囊骤,我們都用fcntl將描述符置為非阻塞吧,皆大歡喜冀值。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末也物,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子列疗,更是在濱河造成了極大的恐慌滑蚯,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,826評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件作彤,死亡現(xiàn)場(chǎng)離奇詭異膘魄,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)竭讳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,968評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)浙踢,“玉大人绢慢,你說(shuō)我怎么就攤上這事÷宀ǎ” “怎么了胰舆?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,234評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蹬挤。 經(jīng)常有香客問(wèn)我缚窿,道長(zhǎng),這世上最難降的妖魔是什么焰扳? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,562評(píng)論 1 293
  • 正文 為了忘掉前任倦零,我火速辦了婚禮,結(jié)果婚禮上吨悍,老公的妹妹穿的比我還像新娘扫茅。我一直安慰自己,他們只是感情好育瓜,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,611評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布葫隙。 她就那樣靜靜地躺著,像睡著了一般躏仇。 火紅的嫁衣襯著肌膚如雪恋脚。 梳的紋絲不亂的頭發(fā)上腺办,一...
    開(kāi)封第一講書(shū)人閱讀 51,482評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音糟描,去河邊找鬼菇晃。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蚓挤,可吹牛的內(nèi)容都是我干的磺送。 我是一名探鬼主播,決...
    沈念sama閱讀 40,271評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼灿意,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼估灿!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起缤剧,我...
    開(kāi)封第一講書(shū)人閱讀 39,166評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤馅袁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后荒辕,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體汗销,經(jīng)...
    沈念sama閱讀 45,608評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,814評(píng)論 3 336
  • 正文 我和宋清朗相戀三年抵窒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了弛针。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,926評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡李皇,死狀恐怖削茁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情掉房,我是刑警寧澤茧跋,帶...
    沈念sama閱讀 35,644評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站卓囚,受9級(jí)特大地震影響瘾杭,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜哪亿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,249評(píng)論 3 329
  • 文/蒙蒙 一粥烁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锣夹,春花似錦页徐、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,866評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春搀绣,著一層夾襖步出監(jiān)牢的瞬間飞袋,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,991評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工链患, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留巧鸭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,063評(píng)論 3 370
  • 正文 我出身青樓麻捻,卻偏偏與公主長(zhǎng)得像纲仍,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子贸毕,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,871評(píng)論 2 354

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