Rust 實(shí)戰(zhàn) - 使用套接字聯(lián)網(wǎng)API(二)

上一節(jié)签餐,我們已經(jīng)實(shí)現(xiàn)了一個(gè)最小可運(yùn)行版本。之所以使用Rust而不是C愚战,是因?yàn)镽ust具備了必要的抽象能力娇唯,還能獲得跟C差不多的性能。這一節(jié)寂玲,我們對(duì)上一節(jié)的代碼做必要的封裝塔插,順便還能把unsafe的代碼包裝成safe的API。

我將上一節(jié)的源碼放到了這里拓哟,你可以去查看想许。

還記得上一節(jié),我們把使用到的libc中的函數(shù)socket断序、bind流纹、connect和結(jié)構(gòu)體sockaddrsockaddr_in违诗、in_addr等漱凝,在Rust這邊定義了出來(lái)。實(shí)際上诸迟,幾乎libc中的函數(shù)茸炒,libc這個(gè)crate都幫我們定義好了愕乎。你可以去這里查看。編譯器和標(biāo)準(zhǔn)庫(kù)本身也使用了這個(gè)crate壁公,我們也使用這個(gè)感论。

首先在Cargo.toml文件的[dependencies]下面加入libc = "0.2":

[dependencies]
libc = "0.2"

接著在main.rs文件上方加入use libc;,也可以use libc as c;紊册”纫蓿或者你直接簡(jiǎn)單粗暴use libc::*,并不推薦這樣湿硝,除非你明確知道你使用的函數(shù)來(lái)自哪里薪前。并將我們定義的與libc中對(duì)用的常量、函數(shù)关斜、結(jié)構(gòu)體刪除示括。再添加libc::c::到我們使用那些常量、結(jié)構(gòu)體痢畜、函數(shù)的地方垛膝。如果你是直接use libc::*,除了直接刪除那部分代碼外丁稀,幾乎什么都不用做吼拥。目前的代碼:

use std::ffi::c_void;
use libc as c;

fn main() {
    use std::io::Error;
    use std::mem;
    use std::thread;
    use std::time::Duration;

    thread::spawn(|| {

        // server
        unsafe {
            let socket = c::socket(c::AF_INET, c::SOCK_STREAM, c::IPPROTO_TCP);
            if socket < 0 {
                panic!("last OS error: {:?}", Error::last_os_error());
            }

            let servaddr = c::sockaddr_in {
                sin_family: c::AF_INET as u16,
                sin_port: 8080u16.to_be(),
                sin_addr: c::in_addr {
                    s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be()
                },
                sin_zero: mem::zeroed()
            };

            let result = c::bind(socket, &servaddr as *const c::sockaddr_in as *const c::sockaddr, mem::size_of_val(&servaddr) as u32);
            if result < 0 {
                println!("last OS error: {:?}", Error::last_os_error());
                c::close(socket);
            }

            c::listen(socket, 128);

            loop {
                let mut cliaddr: c::sockaddr_storage = mem::zeroed();
                let mut len = mem::size_of_val(&cliaddr) as u32;

                let client_socket = c::accept(socket, &mut cliaddr as *mut c::sockaddr_storage as *mut c::sockaddr, &mut len);
                if client_socket < 0 {
                    println!("last OS error: {:?}", Error::last_os_error());
                    break;
                }

                thread::spawn(move || {
                    loop {
                        let mut buf = [0u8; 64];
                        let n = c::read(client_socket, &mut buf as *mut _ as *mut c_void, buf.len());
                        if n <= 0 {
                            break;
                        }

                        println!("{:?}", String::from_utf8_lossy(&buf[0..n as usize]));

                        let msg = b"Hi, client!";
                        let n = c::write(client_socket, msg as *const _ as *const c_void, msg.len());
                        if n <= 0 {
                            break;
                        }
                    }

                    c::close(client_socket);
                });
            }

            c::close(socket);
        }

    });

    thread::sleep(Duration::from_secs(1));

    // client
    unsafe {
        let socket = c::socket(c::AF_INET, c::SOCK_STREAM, c::IPPROTO_TCP);
        if socket < 0 {
            panic!("last OS error: {:?}", Error::last_os_error());
        }

        let servaddr = c::sockaddr_in {
            sin_family: c::AF_INET as u16,
            sin_port: 8080u16.to_be(),
            sin_addr: c::in_addr {
                s_addr: u32::from_be_bytes([127, 0, 0, 1]).to_be()
            },
            sin_zero: mem::zeroed()
        };

        let result = c::connect(socket, &servaddr as *const c::sockaddr_in as *const c::sockaddr, mem::size_of_val(&servaddr) as u32);
        if result < 0 {
            println!("last OS error: {:?}", Error::last_os_error());
            c::close(socket);
        }

        let msg = b"Hello, server!";
        let n = c::write(socket, msg as *const _ as *const c_void, msg.len());
        if n <= 0 {
            println!("last OS error: {:?}", Error::last_os_error());
            c::close(socket);
        }

        let mut buf = [0u8; 64];
        let n = c::read(socket, &mut buf as *mut _ as *mut c_void, buf.len());
        if n <= 0 {
            println!("last OS error: {:?}", Error::last_os_error());
        }

        println!("{:?}", String::from_utf8_lossy(&buf[0..n as usize]));

        c::close(socket);
    }
}

你編譯運(yùn)行,應(yīng)該能得到與上一節(jié)同樣的結(jié)果线衫。

接下來(lái)凿可,我們嘗試把上面代碼中函數(shù),封裝成更具Rust風(fēng)格的API授账,除了TCP外枯跑,也還要考慮之后把UDP、UNIX域和SCTP也增加進(jìn)來(lái)白热。同時(shí)敛助,我們跟標(biāo)準(zhǔn)庫(kù)里 net相關(guān)的API保持一致的風(fēng)格。我們暫時(shí)不考慮跨平臺(tái)屋确,只考慮Linux纳击,因此可以大膽的將一些linux獨(dú)有的API添加進(jìn)來(lái)。

UNIX中一切皆文件攻臀,套接字也不例外焕数。字節(jié)流套接字上的read和write函數(shù)所表現(xiàn)出來(lái)的行為,不同于通常的文件I/O茵烈。字節(jié)流套接字上調(diào)用read和write輸入或輸出字節(jié)數(shù)可能比請(qǐng)求的要少百匆,這個(gè)現(xiàn)象的原因在于內(nèi)核中用于套接字的緩沖區(qū)可能已經(jīng)達(dá)到了極限。不過(guò)呜投,這并不是我們正真關(guān)心的加匈。我們來(lái)看看標(biāo)準(zhǔn)庫(kù)中 File的實(shí)現(xiàn):

pub struct File(FileDesc);

impl File {
    ...
    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
            self.0.read(buf)
    }

    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
            self.0.write(buf)
    }

    pub fn duplicate(&self) -> io::Result<File> {
            self.0.duplicate().map(File)
    }
    ...
}

File 是一個(gè)元組結(jié)構(gòu)體,標(biāo)準(zhǔn)庫(kù)已經(jīng)實(shí)現(xiàn)了readwrite仑荐,以及duplicate雕拼。duplicate很有用,用于復(fù)制出一個(gè)新的描述符粘招。我們繼續(xù)看File中"包裹的FileDesc:

pub struct FileDesc {
    fd: c_int,
}

impl File {
    ...
    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
            let ret = cvt(unsafe {
            libc::read(self.fd,
                       buf.as_mut_ptr() as *mut c_void,
                       cmp::min(buf.len(), max_len()))
            })?;
            Ok(ret as usize)
    }

    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
            let ret = cvt(unsafe {
                    libc::write(self.fd,
                        buf.as_ptr() as *const c_void,
                        cmp::min(buf.len(), max_len()))
            })?;
            Ok(ret as usize)
    }

    pub fn set_cloexec(&self) -> io::Result<()> {
            unsafe {
                    cvt(libc::ioctl(self.fd, libc::FIOCLEX))?;
                    Ok(())
            }
    }

    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
            unsafe {
                 let v = nonblocking as c_int;
                    cvt(libc::ioctl(self.fd, libc::FIONBIO, &v))?;
                    Ok(())
            }
    }
}

這一層應(yīng)該是到頭了啥寇,你可以看到,Rust中的File也是直接對(duì)libc的封裝洒扎,不過(guò)你不用擔(dān)心辑甜,一開(kāi)始就提到,Rust 的ABI與C的ABI是兼容的袍冷,也就意味著Rust和C互相調(diào)用是幾乎是零開(kāi)銷的磷醋。FileDescreadwrite中的實(shí)現(xiàn),與我們之前對(duì)sockfdreadwrite基本是一樣的胡诗。除了readwrite外邓线,還有兩個(gè)很有用的方法set_cloexecset_nonblocking

我把“依附于”某個(gè)類型的函數(shù)叫做方法煌恢,與普通函數(shù)不同的是骇陈,依附于某個(gè)類型的函數(shù),必須通過(guò)它所依附的類型調(diào)用瑰抵。Rust通過(guò)這種方式來(lái)實(shí)現(xiàn)OOP你雌,但是與某些語(yǔ)言的OOP不同的是,Rust的這種實(shí)現(xiàn)是零開(kāi)銷的二汛。也就是婿崭,你將一些函數(shù)依附到某個(gè)類型上,并不會(huì)對(duì)運(yùn)行時(shí)造成額外的開(kāi)銷习贫,這些都在編譯時(shí)去處理逛球。

set_cloexec方法會(huì)對(duì)描述符設(shè)置FD_CLOEXEC。我們經(jīng)常會(huì)碰到需要fork子進(jìn)程的情況苫昌,而且子進(jìn)程很可能會(huì)繼續(xù)exec新的程序颤绕。對(duì)描述符設(shè)置FD_CLOEXEC,就意味著祟身,我們fork子進(jìn)程時(shí)奥务,父子進(jìn)程中相同的文件描述符指向系統(tǒng)文件表的同一項(xiàng),但是袜硫,我們?nèi)绻{(diào)用exec執(zhí)行另一個(gè)程序氯葬,此時(shí)會(huì)用全新的程序替換子進(jìn)程的正文。為了較少不必要的麻煩婉陷,我們以后要對(duì)打開(kāi)的描述符設(shè)置FD_CLOEXEC帚称,除非遇到特殊情況官研。

set_nonblocking用于將描述符設(shè)置為非阻塞模式,如果我們要使用poll闯睹、epoll等api的話戏羽。

既然標(biāo)準(zhǔn)庫(kù)已經(jīng)封裝好了FileDesc,我想直接使用的楼吃,然而FileDesc在標(biāo)準(zhǔn)庫(kù)之外是不可見(jiàn)的始花。如果使用File的話,set_cloexecset_nonblocking 還是要我們?cè)賹懸淮魏⑽?code>File并不是“我自己”的類型酷宵,我沒(méi)法直接給File附加方法,為此還需要一個(gè)額外的Tarit或者用一個(gè)“我自己”的類型躬窜,去包裹它浇垦。挺繞的。那既然這樣斩披,我們還是自己來(lái)吧溜族。不過(guò)我們已經(jīng)有了參考,可以將標(biāo)準(zhǔn)庫(kù)里的FileDecs直接復(fù)制出來(lái)垦沉,然后去掉與Linux無(wú)關(guān)的代碼煌抒,當(dāng)然你也可以自由發(fā)揮一下。

要注意的是厕倍,這段代碼中還調(diào)用了一個(gè)函數(shù)cvt寡壮,我們把相關(guān)代碼也復(fù)制過(guò)來(lái):

use std::io::{self, ErrorKind};

#[doc(hidden)]
pub trait IsMinusOne {
    fn is_minus_one(&self) -> bool;
}

macro_rules! impl_is_minus_one {
    ($($t:ident)*) => ($(impl IsMinusOne for $t {
        fn is_minus_one(&self) -> bool {
            *self == -1
        }
    })*)
}

impl_is_minus_one! { i8 i16 i32 i64 isize }

pub fn cvt<T: IsMinusOne>(t: T) -> io::Result<T> {
    if t.is_minus_one() {
        Err(io::Error::last_os_error())
    } else {
        Ok(t)
    }
}

pub fn cvt_r<T, F>(mut f: F) -> io::Result<T>
    where T: IsMinusOne,
          F: FnMut() -> T
{
    loop {
        match cvt(f()) {
            Err(ref e) if e.kind() == ErrorKind::Interrupted => {}
            other => return other,
        }
    }
}

還記得上一節(jié)我們使用過(guò)的last_os_error()方法么,這段代碼通過(guò)宏impl_is_minus_onei32等常見(jiàn)類型實(shí)現(xiàn)了IsMinusOne這個(gè)Tarit讹弯,然后我們就可以使用cvt函數(shù)更便捷得調(diào)用last_os_error()取得錯(cuò)誤况既。 我將這段代碼放到util.rs文件中,并在main.rs文件上方加入pub mod util;

然后再來(lái)看FileDesc最終的實(shí)現(xiàn):

use std::mem;
use std::io;
use std::cmp;
use std::os::unix::io::FromRawFd;

use libc as c;

use crate::util::cvt;

#[derive(Debug)]
pub struct FileDesc(c::c_int);

pub fn max_len() -> usize {
    <c::ssize_t>::max_value() as usize
}

impl FileDesc {
    pub fn raw(&self) -> c::c_int {
        self.0
    }

    pub fn into_raw(self) -> c::c_int {
        let fd = self.0;
        mem::forget(self);
        fd
    }

    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
        let ret = cvt(unsafe {
            c::read(
                self.0,
                buf.as_mut_ptr() as *mut c::c_void,
                cmp::min(buf.len(), max_len())
            )
        })?;

        Ok(ret as usize)
    }

    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
        let ret = cvt(unsafe {
            c::write(
                self.0,
                buf.as_ptr() as *const c::c_void,
                cmp::min(buf.len(), max_len())
            )
        })?;

        Ok(ret as usize)
    }

    pub fn get_cloexec(&self) -> io::Result<bool> {
        unsafe {
            Ok((cvt(libc::fcntl(self.0, c::F_GETFD))? & libc::FD_CLOEXEC) != 0)
        }
    }

    pub fn set_cloexec(&self) -> io::Result<()> {
        unsafe {
            cvt(c::ioctl(self.0, c::FIOCLEX))?;
            Ok(())
        }
    }

    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
        unsafe {
            let v = nonblocking as c::c_int;
            cvt(c::ioctl(self.0, c::FIONBIO, &v))?;
            Ok(())
        }
    }

    pub fn duplicate(&self) -> io::Result<FileDesc> {
        cvt(unsafe { c::fcntl(self.0, c::F_DUPFD_CLOEXEC, 0) }).and_then(|fd| {
            let fd = FileDesc(fd);
            Ok(fd)
        })
    }
}

impl FromRawFd for FileDesc {
    unsafe fn from_raw_fd(fd: c::c_int) -> FileDesc {
        FileDesc(fd)
    }
}

impl Drop for FileDesc {
    fn drop(&mut self) {
        let _ = unsafe { c::close(self.0) };
    }
}

我已經(jīng)將與Linux不相關(guān)的代碼刪除掉了组民。之所以原有duplicate那么冗長(zhǎng)棒仍,是因?yàn)榕f的Linux內(nèi)核不支持F_DUPFD_CLOEXEC這個(gè)設(shè)置。fcntl這個(gè)函數(shù)臭胜,用來(lái)設(shè)置控制文件描述符的選項(xiàng),我們稍后還會(huì)遇到用來(lái)設(shè)置和獲取套接字的getsockoptsetsockopt。還有read_atwrite_at等實(shí)現(xiàn)比較復(fù)雜的函數(shù),我們用不到,也將他們刪除淫茵。還有impl<'a> Read for &'a FileDesc爪瓜,因?yàn)閮?nèi)部使了一個(gè)Unstable的API匙瘪,我也將其去掉了。

我自由發(fā)揮了一下蝶缀,把:

pub struct FileDesc {
    fd: c_int,
}

替換成了:

pub struct FileDesc(c::c_int);

它們是等效的鳍悠。不知你注意到?jīng)]有,我把pub fn new(...)函數(shù)給去掉了涧卵,因?yàn)檫@個(gè)函數(shù)是unsafe的----如果我們今后將這些代碼作為庫(kù)讓別人使用的話伐脖,他可能傳入了一個(gè)不存在的描述符巫俺,并由此可能引起程序崩潰----但他們并不一定知道撼港。我們可以通過(guò)在這個(gè)函數(shù)前面加unsafe來(lái)告訴使用者這個(gè)函數(shù)是unsafe的: pub unsafe fn new(...)。不過(guò),Rust的開(kāi)發(fā)者們已經(jīng)考慮到了這一點(diǎn)赫段,我們用約定俗成的from_raw_fd來(lái)代替pub unsafe fn new(...),于是才有了下面這一段:

impl FromRawFd for FileDesc {
    unsafe fn from_raw_fd(fd: c::c_int) -> FileDesc {
        FileDesc(fd)
    }
}

最后撩银,還利用Rust的drop實(shí)現(xiàn)了close函數(shù)给涕,也就意味著,描述符離開(kāi)作用域后,會(huì)自動(dòng)close够庙,就不再需要我們手動(dòng)close了恭应。與之先關(guān)的是into_raw方法,意思是把FileDesc轉(zhuǎn)換為“未加工的”或者說(shuō)是“裸的”描述符耘眨,也就是C的描述符昼榛。這個(gè)方法里面調(diào)用了forget,之后變量離開(kāi)作用域后剔难,就不會(huì)調(diào)用drop了胆屿。當(dāng)你使用這個(gè)方法拿到描述符,使用完請(qǐng)不要忘記手動(dòng)close或者再次from_raw_fd钥飞。

pub fn into_raw(self) -> c::c_int {
        let fd = self.0;
        mem::forget(self);
        fd
}

我將這段代碼放到了一個(gè)新的文件fd.rs中莺掠,并在main.rs文件上方加入pub mod fd;

接著读宙,我們還需一個(gè)Socket類型,將socket楔绞、bind结闸、connect等函數(shù)附加上去。這一步應(yīng)該簡(jiǎn)單多了酒朵。同時(shí)你也會(huì)發(fā)現(xiàn)桦锄,我們已經(jīng)把unsafe的代碼,封裝成了safe的代碼蔫耽。

use std::io;
use std::mem;
use std::os::unix::io::{RawFd, AsRawFd, FromRawFd};

use libc as c;

use crate::fd::FileDesc;
use crate::util::cvt;

pub struct Socket(FileDesc);

impl Socket {
    pub fn new(family: c::c_int, ty: c::c_int, protocol: c::c_int) -> io::Result<Socket> {
        unsafe {
            cvt(c::socket(family, ty | c::SOCK_CLOEXEC, protocol))
                .map(|fd| Socket(FileDesc::from_raw_fd(fd)))
        }
    }

    pub fn bind(&self, storage: *const c::sockaddr, len: c::socklen_t) -> io::Result<()> {
        self.setsockopt(c::SOL_SOCKET, c::SO_REUSEADDR, 1)?;

        cvt(unsafe { c::bind(self.0.raw(), storage, len) })?;

        Ok(())
    }

    pub fn listen(&self, backlog: c::c_int) -> io::Result<()> {
        cvt(unsafe { c::listen(self.0.raw(), backlog) })?;
        Ok(())
    }

    pub fn accept(&self, storage: *mut c::sockaddr, len: *mut c::socklen_t) -> io::Result<Socket> {
        let fd = cvt(unsafe { c::accept4(self.0.raw(), storage, len, c::SOCK_CLOEXEC) })?;
        Ok(Socket(unsafe { FileDesc::from_raw_fd(fd) }))
    }

    pub fn connect(&self, storage: *const c::sockaddr, len: c::socklen_t) -> io::Result<()> {
        cvt(unsafe { c::connect(self.0.raw(), storage, len) })?;
        Ok(())
    }

    pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
        self.0.read(buf)
    }

    pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
        self.0.write(buf)
    }

    pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
        self.0.set_nonblocking(nonblocking)
    }

    pub fn get_cloexec(&self) -> io::Result<bool> {
        self.0.get_cloexec()
    }

    pub fn set_cloexec(&self) -> io::Result<()> {
        self.0.set_cloexec()
    }

    pub fn setsockopt<T>(&self, opt: libc::c_int, val: libc::c_int, payload: T) -> io::Result<()> {
        unsafe {
            let payload = &payload as *const T as *const libc::c_void;

            cvt(libc::setsockopt(
                self.0.raw(),
                opt,
                val,
                payload,
                mem::size_of::<T>() as libc::socklen_t
            ))?;

            Ok(())
        }
    }

    pub fn getsockopt<T: Copy>(&self, opt: libc::c_int, val: libc::c_int) -> io::Result<T> {
        unsafe {
            let mut slot: T = mem::zeroed();
            let mut len = mem::size_of::<T>() as libc::socklen_t;

            cvt(libc::getsockopt(
                self.0.raw(),
                opt,
                val,
                &mut slot as *mut T as *mut libc::c_void,
                &mut len
            ))?;

            assert_eq!(len as usize, mem::size_of::<T>());
            Ok(slot)
        }
    }
}

impl FromRawFd for Socket {
    unsafe fn from_raw_fd(fd: RawFd) -> Socket {
        Socket(FileDesc::from_raw_fd(fd))
    }
}

impl AsRawFd for Socket {
    fn as_raw_fd(&self) -> RawFd {
        self.0.raw()
    }
}

我已經(jīng)將上一節(jié)中我們使用到的socket相關(guān)的主要的5個(gè)函數(shù)结耀,外加readwrite匙铡,等幾個(gè)描述符設(shè)置的函數(shù)图甜,“依附”到了Socket上。保存在 socket.rs文件里鳖眼。

要說(shuō)明的是黑毅,我在newaccept方法中,通過(guò)flags直接為新創(chuàng)建的描述符設(shè)置了SOCK_CLOEXEC選項(xiàng)钦讳,如果不想一步設(shè)置的話矿瘦,就需要?jiǎng)?chuàng)建出描述符后,再調(diào)用set_cloexec方法愿卒。bind中缚去,在調(diào)用c::bind之前,我給套接字設(shè)置了個(gè)選項(xiàng)SO_REUSEADDR琼开,意為允許重用本地地址易结,這里不展開(kāi)講,如果你細(xì)心的話就會(huì)發(fā)現(xiàn),上一節(jié)的例子衬衬,如果沒(méi)有正常關(guān)閉socket的話买猖,就可能會(huì)出現(xiàn)error:98,Address already in use滋尉,等一會(huì)兒才會(huì)好玉控。accept4不是個(gè)標(biāo)準(zhǔn)的方法,只有Linux才支持狮惜,我們暫時(shí)不考慮兼容性高诺。setsockoptgetsockopt方法中涉及到了類型轉(zhuǎn)換,結(jié)合前面的例子碾篡,這里應(yīng)該難不倒你了虱而。除了from_raw_fd,我還又給Socket實(shí)現(xiàn)了又一個(gè)約定俗成的方法as_raw_fd开泽。

我已經(jīng)將遠(yuǎn)嗎放到了這里牡拇,你可以去查看。你還可以嘗試將上一節(jié)的例子穆律,修改成我們今天封裝的Socket惠呼。這一節(jié)到這里就結(jié)束了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末峦耘,一起剝皮案震驚了整個(gè)濱河市剔蹋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辅髓,老刑警劉巖泣崩,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異洛口,居然都是意外死亡矫付,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門绍弟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)技即,“玉大人,你說(shuō)我怎么就攤上這事樟遣《穑” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵豹悬,是天一觀的道長(zhǎng)葵陵。 經(jīng)常有香客問(wèn)我,道長(zhǎng)瞻佛,這世上最難降的妖魔是什么脱篙? 我笑而不...
    開(kāi)封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任娇钱,我火速辦了婚禮,結(jié)果婚禮上绊困,老公的妹妹穿的比我還像新娘文搂。我一直安慰自己,他們只是感情好秤朗,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開(kāi)白布煤蹭。 她就那樣靜靜地躺著,像睡著了一般取视。 火紅的嫁衣襯著肌膚如雪硝皂。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天作谭,我揣著相機(jī)與錄音稽物,去河邊找鬼。 笑死折欠,一個(gè)胖子當(dāng)著我的面吹牛贝或,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播怨酝,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼傀缩,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了农猬?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤售淡,失蹤者是張志新(化名)和其女友劉穎斤葱,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體揖闸,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揍堕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了汤纸。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片衩茸。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖贮泞,靈堂內(nèi)的尸體忽然破棺而出楞慈,到底是詐尸還是另有隱情,我是刑警寧澤啃擦,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布囊蓝,位于F島的核電站,受9級(jí)特大地震影響令蛉,放射性物質(zhì)發(fā)生泄漏聚霜。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蝎宇。 院中可真熱鬧弟劲,春花似錦、人聲如沸姥芥。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)撇眯。三九已至报嵌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間熊榛,已是汗流浹背锚国。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留玄坦,地道東北人血筑。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像煎楣,于是被迫代替她去往敵國(guó)和親豺总。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348

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