【網絡編程技術】基本套接字編程TCP

套接字基礎

套接字類型

套接字存在于特定的通信協(xié)議(地址族)中幸海,只有類屬于同一地址族的套接字才能建立通信劫哼,套接字支持多種通信協(xié)議:

AF_LOCAL:Unix 系統(tǒng)本地通信

AF_INET:IP版本4

AF_INET6:IP版本6

Linux支持多種套接字類型,套接字類型:是指創(chuàng)建套接字的應用程序所希望的通信服務類型班眯。

SOCKET_STREAM:雙向可靠數(shù)據流,流式套接字,對應TCP

SOCKET_DGRAM:雙向不可靠數(shù)據報蓖柔,數(shù)據包套接字,對應UDP

SOCKET_RAW:是低于傳輸層的低級協(xié)議或物理網絡直接訪問风纠,可以訪問內部網絡接口况鸣。原始套接字,例如接收和發(fā)送ICMP報竹观。

套接字地址結構(IPv4)

struct sockaddr_in{

? unsigned short int sin_len;? /* IPv4地址長度 */

? short int sin_family; /* 地址類型 */

? unsigned short int sin_port; /* 存儲端口號 */

? struct in_addr sin_addr;? /*存儲IP地址 */

? unsigned char sin_zero[8];? /* 空字節(jié) */

}镐捧;


sin_family指代協(xié)議族,在TCP套接字編程中只能是AF_INET;

sin_zero是為了讓sockaddr與sockaddr_in兩個數(shù)據結構保持大小相同而保留的空字節(jié)臭增。

sin_port存儲端口號(使用網絡字節(jié)順序)懂酱,數(shù)據類型是一個16位的無符號整數(shù)類型;

sin_addr存儲IP地址誊抛,IP地址使用in_addr這個數(shù)據結構:

? struct in_addr{? unsigned long s_addr;? };

這個數(shù)據結構是由于歷史原因保留下來列牺,主要用作與以前的格式兼容。這里的s_addr按照網絡字節(jié)順序存儲IP地址拗窃。



設置地址信息的實例(IPv4)

struct sockaddr_in mysock; /*設置sockaddr_in的結構體變量mysock */

mysock.sin_family=AF_INET; /*地址族*/

mysock.sin_port=htons(3490); /*short,NBO*/

mysock.sin_addr.s_addr=inet_addr(“192.168.1.221”); /*設置地址為192.168.1.221*/

bzero(&(mysock.sin_zero),8); /*設置sin_zero為8位保留字節(jié)*/

注意:如果mysock.sin_addr.s_addr=INADDR_ANY,則不指定IP地址(用于server程序)瞎领。



字節(jié)排序函數(shù)

網絡中存在多種類型的機器,這些不同類型的機器表示數(shù)據的字節(jié)順序是不同的随夸。網絡協(xié)議中的數(shù)據采用統(tǒng)一的網絡字節(jié)順序九默,因為只有采用統(tǒng)一的字節(jié)順序,才能在不同類型的硬件設備之間正確的發(fā)送和接收數(shù)據逃魄。廣域網規(guī)定的網絡字節(jié)順序采用大端字節(jié)順序方式荤西。

系統(tǒng)提供4個函數(shù)來進行字節(jié)順序轉換:

#include “netinet/in.h”

unsigned short int htons(unsigned short int hostshort);

unsigned long int htonl(unsigned long int hostlong);

unsigned short int ntons(unsigned short int netshort);

unsigned long int ntonl(unsigned long int netlong);

h:主機? n:網絡? s:短整數(shù)? l:長整數(shù)

其中。前兩個函數(shù)將主機字節(jié)順序轉換成網絡字節(jié)順序;后兩個函數(shù)將網絡字節(jié)順序轉換成主機字節(jié)順序邪锌。

在使用這些函數(shù)時勉躺,我們不關心主機或網絡順序的真實值到底是大端還是小端,只需要調用適當?shù)暮瘮?shù)來對給定值(函數(shù)的整型參數(shù))進行主機字節(jié)順序和網絡字節(jié)順序的轉換觅丰,它們的返回值就是經過轉換以后的結果饵溅。

字節(jié)操縱函數(shù)

系統(tǒng)提供兩組函數(shù)來處理多字節(jié)數(shù)據,一組函數(shù)是以b(byte)開頭妇萄,和BSD系統(tǒng)兼容的函數(shù)蜕企;另一組是以mem開頭,ANSI C所提供的函數(shù)冠句。

#include <string.h>

void bzero(void *dest, size_t nbytes);

void bcopy(const void *src, void *dest, size_t nbytes);

int? bcmp(const void *src, void *dest, size_t nbytes); /*返回0則相同轻掩,非0不相同*/

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 上述三個函數(shù)源自BSD

void *memset(void *dest, int c, size_t len);

void *memcpy(void *dest, const void *src, size_t nbytes);

int? ? memcmp(const void *ptr1, const void *ptr2, size_t nbytes)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?上述三個函數(shù)屬于ANSI C



memcpy函數(shù)等同于bcopy,差別是bcopy可以處理源src和目標dest相重疊的情況懦底,而memcpy則對這種情況沒有定義唇牧。

memset函數(shù)將參數(shù)s指定的內存區(qū)域的前n個字節(jié)設置為參數(shù)c的內容;

bcmp比較任意兩個內存區(qū)域聚唐,即s1指定的內存區(qū)域與s2指定的內存區(qū)域的前n個字節(jié)丐重,若相同則返回值為0签钩,否則返回值為非0

bzero函數(shù)將目標中指定數(shù)目的字節(jié)置為0弧关,這個函數(shù)經常用來把套接字地址結構初始化為0蒜田,如:

bzero(&servaddr,sizeof(servaddr));

bcopy將指定數(shù)目的字節(jié)從源src移動到目標dest指定的內存區(qū)域荒椭;



地址轉換函數(shù)

地址轉換函數(shù)負責在ASCII字符串和網絡字節(jié)順序的二進制值之間進行地址轉換蒋得。

inet_aton,inet_addr和inet_ntoa函數(shù)

#include <arpa/inet.h>

int inet_aton(const char *strptr,struct in_addr *addrptr);

in_addr_t inet_addr(const char *strptr);

inet_aton函數(shù)將strptr所指向的字符串轉換成32位的網絡字節(jié)序二進制值忆嗜,并存儲在指針addrptr指向的in_addr結構體中州胳,若成功衷笋,返回1客峭。

inet_addr函數(shù)纳猪,其轉換結果作為返回值返回32位二進制網絡字節(jié)序地址,若轉換錯桃笙,則返回INADDR_NONE氏堤。

inet_addr進行相同的轉換,但不進行有效性驗證搏明,當IP地址是255.255.255.255時鼠锈,會認為這是個無效的IP地址,但對于目前大部分的路由器上星著,這個IP都是有效的购笆。

inet_aton函數(shù)將tcp所指的字符串(點分十進制數(shù)串,如192.168.0.1)轉換成32位的網絡字節(jié)序二進制虚循,并通過指針addrptr來存儲同欠。這個函數(shù)需要對字符串所指的地址進行有效性驗證样傍。但如果strptr為空,函數(shù)仍然成功铺遂,但不存儲任何結果衫哥。

char *inet_ntoa(struct in_addr inaddr);

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 返回:指向點分十進制數(shù)串的指針

函數(shù)inet_ntoa將32位的網絡字節(jié)序二進制IPv4地址轉換成相應的點分十進制數(shù)串。但由于返回值所指向的串留在靜態(tài)內存中襟锐,這意味著函數(shù)是不可重入的撤逢。

需要注意的是這個函數(shù)是以結構為參數(shù)的,而不是指針粮坞。

上述三個地址轉換函數(shù)都只能處理IPv4協(xié)議蚊荣,而不能處理IPv6地址。

Tcp套接字



TCP套接字實現(xiàn)過程

服務器端步驟

1.創(chuàng)建套接字

2..綁定套接字

3..設置套接字為監(jiān)聽模式莫杈,進入被動接受連接請求狀態(tài)

4..接受請求互例,建立連接

5.讀/寫數(shù)據

6.終止連接

客戶端步驟

1.創(chuàng)建套接字

2.與遠程服務程序連接

3.讀/寫數(shù)據

5.終止連接

基本套接字函數(shù) - socket

socket實質上提供了進程通信的端點。進程通信之前筝闹,雙方首先必須各自創(chuàng)建一個端點敲霍,否則是沒有辦法建立聯(lián)系并相互通信的。正如打電話之前丁存,雙方必須各自擁有一臺電話機一樣。在網間網內部柴我,每一個socket用一個半相關描述:

(協(xié)議解寝,本地地址,本地端口)

一個完整的socket有一個本地唯一的socket號艘儒,由操作系統(tǒng)分配聋伦。

對于基于TCP的通信,無論是服務器還是客戶界睁,都必須首先產生其TCP通信傳輸端點觉增,即TCP套接字。

應用程序通過調用socket()產生套接字翻斟。該函數(shù)調用必須給出所使用的地址簇逾礁、套接字類型和協(xié)議標志。該函數(shù)返回一個套接字描述符访惜。

由于系統(tǒng)中套接字也是一種文件嘹履,所以套接字描述符可以看成是一種文件描述符。之后的任何I/O操作都是作用于該套接字描述符债热。其數(shù)據結構包括一個網絡連接的5種信息:通信協(xié)議砾嫉、本地協(xié)議地址、本機主機端口窒篱、遠程主機地址和遠程協(xié)議端口焕刮。

socket函數(shù):為了執(zhí)行網絡輸入輸出舶沿,一個進程必須做的第一件事就是調用socket函數(shù)獲得一個文件描述符。


第一個參數(shù)指明了協(xié)議簇配并,目前支持5種協(xié)議簇括荡,最常用的有AF_INET(IPv4協(xié)議)和AF_INET6(IPv6協(xié)議);第二個參數(shù)指明套接口類型荐绝,有三種類型可選:SOCK_STREAM(字節(jié)流套接口)一汽、SOCK_DGRAM(數(shù)據報套接口)和SOCK_RAW(原始套接口);如果套接口類型不是原始套接口低滩,那么第三個參數(shù)就為0召夹。


基本套接字函數(shù)-bind

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_len len)

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? 返回:0-成功;-1-出錯并置errno

該函數(shù)指明套接字將使用本地的哪一個協(xié)議端口進行數(shù)據傳送(IP地址和端口號)恕沫,注意:協(xié)議地址addr是通用地址监憎。

Len是該地址結構(第二個參數(shù))的長度。

一般而言婶溯,服務器調用此函數(shù)鲸阔,而客戶則很少調用它。

因為:客戶端是主動向服務器發(fā)出請求的迄委,客戶開始發(fā)送數(shù)據褐筛,系統(tǒng)就給客戶端分配一個隨機的端口,這個端口和客戶端的IP會隨著數(shù)據一起發(fā)給服務器叙身,服務器就可以從中或得客戶的IP和端口渔扎,接下來服務器就可以利用獲得的IP和端口給客戶端回應消息。

bind函數(shù)用法


SO_REUSEADDR:允許套接口和一個已在使用中得地址綁定信轿。一般缺省條件下一個套接口不能與一個已在使用中的本地地址綁定晃痴,但有時會需要“重用”地址。僅在bind()調用時該選項才被解釋财忽。

setsockopt函數(shù)?


返回:0-OK倘核;-1-出錯。sockfd必須指向一個打開的套接字描述字即彪。Level是選項所在的層及協(xié)議紧唱,有如下值: SOL_SOCKET (通用套接字 ) IPPROTO_TCP(傳輸層,TCP協(xié)議) IPPROTO_IP(網際層隶校,IP協(xié)議)Optname是所要操作的選項名琼蚯。(SO_、IP_惠况、TCP_是選項名的前綴遭庶,不同的level有不同的前綴。)optval是一個指向變量的指針稠屠,通過它設置選項的新值峦睡,此變量的大小由最后一個參數(shù)指定翎苫。

該函數(shù)用于任意類型、任意狀態(tài)套接口的設置選項值榨了,盡管在不同協(xié)議層上存在選項煎谍,但本函數(shù)定義了最高的“套接口”層次上得選項。選項影響套接口的操作龙屉,諸如:廣播數(shù)據是否可以從套接口發(fā)送等等呐粘。

基本套接字函數(shù)-listen

listen函數(shù):listen函數(shù)僅被TCP服務器調用,它的作用是將用sock創(chuàng)建的主動套接口轉換成被動套接口转捕,并等待來自客戶端的連接請求作岖。


第一個參數(shù)是socket函數(shù)返回的套接口描述字;第二個參數(shù)規(guī)定了內核為此套接口排隊的最大連接個數(shù)五芝。由于listen函數(shù)第二個參數(shù)的原因痘儡,內核要維護兩個隊列:已完成連接隊列和未完成連接隊列。未完成隊列中存放的是TCP連接的三路握手為完成的連接枢步,accept函數(shù)是從已連接隊列中取連接返回給進程沉删;當以連接隊列為空時,進程將進入睡眠狀態(tài)醉途。

基本套接字函數(shù)-close

#include <unistd.h>

int close(int sockfd);

? ? ? ? ? ? ? ? ? 返回:0-OK矾瑰;-1-出錯;

close函數(shù)缺省功能是將套接字做上“已關閉”標記隘擎,并立即返回到進程殴穴。這個套接字不能再為該進程所用。

正常情況下嵌屎,close將引發(fā)向TCP的四分節(jié)終止序列,但在終止前將發(fā)送已排隊的數(shù)據恍涂;

如果套接字描述符訪問計數(shù)在調用close后大于0(在多個進程共享同一個套接字的情況下)宝惰,則不會引發(fā)TCP終止序列(即不會發(fā)送FIN分節(jié));

基本套接字函數(shù)-shutdown

#include <sys/socket.h>

int shutdown(int sockfd, int howto);

? ? ? ? ? ? ? ? ? 返回:0-OK再沧;-1-出錯尼夺,并置相應的errno的值;

該函數(shù)立即發(fā)送FIN分節(jié)(無論其訪問計數(shù)是否大于0)炒瘸。shutdown根據參數(shù)howto關閉指定方向的數(shù)據傳輸淤堵;

SHUT_RD:關閉連接的讀這一半,不再接收套接字中的數(shù)據且現(xiàn)留在接收緩沖區(qū)的數(shù)據作廢顷扩;

SHUT_WR :關閉連接的寫這一半(半關閉)拐邪,當留在套接字發(fā)送緩沖區(qū)中的數(shù)據都被發(fā)送,后跟tcp連接終止序列隘截,不管訪問計數(shù)是否大于0扎阶;此后將不能在執(zhí)行對套接字的任何寫操作汹胃;

SHUT_RDWR:連接的讀、寫都關閉东臀,這等效于調用shutdown兩次着饥,一次調用是用SHUT_RD,第二次用SHUT_WR惰赋。

基本套接字函數(shù)-read

#include <unistd.h>

int read(int fd, char *buf, int len);

返回:大于0-讀寫字節(jié)大性椎簟;-1-出錯赁濒;

調用函數(shù)read時轨奄,有如下幾種情況:

套接字接收緩沖區(qū)接收數(shù)據,返回接收到的字節(jié)數(shù)流部;

tcp協(xié)議收到FIN數(shù)據戚绕,返回0;

tcp協(xié)議收到RST數(shù)據枝冀,返回-1舞丛,同時errno為ECONNRESET;

進程阻塞過程中接收到信號果漾,返回-1球切,同時errno為EINTR。

read(connfd绒障,buff吨凑,strlen(buff));

基本套接字函數(shù)-write

#include <unistd.h>

int write(int fd, char *buf, int len);

? ? ? ? ? ? ? ? ? ? 返回:大于0-讀寫字節(jié)大小户辱;-1-出錯鸵钝;

調用函數(shù)write,有如下幾種情況:

套接字發(fā)送緩沖區(qū)有足夠空間庐镐,返回發(fā)送的字節(jié)數(shù)恩商;

tcp協(xié)議接收到RST數(shù)據,返回-1必逆,同時errno為ECONNRESET怠堪; ;

進程阻塞過程中接收到信號名眉,返回-1粟矿,同時errno為EINTR。

write(connfd损拢,buff陌粹,strlen(buff));

數(shù)據傳輸函數(shù)-send

#include <sys/types.h>

#include <sys/socket.h>

ssize_t send (int fd, const void *msg, size_t len, int flags);

? ? ? 返回:非0-發(fā)送成功的數(shù)據長度;-1-出錯福压;

flags 是傳輸控制標志申屹,其值定義如下:

0:常規(guī)操作绘证,如同write()函數(shù)

MSG_OOB,發(fā)送帶外數(shù)據(TCP緊急數(shù)據)哗讥。

MSG_DONTROUTE:忽略底層協(xié)議的路由設置嚷那,只能將數(shù)據發(fā)送給與發(fā)送機處在同一個網絡中的機器上。

數(shù)據傳輸函數(shù)-recv

#include <sys/types.h>

#include <sys/socket.h>

ssize_t recv(int fd, void *buf ,size_t len, int flags);

? ? ? ? ? ? 返回:大于0表示成功接收的數(shù)據長度杆煞;0: 對方已關閉魏宽,-1:出錯。

flags是傳輸控制標志决乎,其值定義如下:

0:常規(guī)操作队询,如同read()函數(shù);

MSG_PEEK:只查看數(shù)據而不讀出數(shù)據构诚,后續(xù)讀操作仍然能讀出所查看的該數(shù)據蚌斩;

MSG_OOB:忽略常規(guī)數(shù)據,而只讀帶外數(shù)據范嘱;

MSG_WAITALL:recv函數(shù)只有在將接收緩沖區(qū)填滿后才返回送膳。

域名解析函數(shù)-gethostbyname

#include <netdb.h>

struct hostent *gethostbyname(const char *hostname)

? ? 返回:非空指針-成功;空指針-出錯丑蛤,同時設置h_error

該函數(shù)既可解析IPv4地址叠聋,也可解析IPv6地址;

該函數(shù)既可接收域名受裹,也可接收點分十進制參數(shù)

當hostname為點分十進制時碌补,函數(shù)并不執(zhí)行網絡查詢,而是直接將其拷貝到結果字段中棉饶。

此函數(shù)返回的非空指針指向下面的hostent結構



TCP服務器模板



TCP客戶模板





UDP套接字

實現(xiàn)UDP套接字基本步驟分為服務器端和客戶端兩部分:

服務器端

1.建立UDP套接字厦章;

2.綁定套接字到特定地址;

3.等待并接收客戶端信息照藻;

4.處理客戶端請求袜啃;

5.發(fā)送信息回客戶端;

6.關閉套接字岩梳;

客戶端步驟

1.建立UDP套接字囊骤;

2.發(fā)送信息給服務器晃择;

3.接收來自服務器的信息冀值;

4.關閉套接字

UDP數(shù)據傳輸函數(shù)-sendto

#include <sys/types.h>

#include <sys/socket.h>

ssize_t sendto(int sockfd, const void *msg, size_t len, int flags, const struct sockaddr *to, int tolen);

? ? ? ? 返回:大于0-成功發(fā)送數(shù)據長度;-1-出錯宫屠;

UDP套接字使用無連接協(xié)議列疗,因此必須使用sendto函數(shù),指明目的地址浪蹂;

flags是傳輸控制標志抵栈,其值定義如下:

0:常規(guī)操作告材,如同write()函數(shù);

MSG_OOB:發(fā)送帶外數(shù)據古劲;

MSG_DONTROUTE:忽略底層路由協(xié)議斥赋,直接發(fā)送。

UDP數(shù)據傳輸函數(shù)-recvfrom

#include <sys/types.h>

#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *from, int *fromlen);

? ? ? ? ? 返回:大于0-成功接收數(shù)據長度产艾;-1-出錯疤剑;

UDP套接字使用無連接協(xié)議,因此必須使用recvfrom函數(shù)闷堡,指明源地址隘膘;

flags是傳輸控制標志,其值定義如下:

0:常規(guī)操作杠览,如同read()函數(shù)弯菊;

MSG_PEEK:只察看數(shù)據而不讀出數(shù)據;

MSG_OOB:忽略常規(guī)數(shù)據踱阿,而只讀取帶外數(shù)據管钳;

from 和 fromlen 是“值-結果”參數(shù)。

UDP服務器模板



UDP客戶模板


?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末扫茅,一起剝皮案震驚了整個濱河市蹋嵌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌葫隙,老刑警劉巖栽烂,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恋脚,居然都是意外死亡腺办,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門糟描,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怀喉,“玉大人,你說我怎么就攤上這事船响」#” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵见间,是天一觀的道長聊闯。 經常有香客問我,道長米诉,這世上最難降的妖魔是什么菱蔬? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上拴泌,老公的妹妹穿的比我還像新娘魏身。我一直安慰自己,他們只是感情好蚪腐,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布箭昵。 她就那樣靜靜地躺著,像睡著了一般回季。 火紅的嫁衣襯著肌膚如雪宙枷。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天茧跋,我揣著相機與錄音慰丛,去河邊找鬼。 笑死瘾杭,一個胖子當著我的面吹牛诅病,可吹牛的內容都是我干的。 我是一名探鬼主播粥烁,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼贤笆,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了讨阻?” 一聲冷哼從身側響起芥永,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎钝吮,沒想到半個月后埋涧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡奇瘦,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年棘催,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片耳标。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡醇坝,死狀恐怖,靈堂內的尸體忽然破棺而出次坡,到底是詐尸還是另有隱情呼猪,我是刑警寧澤,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布砸琅,位于F島的核電站宋距,受9級特大地震影響,放射性物質發(fā)生泄漏明棍。R本人自食惡果不足惜乡革,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一寇僧、第九天 我趴在偏房一處隱蔽的房頂上張望摊腋。 院中可真熱鬧沸版,春花似錦、人聲如沸兴蒸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽橙凳。三九已至蕾殴,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間岛啸,已是汗流浹背钓觉。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坚踩,地道東北人荡灾。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像瞬铸,于是被迫代替她去往敵國和親批幌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內容