http長(zhǎng)短連接和長(zhǎng)短輪詢、webSocket

本文主要介紹長(zhǎng)連接、短連接、長(zhǎng)輪詢、短輪詢 和 webSocket。
長(zhǎng)連接、短連接、長(zhǎng)輪詢、短輪詢是基于http的,是由客戶端主動(dòng)發(fā)起通信請(qǐng)求;
webSocket是H5新增的基于單個(gè) TCP 連接的通信方式,主要實(shí)現(xiàn)一次握手持久性連接,并能進(jìn)行雙向數(shù)據(jù)傳遞的通信方式

一、長(zhǎng)短連接

http的長(zhǎng)連接和短連接(史上最通俗!)
HTTP協(xié)議是基于請(qǐng)求/響應(yīng)模式的,因此只要服務(wù)端給了響應(yīng),本次HTTP連接就結(jié)束了,或者更準(zhǔn)確的說(shuō),是本次HTTP請(qǐng)求就結(jié)束了,根本沒(méi)有長(zhǎng)連接這一說(shuō),那么自然也就沒(méi)有短連接這一說(shuō)了;網(wǎng)上所說(shuō)的長(zhǎng)連接、短連接,本質(zhì)其實(shí)說(shuō)的是TCP連接。TCP連接是一個(gè)雙向通道,它是可以保持一段時(shí)間不關(guān)閉,因此TCP才有正真的長(zhǎng)連接、短連接。

1.1 短連接

概念:客戶端發(fā)送請(qǐng)求,服務(wù)器接收請(qǐng)求,雙方建立連接,服務(wù)器響應(yīng)資源,請(qǐng)求結(jié)束。

特性:

  • HTTP1.0協(xié)議中使用的是短連接;
  • 客戶端和服務(wù)器每進(jìn)行一次HTTP操作,就建立一次連接,任務(wù)結(jié)束就中斷連接;當(dāng)請(qǐng)求網(wǎng)頁(yè)中的css、js等靜態(tài)文件時(shí),如果使用短連接,每次都需要重新建立TCP連接,很浪費(fèi)資源;

缺點(diǎn):

  • 浪費(fèi)資源

1.2 長(zhǎng)連接

概念:客戶端發(fā)出請(qǐng)求,服務(wù)端接收請(qǐng)求,雙方建立連接,在服務(wù)端沒(méi)有返回之前保持連接,當(dāng)客戶端再發(fā)送請(qǐng)求時(shí),它會(huì)使用同一個(gè)連接。這一直繼續(xù)到客戶端或服務(wù)器端認(rèn)為會(huì)話已經(jīng)結(jié)束,其中一方中斷連接。

特性:

  • 從HTTP1.1協(xié)議以后,連接默認(rèn)都是長(zhǎng)連接;現(xiàn)如今的HTTP協(xié)議,大部分都是1.1的,因此我們平時(shí)用的基本上都是長(zhǎng)連接;
  • 長(zhǎng)連接實(shí)際指TCP長(zhǎng)連接;
  • 長(zhǎng)連接是為了復(fù)用TCP連接,多個(gè)HTTP請(qǐng)求可以復(fù)用同一個(gè)TCP連接,這就節(jié)省了很多TCP連接建立和斷開的消耗(節(jié)約時(shí)間、節(jié)省流量);
  • 客戶端和服務(wù)器都需要設(shè)置Connection: keep-alive
圖1-1 keep-alive

優(yōu)點(diǎn):

  • 減少了連接請(qǐng)求;
  • 降低TCP阻塞;
  • 減少了延遲;
  • 實(shí)時(shí)性較好;

缺點(diǎn):

  • 影響性能,因?yàn)樗谖募徽?qǐng)求之后還保持了不必要的連接很長(zhǎng)時(shí)間;

1.3 長(zhǎng)連接SSE

SSE(Server Sent Events) HTTP服務(wù)端推送詳解
Html5服務(wù)器發(fā)送事件(sse)在nodejs中的應(yīng)用
SSE(Sever-Sent Event)服務(wù)器發(fā)送事件,是HTML5新增的特性,主要用于服務(wù)器向客戶端發(fā)送數(shù)據(jù)即單雙工通信

單雙工通信:數(shù)據(jù)只能單向傳遞;
半雙工通信:數(shù)據(jù)能雙向傳遞,但是不能同時(shí)雙向傳遞;
全雙工通信:數(shù)據(jù)能夠同時(shí)雙向傳遞;

所謂的SSE,就是瀏覽器向服務(wù)器發(fā)送了一個(gè)HTTP請(qǐng)求,保持長(zhǎng)連接,服務(wù)器不斷單向地向?yàn)g覽器推送“信息”,這么做是為了節(jié)省網(wǎng)絡(luò)資源,不用一直發(fā)請(qǐng)求,建立新連接。
支持默認(rèn)3種事件,連接一旦建立就會(huì)觸發(fā)open事件,客戶端收到服務(wù)器發(fā)來(lái)的數(shù)據(jù),就會(huì)觸發(fā)message事件,如果發(fā)生通訊錯(cuò)誤(如斷開連接)就會(huì)觸發(fā)error事件。

優(yōu)點(diǎn):

  • SSE和WebSocket相比,最大的優(yōu)勢(shì)是便利,服務(wù)端不需要第三方組件,開發(fā)難度低;
  • SSE和輪詢相比不需要建立或保持大量客戶端發(fā)往服務(wù)器端的請(qǐng)求,節(jié)約了很多資源,提升應(yīng)用性能。

缺點(diǎn):

  • 如果客戶端有很多需要保持很多長(zhǎng)連接,會(huì)占用大量?jī)?nèi)存和連接數(shù);
  • 受同源策略的影響,不能跨域;
  • 有兼容問(wèn)題,IE上不支持。

實(shí)現(xiàn):
前端:

/**
  * SSE受同源策略的影響,不能跨域,此代碼在vue中是可以實(shí)現(xiàn)的。/apis是代理地址 、 /sse是接口地址
  * 支持默認(rèn)3種事件,連接一旦建立就會(huì)觸發(fā)open事件,客戶端收到服務(wù)器發(fā)來(lái)的數(shù)據(jù),就會(huì)觸發(fā)message事件,如果發(fā)生通訊錯(cuò)誤(如斷開連接)就會(huì)觸發(fā)error事件。
*/
// 判斷是否支持EventSource
if (typeof EventSource !== 'undefined') {
  // 為http://localhost:8080/apis/sse
  var source = new EventSource('/apis/sse');

  // 接受服務(wù)器發(fā)來(lái)的數(shù)據(jù)
  source.addEventListener('message', function (e) {
    console.log(e);
  });

  source.addEventListener('open', function (e) {
    console.log('連接sse');
  });

  source.addEventListener('error', function (e) {
    console.log('連接報(bào)錯(cuò)了');
  });
}

nodejs:

const http = require('http');
const SSE = require('sse');

var sseClients = [];
 
var server = http.createServer(function(req, res) {
  res.writeHead(200, {'Content-Type': 'text/plain'});
  res.end('okay');
});
server.listen(8080, '127.0.0.1', function() {
  var sse = new SSE(server, { path: '/sse', verifyRequest: (req) => {
    return true;
  }});
  sse.on('connection', function(client) {
    client.on('close', function() {
      let index = sseClients.indexOf(client);
      if (index > -1) {
        sseClients.splice(index, 1);
      }
    });
    sseClients.push(client);
    client.send('Hello world');
    client.count = 1;
    setInterval(() => {
      sseClients.forEach(function (item, index) {
        item.send(`[${sseClients.length}]服務(wù)端推送給客戶端${index} : ${item.count}`);
        item.count++;
      });
    }, 1000);
  });
});

結(jié)果:


圖1-2 SSE請(qǐng)求頭

圖1-3 SSE運(yùn)行截圖
二、長(zhǎng)短輪詢
2.1 短輪詢

概念:客戶端定時(shí)向服務(wù)器發(fā)送Ajax請(qǐng)求,服務(wù)器接到請(qǐng)求后馬上返回響應(yīng)信息并關(guān)閉連接;即在特定的的時(shí)間間隔(如每1秒),由瀏覽器對(duì)服務(wù)器發(fā)出HTTP request,然后由服務(wù)器返回最新的數(shù)據(jù)給客戶端的瀏覽器(可以理解為TCP連接不復(fù)用)。

優(yōu)點(diǎn):

  • 前后端程序編寫比較容易。

缺點(diǎn):

  • 請(qǐng)求中有大半是無(wú)用,難于維護(hù),浪費(fèi)帶寬和服務(wù)器資源;如果客戶請(qǐng)求頻繁/請(qǐng)求頭很大,將在TCP的建立和關(guān)閉操作上浪費(fèi)較多時(shí)間和帶寬;
  • 響應(yīng)的結(jié)果沒(méi)有順序(因?yàn)槭钱惒秸?qǐng)求,當(dāng)發(fā)送的請(qǐng)求沒(méi)有返回結(jié)果的時(shí)候,后面的請(qǐng)求又被發(fā)送。而此時(shí)如果后面的請(qǐng)求比前面的請(qǐng) 求要先返回結(jié)果,那么當(dāng)前面的請(qǐng)求返回結(jié)果數(shù)據(jù)時(shí)已經(jīng)是過(guò)時(shí)無(wú)效的數(shù)據(jù)了)。

應(yīng)用場(chǎng)景
傳統(tǒng)的web通信模式。后臺(tái)處理數(shù)據(jù),需要一定時(shí)間,前端想要知道后端的處理結(jié)果,就要不定時(shí)的向后端發(fā)出請(qǐng)求以獲得最新情況。

實(shí)例:
適于小型應(yīng)用。

實(shí)現(xiàn):

function requestApi(url, methed) {
  let ajax = new XMLHttpRequest();
  ajax.open(methed.toUpperCase(), url);
  ajax.setRequestHeader('Authorization', 'token');
  ajax.onreadystatechange = function () {
    if (this.readyState === 4 && this.status === 200) {
      console.log('接口數(shù)據(jù)', this.response);
    }
  }
  ajax.send();
}

// 響應(yīng)的結(jié)果沒(méi)有順序(因?yàn)槭钱惒秸?qǐng)求,當(dāng)發(fā)送的請(qǐng)求沒(méi)有返回結(jié)果的時(shí)候,后面的請(qǐng)求又被發(fā)送。而此時(shí)如果后面的請(qǐng)求比前面的請(qǐng) 求要先返回結(jié)果,那么當(dāng)前面的請(qǐng)求返回結(jié)果數(shù)據(jù)時(shí)已經(jīng)是過(guò)時(shí)無(wú)效的數(shù)據(jù)了)。
setInterval(() => {
  requestApi(`http://localhost:13666/polling?_date=${+new Date()}`, 'get');
}, 3000);

2.2 長(zhǎng)輪詢(comet)

概念:當(dāng)服務(wù)器收到客戶端發(fā)來(lái)的請(qǐng)求后,服務(wù)器端不會(huì)直接進(jìn)行響應(yīng),而是先將這個(gè)請(qǐng)求掛起,然后判斷服務(wù)器端數(shù)據(jù)是否有更新;如果有更新,則進(jìn)行響應(yīng),如果一直沒(méi)有數(shù)據(jù),則到達(dá)一定的時(shí)間限制(服務(wù)器端設(shè)置,比如nginx需要設(shè)置proxy_read_timeout 或者寫個(gè)心跳檢測(cè)的代碼)才返回, 客戶端在處理完服務(wù)器返回的信息后,再次發(fā)出請(qǐng)求,重新建立連接。

優(yōu)點(diǎn):

  • 在無(wú)消息的情況下不會(huì)頻繁的請(qǐng)求,耗費(fèi)資源小。

缺點(diǎn):

  • 請(qǐng)求掛起同樣會(huì)導(dǎo)致資源的浪費(fèi)。

應(yīng)用場(chǎng)景:
數(shù)據(jù)實(shí)時(shí)更新。

實(shí)例
WebQQ、Hi網(wǎng)頁(yè)版、Facebook IM。

實(shí)現(xiàn):

function requestApi(url, methed) {
  let ajax = new XMLHttpRequest();
  ajax.open(methed.toUpperCase(), url);
  ajax.setRequestHeader('Authorization', 'token');
  ajax.onreadystatechange = function () {
    requestApi(`http://localhost:13666/polling?_date=${+new Date()}`, 'get');
  }
  ajax.send();
}

requestApi(`http://localhost:13666/polling?_date=${+new Date()}`, 'get');
三、webSocket

webSocket是一種網(wǎng)絡(luò)通信協(xié)議,是 HTML5 開始提供的一種在單個(gè) TCP 連接上進(jìn)行全雙工通訊的協(xié)議。
瀏覽器和服務(wù)器只需要完成一次握手,兩者之間就直接可以創(chuàng)建持久性的連接,并進(jìn)行雙向數(shù)據(jù)傳輸。

3.1 為什么需要websocket

websocket深入淺出

  • 因?yàn)?HTTP 協(xié)議有一個(gè)缺陷:通信只能由客戶端發(fā)起;
  • 我們都知道輪詢的效率低,非常浪費(fèi)資源(因?yàn)楸仨毑煌_B接,或者 HTTP 連接始終打開), 因此websocket應(yīng)運(yùn)而生。


    圖3-1 websocket簡(jiǎn)介
3.2 實(shí)現(xiàn)

前端:

const webSocket = new WebSocket('ws://localhost:13666/test1/1');
webSocket.onopen = () => {
  alert('open:連接成功');
  webSocket.send('hello world');
};
webSocket.onmessage = (e) => {
  console.log('后端推送', e);
};
webSocket.onclose = (e) => {
  console.log('關(guān)閉連接', e);
};
webSocket.onerror = (e) => {
  console.log('連接失敗', e);
};

后端(nodejs):
app.js文件:

const express = require("express");
const app = express();
const PORT = 13666;

const webSocket = require('./routes/polling/webSocket');

// 將指定目錄下的文件對(duì)外開放  http://localhost:13666/test.jpg就可以訪問(wèn)到public下的文件了
app.use(express.static('public'));


// app.all() 用于在所有HTTP 請(qǐng)求方法的路徑上加載中間件函數(shù), 所有的路由都會(huì)走這
app.all('*', (req, res, next) => {
  // 設(shè)置跨域訪問(wèn)
  res.header("Access-Control-Allow-Origin", "*");
  /**
   * 解決跨域
   * 包含自定義header字段的跨域請(qǐng)求,瀏覽器會(huì)先向服務(wù)器發(fā)送OPTIONS請(qǐng)求,探測(cè)該服務(wù)器是否允許自定義的跨域字段。如果允許,則繼續(xù)實(shí)際的POST/GET正常請(qǐng)求,否則,返回標(biāo)題所示錯(cuò)誤。
   * 若報(bào)跨域:...by CORS policy: Request header field range is not allowed by Access-Control-Allow-Headers in preflight response,只需在響應(yīng)頭中包含該字段即可(加入range)
  */
  res.header("Access-Control-Allow-Headers", "content-type,x-requested-with,Authorization,x-ui-request,lang,accept,access-control-allow-origin,range");
  res.header("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
  res.header("X-Powered-By", ' 3.2.1');
  res.header("Content-Type", "application/json;charset=utf-8");

  // 所有接口都會(huì)走這,所以可以添加全局處理方法,比如過(guò)濾器
  console.log('哈哈哈哈這里可以添加過(guò)濾器哦~');
  next();
});

/**
 * 基本路由,也可以使用router -- https://www.expressjs.com.cn/guide/routing.html
 * app.method(url, cbList)
 * method - 方法:get、post、put、delete
 * cbList - 回調(diào)函數(shù),可以是個(gè)數(shù)組/函數(shù),使用next就會(huì)調(diào)用下一個(gè)回調(diào);參數(shù)req、res、next
*/

const httpServe = app.listen(PORT, () => {
  console.log('\033[;32m expressService listening at http://localhost:' + PORT + '\033[0m');
});

webSocket(httpServe);

webSocket.js文件:

/*********************************************************************
 * express 實(shí)現(xiàn) webSocket
 * 注意點(diǎn):
 * 1、send只能發(fā)送 字符串/buffer,由于接收到的是Buffer,所以如果需要傳對(duì)象,需要先將buffer轉(zhuǎn)字符串,再使用JSON.stringify;
 * 2、使用nodejs-websocket包,webSocket和http不能使用一個(gè)端口,會(huì)報(bào)“Error: listen EADDRINUSE: address already in use :::13666”,所以改用ws;
 * 3、使用ws共用一個(gè)端口是根據(jù)請(qǐng)求頭中 Connection:Upgrade 和 Upgrade:websocket 這兩個(gè)字段確認(rèn)是否是webSocket;
 *    webSocket和http共用一個(gè)端口: https://blog.csdn.net/qq_44856695/article/details/120250286
 *********************************************************************/

const WS = require('ws');

// 不區(qū)分地址 - 即ws://localhost:13666 和 ws://localhost:13666/test都能訪問(wèn)到
const bindWs = (httpServer) => {
  const ws = new WS.Server({server: httpServer});
  ws.on('connection', (connect) => wsConnect(connect, '不區(qū)分地址'))
}

// ws連接 - 發(fā)送消息/關(guān)閉連接
const wsConnect = (connect, type) => {
  connect.on('message', (str) => {
    // send只能發(fā)送字符串/buffer,接收到的str是buffer類型,使用toString轉(zhuǎn)成字符串
    connect.send(JSON.stringify({type, data: str.toString()}));
    setTimeout(() => {
      // 服務(wù)端主動(dòng)關(guān)閉連接
      connect.close();
    }, 3000);
  });
  connect.on('close', (code, reason) => {
    console.log('關(guān)閉連接了', code, reason);
  })
}

// 區(qū)分地址 - 即只有 ws://localhost:13666/test/:id  和 ws://localhost:13666/test1/:id 能訪問(wèn),獲取id:req.url.match(/\/\d/g)[0].slice(1)
const bindWss = (httpServer) => {
  // ws://localhost:13666/test/:id 使用的ws
  const ws = new WS.Server({noServer: true});
  ws.on('connection', (connect) => wsConnect(connect, 'first'));

  // ws://localhost:13666/test1/:id 使用的ws
  const ws1 = new WS.Server({noServer: true});
  ws1.on('connection', (connect) => wsConnect(connect, 'second'));

  httpServer.on('upgrade', (req, socket, head) => {
    if (req.url.startsWith('/test/')) {
      ws.handleUpgrade(req, socket, head, (connect) => {
        ws.emit('connection', connect, req);
      });
    } else if (req.url.startsWith('/test1/')) {
      ws1.handleUpgrade(req, socket, head, (connect) => {
        ws1.emit('connection', connect, req);
      });
    } else {
      console.log('ws接口不存在');
      socket.destroy();
    }
  })
}
module.exports = function(httpServer) {
  bindWss(httpServer);
}
3.3 SSE和webSocket區(qū)別
    1. WebSocket是全雙工通道,可以雙向通信,功能更強(qiáng);SSE是單向通道,只能服務(wù)器向客戶端發(fā)送;
    1. WebSocket是一個(gè)新的協(xié)議,需要服務(wù)器端支持;SSE則是部署在 HTTP協(xié)議之上的,現(xiàn)有的服務(wù)器軟件都支持;
    1. SSE是一個(gè)輕量級(jí)協(xié)議,相對(duì)簡(jiǎn)單;WebSocket是一種較重的協(xié)議,相對(duì)復(fù)雜;
    1. SSE默認(rèn)支持?jǐn)嗑€重連,WebSocket則需要額外部署;
    1. SSE支持自定義發(fā)送的數(shù)據(jù)類型,webSocket只能發(fā)送字符串/buffer;
    1. SSE不支持CORS,參數(shù)url就是服務(wù)器網(wǎng)址,必須與當(dāng)前網(wǎng)頁(yè)的網(wǎng)址在同一個(gè)網(wǎng)域(domain),而且協(xié)議和端口都必須相同;WebSocket支持跨域。
3.4 webSocket優(yōu)點(diǎn)
圖3-2 webSocket優(yōu)點(diǎn)
四、通信技術(shù)比較

從兼容性角度考慮,短輪詢 > 長(zhǎng)輪詢 > 長(zhǎng)連接SSE > WebSocket
從性能方面考慮,WebSocket > 長(zhǎng)連接SSE > 長(zhǎng)輪詢 > 短輪詢

參考文章

http的長(zhǎng)連接和短連接(史上最通俗!)
長(zhǎng)連接、短連接、長(zhǎng)輪詢和WebSocket
SSE(Server Sent Events) HTTP服務(wù)端推送詳解
Html5服務(wù)器發(fā)送事件(sse)在nodejs中的應(yīng)用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,055評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評(píng)論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,346評(píng)論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,889評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評(píng)論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,118評(píng)論 0 286
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,637評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,558評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,739評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評(píng)論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 43,980評(píng)論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評(píng)論 1 280
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,347評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,702評(píng)論 2 370