Socket搭建即時通訊服務器

webSecket

即時通訊

  • 相關代碼Demo地址, 內(nèi)附服務端代碼和iOS端聊天室測試Demo
  • 原文地址: Socket搭建即時通訊服務器
  • 即時通訊(Instant messaging,簡稱IM)是一個終端服務,允許兩人或多人使用網(wǎng)路即時的傳遞文字訊息、檔案、語音與視頻交流
  • 即時通訊按使用用途分為企業(yè)即時通訊和網(wǎng)站即時通訊
  • 根據(jù)裝載的對象又可分為手機即時通訊和PC即時通訊,手機即時通訊代表是短信,網(wǎng)站、視頻即時通訊

IM通信原理

  • 客戶端A與客戶端B如何產(chǎn)生通信?客戶端A不能直接和客戶端B,因為兩者相距太遠。
  • 這時就需要通過IM服務器,讓兩者產(chǎn)生通信.
  • 客戶端A通過socket與IM服務器產(chǎn)生連接,客戶端B也通過socket與IM服務器產(chǎn)生連接
  • A先把信息發(fā)送給IM應用服務器,并且指定發(fā)送給B,服務器根據(jù)A信息中描述的接收者將它轉(zhuǎn)發(fā)給B,同樣B到A也是這樣。
  • 通訊問題: 服務器是不能主動連接客戶端的,只能客戶端主動連接服務器

即時通訊連接原理

  • 即時通訊都是長連接,基本上都是HTTP1.1協(xié)議,設置Connectionkeep-alive即可實現(xiàn)長連接,而HTTP1.1默認是長連接,也就是默認Connection的值就是keep-alive
  • HTTP分為長連接和短連接,其實本質(zhì)上是TCP連接,HTTP協(xié)議是應用層的協(xié)議,而TCP才是真正的傳輸層協(xié)議, IP是網(wǎng)絡層協(xié)議,只有負責傳輸?shù)倪@一層才需要建立連接
  • 例如: 急送一個快遞,HTTP協(xié)議指的那個快遞單,你寄件的時候填的單子就像是發(fā)了一個HTTP請求。而TCP協(xié)議就是中間運貨的運輸工具,它是負責運輸?shù)模\輸工具所行駛的路就是所謂的TCP連接
  • HTTP短連接(非持久連接)是指,客戶端和服務端進行一次HTTP請求/響應之后,就關閉連接。所以,下一次的HTTP請求/響應操作就需要重新建立連接。
  • HTTP長連接(持久連接)是指,客戶端和服務端建立一次連接之后,可以在這條連接上進行多次請求/響應操作。持久連接可以設置過期時間,也可以不設置

即時通訊數(shù)據(jù)傳遞方式

目前實現(xiàn)即時通訊的有四種方式(短輪詢、長輪詢、SSE、Websocket

短輪詢:

  • 每隔一小段時間就發(fā)送一個請求到服務器,服務器返回最新數(shù)據(jù),然后客戶端根據(jù)獲得的數(shù)據(jù)來更新界面,這樣就間接實現(xiàn)了即時通信
  • 優(yōu)點是簡單,缺點是對服務器壓力較大,浪費帶寬流量(通常情況下數(shù)據(jù)都是沒有發(fā)生改變的)。
  • 主要是客戶端人員寫代碼,服務器人員比較簡單,適于小型應用

長輪詢:

  • 客戶端發(fā)送一個請求到服務器,服務器查看客戶端請求的數(shù)據(jù)(服務器中數(shù)據(jù))是否發(fā)生了變化(是否有最新數(shù)據(jù)),如果發(fā)生變化則立即響應返回,否則保持這個連接并定期檢查最新數(shù)據(jù),直到發(fā)生了數(shù)據(jù)更新或連接超時
  • 同時客戶端連接一旦斷開,則再次發(fā)出請求,這樣在相同時間內(nèi)大大減少了客戶端請求服務器的次數(shù).
  • 弊端:服務器長時間連接會消耗資源,返回數(shù)據(jù)順序無保證,難于管理維護
  • 底層實現(xiàn):在服務器的程序中加入一個死循環(huán),在循環(huán)中監(jiān)測數(shù)據(jù)的變動。當發(fā)現(xiàn)新數(shù)據(jù)時,立即將其輸出給瀏覽器并斷開連接,瀏覽器在收到數(shù)據(jù)后,再次發(fā)起請求以進入下一個周期

SSE

  • Server-sent Events服務器推送事件):為了解決瀏覽器只能夠單向傳輸數(shù)據(jù)到服務端,HTML5提供了一種新的技術叫做服務器推送事件SSE
  • SSE技術提供的是從服務器單向推送數(shù)據(jù)給瀏覽器的功能,加上配合瀏覽器主動HTTP請求,兩者結(jié)合起來,實際上就實現(xiàn)了客戶端和服務器的雙向通信.

WebSocket

  • 以上提到的這些解決方案中,都是利用瀏覽器單向請求服務器或者服務器單向推送數(shù)據(jù)到瀏覽器
  • 而在HTML5中,為了加強web的功能,提供了websocket技術,它不僅是一種web通信方式,也是一種應用層協(xié)議
  • 它提供了瀏覽器和服務器之間原生的全雙工跨域通信,通過瀏覽器和服務器之間建立websocket連接,在同一時刻能夠?qū)崿F(xiàn)客戶端到服務器和服務器到客戶端的數(shù)據(jù)發(fā)送

WebSocket

  • WebSocket 是一種網(wǎng)絡通信協(xié)議。RFC6455 定義了它的通信標準
  • WebSocket是一種雙向通信協(xié)議,在建立連接后,WebSocket 服務器和客戶端都能主動的向?qū)Ψ桨l(fā)送或接收數(shù)據(jù)
  • WebSocket是基于HTTP協(xié)議的,或者說借用了HTTP協(xié)議來完成一部分握手(連接),在握手(連接)階段與HTTP是相同的,只不過HTTP不能服務器給客戶端推送,而WebSocket可以

WebSocket如何工作

  • Web瀏覽器和服務器都必須實現(xiàn)WebSockets協(xié)議來建立和維護連接。
  • 由于WebSockets連接長期存在,與典型的HTTP連接不同,對服務器有重要的影響
  • 基于多線程或多進程的服務器無法適用于 WebSockets,因為它旨在打開連接,盡可能快地處理請求,然后關閉連接
  • 任何實際的WebSockets服務器端實現(xiàn)都需要一個異步服務器
webServer

Websocket協(xié)議

協(xié)議頭: ws, 服務器根據(jù)協(xié)議頭判斷是Http還是websocket

// 請求頭
     GET ws://localhost:12345/websocket/test.html HTTP/1.1
     Origin: http://localhost
     Connection: Upgrade
     Host: localhost:12345
     Sec-WebSocket-Key: JspZdPxs9MrWCt3j6h7KdQ==  
     Upgrade: websocket 
     Sec-WebSocket-Version: 13
    // Sec-WebSocket-Key: 叫“夢幻字符串”是個密鑰,只有有這個密鑰 服務器才能通過解碼認出來,這是個WB的請求,要建立TCP連接了!!!如果這個字符串沒有按照加密規(guī)則加密,那服務端就認不出來,就會認為這整個協(xié)議就是個HTTP請求。更不會開TCP。其他的字段都可以隨便設置,但是這個字段是最重要的字段,標識WB協(xié)議的一個字段
     

// 響應頭
     HTTP/1.1 101 Web Socket Protocol Handshake
     WebSocket-Location: ws://localhost:12345/websocket/test.php
     Connection: Upgrade
     Upgrade: websocket
     Sec-WebSocket-Accept: zUyzbJdkVJjhhu8KiAUCDmHtY/o= 
     WebSocket-Origin: http://localhost
     
    // Sec-WebSocket-Accept: 叫“夢幻字符串”,和上面那個夢幻字符串作用一樣。不同的是,這個字符串是要讓客戶端辨認的,客戶端拿到后自動解碼。并且辨認是不是一個WB請求。然后進行相應的操作。這個字段也是重中之重,不可隨便修改的。加密規(guī)則,依然是有規(guī)則的

WebSocket客戶端

在客戶端,沒有必要為WebSockets使用JavaScript庫。實現(xiàn)WebSocketsWeb 瀏覽器將通過WebSockets對象公開所有必需的客戶端功能(主要指支持HTML5的瀏覽器)

客戶端 API

以下 API 用于創(chuàng)建WebSocket對象。

var Socket = new WebSocket(url, [protocol] );
  • 以上代碼中的第一個參數(shù)url, 指定連接的URL
  • 第二個參數(shù)protocol是可選的,指定了可接受的子協(xié)議

WebSocket屬性

以下是WebSocket對象的屬性。假定我們使用了以上代碼創(chuàng)建了Socket對象

  • Socket.readyState: 只讀屬性readyState表示連接狀態(tài), 可以是以下值
    • 0 : 表示連接尚未建立
    • 1 : 表示連接已建立,可以進行通信
    • 2 : 表示連接正在進行關閉
    • 3 : 表示連接已經(jīng)關閉或者連接不能打開。
  • Socket.bufferedAmount: 只讀屬性bufferedAmount
    • 表示已被send() 放入正在隊列中等待傳輸,但是還沒有發(fā)出的UTF-8文本字節(jié)數(shù)

WebSocket事件

以下是WebSocket對象的相關事件。假定我們使用了以上代碼創(chuàng)建了Socket 對象:

事件 事件處理程序 描述
open Socket.onopen 連接建立時觸發(fā)
message Socket.onmessage 客戶端接收服務端數(shù)據(jù)時觸發(fā)
error Socket.onerror 通信發(fā)生錯誤時觸發(fā)
close Socket.onclose 連接關閉時觸發(fā)

WebSocket方法

以下是WebSocket對象的相關方法。假定我們使用了以上代碼創(chuàng)建了Socket對象:

方法 描述
Socket.send() 使用連接發(fā)送數(shù)據(jù)
Socket.close() 關閉連接

示例

// 客戶端
var socket = new WebSocket("ws://localhost:9090")

// 建立 web socket 連接成功觸發(fā)事件
socket.onopen = function () {
    // 使用send發(fā)送數(shù)據(jù)
    socket.send("發(fā)送數(shù)據(jù)")
    console.log(socket.bufferedAmount)
    alert('數(shù)據(jù)發(fā)送中')
}

// 接受服務端數(shù)據(jù)是觸發(fā)事件
socket.onmessage = function (evt) {
    var received_msg = evt.data
    alert('數(shù)據(jù)已經(jīng)接受..')
}

// 斷開 websocket 連接成功觸發(fā)事件
socket.onclose = function () {
    alert('鏈接已經(jīng)關閉')
    console.log(socket.readyState)
}

WebSocket服務端

WebSocket在服務端的實現(xiàn)非常豐富。Node.jsJavaC++Python 等多種語言都有自己的解決方案, 其中Node.js常用的有以下三種

下面就著重研究一下Socket.IO吧, 因為別的我也不會, 哈哈哈哈......

Socket.IO

  • Socket.IO是一個庫,可以在瀏覽器和服務器之間實現(xiàn)實時,雙向和基于事件的通信
  • Socket.IO是一個完全由JavaScript實現(xiàn)、基于Node.js、支持WebSocket的協(xié)議用于實時通信、跨平臺的開源框架
  • Socket.IO包括了客戶端(iOS,Android)和服務器端(Node.js)的代碼,可以很好的實現(xiàn)iOS即時通訊技術
  • Socket.IO支持及時、雙向、基于事件的交流,可在不同平臺、瀏覽器、設備上工作,可靠性和速度穩(wěn)定
  • Socket.IO實際上是WebSocket的父集,Socket.io封裝了WebSocket和輪詢等方法,會根據(jù)情況選擇方法來進行通訊
  • 典型的應用場景如:
    • 實時分析:將數(shù)據(jù)推送到客戶端,客戶端表現(xiàn)為實時計數(shù)器、圖表、日志客戶
    • 實時通訊:聊天應用
    • 二進制流傳輸:socket.io支持任何形式的二進制文件傳輸,例如圖片、視頻、音頻等
    • 文檔合并:允許多個用戶同時編輯一個文檔,并能夠看到每個用戶做出的修改

Socket.IO服務端

  • Socket.IO實質(zhì)是一個庫, 所以在使用之前必須先導入Socket.IO
  • Node.js導入庫和iOS導入第三方庫性質(zhì)一樣, 只不過iOS使用的是pods管理, Node.js使用npm

導入Socket.IO

// 1. 進入當當前文件夾
cd ...

// 2. 創(chuàng)建package.json文件
npm init

/// 3. 導入庫
npm install socket.io --sava
npm install express --sava

創(chuàng)建socket

  • socket本質(zhì)還是http協(xié)議,所以需要綁定http服務器,才能啟動socket服務.
  • 而且需要通過web服務器監(jiān)聽端口,socket不能監(jiān)聽端口,有人訪問端口才能建立連接,所以先創(chuàng)建web服務器
// 引入http模塊
var http = require('http')

// 面向express框架開發(fā),加載express框架,方便處理get,post請求
var express = require('express')

// 創(chuàng)建web服務器
var server = http.Server(express)

// 引入socket.io模塊
var socketio = require('socket.io')

// 創(chuàng)建愛你socket服務器
var serverSocket = socketio(server)


server.listen(9090)
console.log('監(jiān)聽9090')

建立socket連接

  • 服務器不需要主動建立連接,建立連接是客戶端的事情,服務器只需要監(jiān)聽連接
  • 客戶端主動連接會發(fā)送connection事件,服務端只需要監(jiān)聽connection事件有沒有發(fā)送,就知道客戶端有沒有主動連接服務器
  • Socket.IO本質(zhì)是通過發(fā)送和接受事件觸發(fā)服務器和客戶端之間的通訊,任何能被編輯成JSON或二進制的對象都可以傳遞
  • socket.on: 監(jiān)聽事件,這個方法會有兩個參數(shù),第一個參數(shù)是事件名稱,第二個參數(shù)是監(jiān)聽事件的回調(diào)函數(shù),監(jiān)聽到鏈接就會執(zhí)行這個回調(diào)函數(shù)
  • 監(jiān)聽connection,回調(diào)函數(shù)會傳入一個連接好的socket,這個socket就是客戶端的socket
  • socket連接原理,就是客戶端和服務端通過socket連接,服務器有socket,客戶端也有
// 監(jiān)聽客戶端有沒有連接成功,如果連接成功,服務端會發(fā)送connection事件,通知客戶端連接成功
// serverSocket: 服務端, clientSocket: 客戶端
serverSocket.on('connection', function (clientSocket) {
    // 建立socket連接成功
    console.log('建立連接成功')

    console.log(clientSocket)
})

Socket.IO客戶端

創(chuàng)建socket對象

創(chuàng)建SocketIOClient對象, 兩種創(chuàng)建方式

// 第一種, SocketIOClientConfiguration: 可選參數(shù)
public init(socketURL: URL, config: SocketIOClientConfiguration = [])

// 第二種, 底層還是使用的第一種方式創(chuàng)建
public convenience init(socketURL: URL, config: [String: Any]?) {
        self.init(socketURL: socketURL, config: config?.toSocketConfiguration() ?? [])
}
  • SocketIOClientConfiguration: 是一個數(shù)組, 等同于[SocketIOClientOption]
  • SocketIOClientOption的所有取值如下
public enum SocketIOClientOption : ClientOption {
    /// 使用壓縮的方式進行傳輸
    case compress
    /// 通過字典內(nèi)容連接
    case connectParams([String: Any])
    /// NSHTTPCookies的數(shù)組, 在握手過程中傳遞, Default is nil.
    case cookies([HTTPCookie])
    /// 添加自定義請求頭初始化來請求, 默認為nil
    case extraHeaders([String: String])
    /// 將為每個連接創(chuàng)建一個新的connect, 如果你在重新連接有bug時使用.
    case forceNew(Bool)
    /// 傳輸是否使用HTTP長輪詢, 默認false
    case forcePolling(Bool)
    /// 是否使用 WebSockets. Default is `false`
    case forceWebsockets(Bool)
    /// 調(diào)度handle的運行隊列, 默認在主隊列
    case handleQueue(DispatchQueue)
    /// 是否打印調(diào)試信息. Default is false
    case log(Bool)
    /// 可自定義SocketLogger調(diào)試日志
    case logger(SocketLogger)
    /// 自定義服務器使用的路徑.
    case path(String)
    /// 鏈接失敗時, 是否重新鏈接, Default is `true`
    case reconnects(Bool)
    /// 重新連接多少次. Default is `-1` (無限次)
    case reconnectAttempts(Int)
    /// 等待重連時間. Default is `10`
    case reconnectWait(Int)
    /// 是否使用安全傳輸, Default is false
    case secure(Bool)
    /// 設置允許那些證書有效
    case security(SSLSecurity)
    /// 自簽名只能用于開發(fā)模式
    case selfSigned(Bool)
    /// NSURLSessionDelegate 底層引擎設置. 如果你需要處理自簽名證書. Default is nil.
    case sessionDelegate(URLSessionDelegate)
}

創(chuàng)建SocketIOClient

// 注意協(xié)議:ws開頭
guard let url = URL(string: "ws://localhost:9090") else { return }
let manager = SocketManager(socketURL: url, config: [.log(true), .compress])
// SocketIOClient
let socket = manager.defaultSocket

監(jiān)聽連接

  • 創(chuàng)建好socket對象,然后連接用connect方法
  • 因為socket需要進行3次握手,不可能馬上建議連接,需要監(jiān)聽是否連接成功的回調(diào),使用on方法
  • ON方法兩個參數(shù)
    • 參數(shù)一: 監(jiān)聽的事件名稱,參數(shù)二:監(jiān)聽事件回調(diào)函數(shù),會自動調(diào)用
    • 回調(diào)函數(shù)也有兩個參數(shù)(參數(shù)一:服務器傳遞的數(shù)據(jù) 參數(shù)二:確認請求數(shù)據(jù)ACK)
    • TCP/IP協(xié)議中,如果接收方成功的接收到數(shù)據(jù),那么會回復一個ACK數(shù)據(jù)- ACK只是一個標記,標記是否成功傳輸數(shù)據(jù)
// 回調(diào)閉包
public typealias NormalCallback = ([Any], SocketAckEmitter) -> ()

// on方法
@discardableResult
open func on(_ event: String, callback: @escaping NormalCallback) -> UUID

// SocketClientEvent: 接受枚舉類型的on方法
@discardableResult
open func on(clientEvent event: SocketClientEvent, callback: @escaping NormalCallback) -> UUID {
    // 這里調(diào)用的是上面的on方法
    return on(event.rawValue, callback: callback)
}

完整代碼

guard let url = URL(string: "ws://localhost:9090") else { return }

let manager = SocketManager(socketURL: url, config: [.log(true), .compress])
let socket = manager.defaultSocket

// 監(jiān)聽鏈接成功
socket.on(clientEvent: .connect) { (data, ack) in
    print("鏈接成功")
    print(data)
    print(ack)
}
        
socket.connect()

SocketIO事件

SocketIO通過事件鏈接服務器和傳遞數(shù)據(jù)

客戶端監(jiān)聽事件

// 監(jiān)聽鏈接成功
socket.on(clientEvent: .connect) { (data, ack) in
    print("鏈接成功")
    print(data)
    print(ack)
}

客戶端發(fā)送事件

只有連接成功之后,才能發(fā)送事件

// 建立一個連接到服務器. 連接成功會觸發(fā) "connect"事件
open func connect()

// 連接到服務器. 如果連接超時,會調(diào)用handle
open func connect(timeoutAfter: Double, withHandler handler: (() -> ())?)

// 重開一個斷開連接的socket
open func disconnect()

// 向服務器發(fā)送事件, 參數(shù)一: 事件的名稱,參數(shù)二: 傳輸?shù)臄?shù)據(jù)組
open func emit(_ event: String, with items: [Any])

服務器監(jiān)聽事件

  • 監(jiān)聽客戶端事件,需要嵌套在連接好的connect回調(diào)函數(shù)中
  • 必須使用回調(diào)函數(shù)的socket參數(shù),如function(s)中的s,監(jiān)聽事件,因此這是客戶端的socket,肯定監(jiān)聽客戶端發(fā)來的事件
  • 服務器監(jiān)聽連接的回調(diào)函數(shù)的參數(shù)可以添加多個,具體看客戶端傳遞數(shù)據(jù)數(shù)組有幾個,每個參數(shù)都是與客戶段一一對應,第一個參數(shù)對應客戶端數(shù)組第0個數(shù)據(jù)
// 監(jiān)聽socket連接
socket.on('connection',function(s){

    console.log('監(jiān)聽到客戶端連接');

    // data:客戶端數(shù)組第0個元素
    // data1:客戶端數(shù)組第1個元素
    s.on('chat',function(data,data1){

        console.log('監(jiān)聽到chat事件');

        console.log(data,data1);
        
    });
});

服務器發(fā)送事件

這里的socket一定要用服務器端的socket

// 給當前客戶端發(fā)送數(shù)據(jù),其他客戶端收不到.
socket.emit('chat', '服務器' + data)

// 發(fā)給所有客戶端,不包含當前客戶端
socket.emit.broadcast.emit('chat', '發(fā)給所有客戶端,不包含當前客戶端' + data)

// 發(fā)給所有客戶端,包含當前客戶端
socket.emit.sockets.emit('chat', '發(fā)給所有客戶端,包含當前客戶端' + data)

SocketIO分組

  • 每一個客戶端和服務器只會保持一個socket鏈接, 那么怎么吧每一條信息推送到對應的聊天室, 針對多個聊天室的問題有如何解決
  • 給每個聊天室都分組, 服務器就可以給指定的組進行數(shù)據(jù)的推送, 就不會影響到其他的聊天室

如何分組

  • socket.io提供rooms和namespace的API
  • rooms的API就可以實現(xiàn)多房間聊天了,總結(jié)出來無外乎就是:join/leave roomsay to room
  • 這里的socket是客戶端的socket,也就是連接成功,傳遞過來的socket
// join和leave
io.on('connection', function(socket){
  socket.join('some room');
  // socket.leave('some room');
});
 
// say to room
io.to('some room').emit('some event'):
io.in('some room').emit('some event'):

分組的原理

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

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