Linux系统编程13:libevent

目标

  1. 学会libevent的安装。
  2. 学会编译和运行使用libevent的程序。
  3. 了解libevent反应器原理
  4. 熟悉libevent的开发步骤
  5. 能够使用libevent解决一些简单问题。

文档

1.简介

  1. 适用于windows、linux、bsd等多种平台
  2. 轻量级的开源的高性能的事件触发的网络库
  3. 内部使用selectpollepoll等系统调用管理事件机制

2. 下载安装

2.1 自动安装

  • Centos
yum install libevent-devel
  • Ubuntu
apt-get install libevent-dev

2.2 手动安装

  1. 下载官网
  2. 配置./configure
  3. 编译make
  4. 安装sudo make install
  5. 测试
ll /usr/lib/libevent*.so

或者

ll /usr/local/lib/libevent*.so

3. 构成

No. 模块 说明
1 libevent_core 所有核心的事件和缓冲功能,包含event_baseevbufferbufferevent,工具函数
2 libevent_pthreads 基于pthread可移植线程库的线程和锁,它独立于libevent_core,这样程序使用libevent时就不需要链接到pthread,除非是以多线程方式使用libevent。
3 libevent_extra 定义了特定协议HTTP、DNS、RPC
4 libevent 因为历史原因而存在,不要使用这个库,未来版本可能被去掉libevent_corelibevent_extra

4. 功能

No. 功能 含义
1 事件通知 当文件描述符可读可写时将执行回调函数。
2 IO缓存 缓存事件提供了输入输出缓存,能自动的读入和写入,用户不必直接操作IO。
3 定时器 定时器的机制,在一定的时间间隔之后调用回调函数。
4 信号 触发信号,执行回调。
5 异步的DNS解析 异步解析DNS服务器的DNS解析函数集。
6 事件驱动的HTTP服务器 HTTP服务器。
7 RPC客户端服务器框架 RPC服务器和客户端框架,自动的封装和解封数据结构。
8 Reactor(反应器)模式 应用程序提供相应的接口并且注册到reactor

相应的事件发生后,reactor自动调用相应的注册的接口函数(类似于回调函数)通知。


5. 接口

头文件:event2/event.h
库:event

执行需要指定动态链接库位置

export LD_LIBRARY_PATH=动态链接库位置

6. 使用流程

在libevent中主要有两部分构成反应器(event_base)和事件(event)构成。事件是基本操作单元。

  1. 创建反应器
struct event_base* pBase = event_base_new();
  1. 创建事件/赋值事件
struct event* pEvent = event_new(pBase,fd,what,cb,arg);

或者

struct event evt;
event_new(&evt,pBase,fd,what,cb,arg);
  1. 添加事件
event_add(pEvent,NULL);
  1. 分发事件
event_base_dispatch(pBase);
  1. 释放事件
event_free(pEvent);
  1. 释放反应器
event_base_free(pBase);

事件状态

No. 状态 说明 对应接口
1 已初始化(initialized) 初始化事件后的状态 调用event_new()/event_assign()
2 未决状态(pending) 添加事件后的状态 调用event_add()
3 激活状态(active) 触发事件发生 回调cb

持久(persisitent)

7. 函数

  • 反应器
No. 功能 函数
1 创建反应器 struct event_base* event_base_new();
2 释放反应器 void event_base_free(struct event_base* base);
3 分发事件 int event_base_dispatch(struct event_base* base);
  • 事件
No. 功能 函数
1 创建事件 struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fn cb,void *arg)
2 释放事件 void event_free(struct event* ev);
3 赋值事件 int event_assign(struct event* ev,struct event_base* base,evutil_socket_t fd,short what,event_callback_fn cb,void *arg);
4 添加事件 int event_add(struct event* ev,const struct timeval *tv);
5 删除事件 int event_del(struct event* ev);
6 未决事件 int event_pending(struct event* ev,short what,struct timeval *tv_out);

7.1 反应器函数

7.1.1 创建反应器

struct event_base*  event_base_new();
  • 返回值
No. 参数 说明
1 NULL 失败
2 NULL 反应器指针

7.1.2 释放反应器

void event_base_free(struct event_base* base);
  • 参数
No. 参数 说明
1 base 反应器指针

7.1.3 分发事件

int  event_base_dispatch(struct event_base* base);
  • 参数
No. 参数 说明
1 base 反应器指针
  • 返回值
No. 参数 说明
1 -1 失败
2 0 成功

7.2 事件函数

7.2.1 创建事件

struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fn cb,void *arg)
  • 参数
No. 参数 说明
1 base 反应器指针
2 fd 文件描述符
3 what 事件类型
4 cb 事件回调函数
5 arg 事件回调函数参数

对于文件描述符fd,如果没有文件描述符传入-1,例如定时器事件。如果是系统事件,传入对应的事件宏定义。

  • 事件类型
No. 事件类型 说明
1 EV_TIMEOUT 超时事件 0x01
2 EV_READ 读事件 0x02
3 EV_WRITE 写事件 0x03
4 EV_SIGNAL 信号事件 0x04
5 EV_PERSIST 持久配置 0x05
6 EV_ET 读写的边沿触发事件 0x06
  • event_callback_fn
typedef void (*event_callback_fn)(evutil_socket_t,short,void*);
  • 返回值
No. 参数 说明
1 NULL 失败
2 NULL 事件指针

7.2.2 释放事件

void event_free(struct event* ev);
  • 参数
No. 参数 说明
1 ev 事件指针

7.2.3 赋值事件

int event_new(struct event* evt,struct event_base* base,evutil_socket_t fd,short what,event_callback_fn cb,void *arg);
  • 参数
    除了第一个参数表示需要赋值的事件外,其他的与event_new()相同。

  • 返回值

No. 参数 说明
1 -1 失败
2 0 成功

7.2.4 添加事件

int event_add(struct event* ev,const struct timeval *tv);
  • 参数
No. 参数 说明
1 ev 事件指针
2 tv 指定超时事件,NULL表示永不超时
  • 返回值
No. 参数 说明
1 -1 失败
2 0 成功

7.2.5 删除事件

int event_del(struct event* ev);
  • 参数
No. 参数 说明
1 ev 事件指针
  • 返回值
No. 参数 说明
1 -1 失败
2 0 成功

7.2.6 未决事件

int event_pending(struct event* ev,short what,struct timeval *tv_out);
  • 参数
No. 参数 说明
1 ev 事件指针
2 what 事件类型
2 tv_out 返回超时事件
  • 返回值
No. 参数 说明
1 非零0 未决状态的事件类型

8. 示例

libevent事件有两种创建方式:堆上创建和非堆(栈或者全局变量)上创建。

下面通过下面几个案例二者的区别。

8.1 读取控制台数据

  1. 堆上创建读取事件
#include <stdlib.h>  
#include <stdio.h>   
#include <unistd.h>
#include <event2/event.h>  

void onRead(evutil_socket_t fd, short event, void *arg)  { 
    char buf[BUFSIZ];
    read(fd,buf,BUFSIZ);
    printf("%s\n",buf);  
}   
      
int main()  {   
    // 初始化反应器   
    struct event_base* pBase = event_base_new();   

    // 创建事件  
    struct event* pEvent = NULL;
    pEvent = event_new(pBase,STDIN_FILENO,EV_READ,onRead,NULL);

    // 添加事件   
    event_add(pEvent,NULL);   
      
    // 事件循环   
    event_base_dispatch(pBase);   

    // 释放事件
    event_free(pEvent);

    // 释放反应器
    event_base_free(pBase);      
    return 0;   
}  
  1. 非堆上创建读取事件
#include <stdlib.h>  
#include <stdio.h>   
#include <unistd.h>
#include <event2/event.h>  
#include <event2/event_struct.h> // 注意:可能会出现不兼容后期版本

void onRead(evutil_socket_t fd, short event, void *arg)  { 
    char buf[BUFSIZ];
    read(fd,buf,BUFSIZ);
    printf("%s\n",buf);  
}   
      
int main()  {   
    // 初始化反应器   
    struct event_base* pBase = event_base_new();   

    // 创建事件  
    struct event evt;
    event_assign(&evt,pBase,STDIN_FILENO,EV_READ,onRead,NULL);

    // 添加事件   
    event_add(&evt,NULL);   
      
    // 事件循环   
    event_base_dispatch(pBase);   

    // 不需要释放事件

    // 释放反应器
    event_base_free(pBase);      
    return 0;   
}  
  • 两种方式进行比较


8.2 定时器事件

  1. 堆上创建定时器事件
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <event2/event.h>

// 定时事件回调函数
void onTime(evutil_socket_t fd, short event, void *arg)  {
    printf("Hello,World!\n");
    struct event** ppEvent = (struct event**)(arg);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 重新添加定时事件(定时事件触发后默认自动删除)
    event_add(*ppEvent, &tv);
}

int main()  {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建定时事件
    struct event* pEvent = NULL;
    pEvent = event_new(pBase,-1,EV_TIMEOUT,onTime,&pEvent);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 添加定时事件
    event_add(pEvent, &tv);

    // 事件循环
    event_base_dispatch(pBase);

    // 释放事件
    event_free(pEvent);

    // 释放反应器
    event_base_free(pBase);
    return 0;
}
  1. 非堆栈上创建定时器事件
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <event2/event.h>
#include <event2/event_struct.h> // 注意:可能会出现不兼容后期版本

// 定时事件回调函数
void onTime(evutil_socket_t fd, short event, void *arg)  {
    printf("Hello,World!\n");
    struct event* pEvent = (struct event*)(arg);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 重新添加定时事件(定时事件触发后默认自动删除)
    event_add(pEvent, &tv);
}

int main()  {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建定时事件
    struct event evt;
    event_assign(&evt,pBase,-1,EV_TIMEOUT,onTime,&evt);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 添加定时事件
    event_add(&evt, &tv);

    // 事件循环
    event_base_dispatch(pBase);

    // 释放反应器
    event_base_free(pBase);
    return 0;
}

二者比较

  1. 持续化处理
    默认处理是不持续的,即事件只能使用一次,可以通过设置EV_PERSIST把事件设置成持续的。

堆栈上创建定时器事件

#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <event2/event.h>

void onTime(evutil_socket_t fd, short event, void *arg)  {
    printf("Hello,World!\n");
}

int main()  {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建定时事件
    struct event* pEvent = NULL;
    pEvent = event_new(pBase,-1,EV_TIMEOUT|EV_PERSIST,onTime,NULL);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 添加定时事件
    event_add(pEvent, &tv);

    // 事件循环
    event_base_dispatch(pBase);

    // 释放事件
    event_free(pEvent);

    // 释放反应器
    event_base_free(pBase);
    return 0;
}

非堆栈上创建定时器事件


#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <event2/event.h>
#include <event2/event_struct.h> // 注意:可能会出现不兼容后期版本

// 定时事件回调函数
void onTime(evutil_socket_t fd, short event, void *arg)  {
    printf("Hello,World!\n");
}

int main()  {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建定时事件
    struct event evt;
    event_assign(&evt,pBase,-1,EV_TIMEOUT|EV_PERSIST,onTime,NULL);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 添加定时事件
    event_add(&evt, &tv);

    // 事件循环
    event_base_dispatch(pBase);

    // 不需要释放事件

    // 释放反应器
    event_base_free(pBase);
    return 0;
}

8.3 信号事件

堆栈上创建信号事件

#include <stdio.h>
#include <event2/event.h>
#include <signal.h>

void signal_cb(evutil_socket_t fd, short event, void *arg){
        struct event *signal = (struct event*)arg;
        printf("signal_cb: got signal %d\n", event_get_signal(signal));
}
int main(int argc, char **argv) {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建信号事件
    struct event *pEvent = event_new(pBase, SIGINT,EV_SIGNAL,signal_cb,event_self_cbarg());

    // 添加信号事件
    event_add(pEvent, NULL);

    // 事件循环
    event_base_dispatch(pBase);

    // 释放事件
    event_free(pEvent);

    // 释放反应器
    event_base_free(pBase);

    return 0;
}

非堆栈上创建信号事件

#include <stdio.h>
#include <event2/event.h>
#include <signal.h>
#include <event2/event_struct.h> // 注意:可能会出现不兼容后期版本

void signal_cb(evutil_socket_t fd, short event, void *arg){
        struct event *signal = (struct event*)arg;
        printf("signal_cb: got signal %d\n", event_get_signal(signal));
}
int main(int argc, char **argv) {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建信号事件
    struct event evt;
    event_new(&evt,pBase, SIGINT,EV_SIGNAL,signal_cb,event_self_cbarg());

    // 添加信号事件
    event_add(&evt, NULL);

    // 事件循环
    event_base_dispatch(pBase);

    // 不需要释放事件

    // 释放反应器
    event_base_free(pBase);

    return 0;
}

8.4 FIFO读事件

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#include <event2/event.h>

static void fifo_read(evutil_socket_t fd, short event, void *arg)
{
    char buf[255];
    int len;
    struct event *ev = (struct event*)arg;
    fprintf(stderr, "fifo_read called with fd: %d, event: %d, arg: %p\n",
        (int)fd, event, arg);

    // 读取管道
    len = read(fd, buf, sizeof(buf) - 1);

    if (len <= 0) {
        if (len == -1)
            perror("read");
        else if (len == 0)
            fprintf(stderr, "Connection closed\n");
        
        // 删除事件
        event_del(ev);

        // 退出事件循环
        event_base_loopbreak(event_get_base(ev));
        return;
    }

    // 打印管道
    buf[len] = '\0';
    fprintf(stdout, "Read: %s\n", buf);
}

int main(int argc, char **argv) {
    const char *fifo = argv[1];
    // 创建命名管道
    unlink(fifo);
    if (mkfifo(fifo, 0600) == -1) {
        perror("mkfifo");
        exit(1);
    }

    // 打开命名管道
    int fifo_fd = open(fifo, O_RDONLY | O_NONBLOCK, 0);
    if (fifo_fd == -1) {
        perror("open");
        exit(1);
    }

    // 初始化反应器 
    struct event_base* base = event_base_new();

    // 创建FIFO读事件
    struct event* evfifo = event_new(base, fifo_fd, EV_READ|EV_PERSIST, fifo_read,
                           event_self_cbarg());

    // 添加FIFO读事件
    event_add(evfifo, NULL);

    // 事件循环
    event_base_dispatch(base);

    // 释放反应器
    event_base_free(base);

    // 关闭管道
    close(fifo_fd);

    // 删除管道
    unlink(fifo);
    return (0);
}

9. 定时器宏定义

对定时器事件,libevent提供了几个以evtimer_开头的宏定义,简化代码。

// 创建定时器事件
#define evtimer_new(base,callback,arg) \
    event_new((base),-1,0,(callback),(arg))
// 赋值定时器事件
#define evtimer_assign(event,base,callback,arg) \
    event_assign((event),(base),-1,0,(callback),(arg))
// 添加定时器事件
#define evtimer_add(ev,tv) \
    event_add((ev),(tv))
// 删除定时器事件
#define evtimer_del(ev) \
    event_add(ev)
// 未决定时器事件
#define evtimer_pending(ev,what,tv_out) \
    event_pending((ev),(what),(tv_out))

使用宏定义代替

    // 创建定时事件
    struct event* pEvent = NULL;
    pEvent = event_new(pBase,-1,EV_TIMEOUT,onTime,&pEvent);

    // 添加定时事件
    event_add(pEvent, &tv);

代替

    // 创建定时事件
    struct event* pEvent = NULL;
    pEvent = evtimer_new(pBase,onTime,&pEvent);
  
    // 添加定时事件
    evtimer_add(pEvent, &tv);
  • 代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <event2/event.h>
#include <assert.h>

// 定时事件回调函数
void onTime(evutil_socket_t fd, short event, void *arg)  {
    printf("Hello,World!\n");
    assert(NULL != arg);
    struct event** ppEvent = (struct event**)(arg);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 重新添加定时事件(定时事件触发后默认自动删除)
    evtimer_add(*ppEvent, &tv);
}

int main()  {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建定时事件
    struct event* pEvent = NULL;
    pEvent = evtimer_new(pBase,onTime,&pEvent);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 添加定时事件
    evtimer_add(pEvent, &tv);

    // 事件循环
    event_base_dispatch(pBase);

    // 释放事件
    event_free(pEvent);

    // 释放反应器
    event_base_free(pBase);
    return 0;
}

使用宏定义代替

    // 创建定时事件
    struct event evt;
    event_assign(&evt,pBase,-1,EV_TIMEOUT|EV_PERSIST,onTime,NULL);

    // 添加定时事件
    event_add(&evt, &tv);

代替

    // 创建定时事件
    struct event evt;
    evtimer_assign(&evt,pBase,onTime,&evt);

    // 添加定时事件
    evtimer_add(&evt, &tv);
  • 代码
#include <stdlib.h>
#include <stdio.h>
#include <sys/time.h>
#include <event2/event.h>
#include <event2/event_struct.h> // 注意:可能会出现不兼容后期版本

// 定时事件回调函数
void onTime(evutil_socket_t fd, short event, void *arg)  {
    struct event* pEvent = (struct event*)(arg);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 重新添加定时事件(定时事件触发后默认自动删除)
    evtimer_add(pEvent, &tv);
}

int main()  {
    // 初始化反应器
    struct event_base* pBase = event_base_new();

    // 创建定时事件
    struct event evt;
    evtimer_assign(&evt,pBase,onTime,&evt);

    // 定时间隔
    struct timeval tv;
    evutil_timerclear(&tv);
    tv.tv_sec = 1;

    // 添加定时事件
    evtimer_add(&evt, &tv);

    // 事件循环
    event_base_dispatch(pBase);

    // 释放反应器
    event_base_free(pBase);
    return 0;
}

10. 信号宏定义

对信号事件,libevent提供了几个以evsignal_开头的宏定义,简化代码。

#define evsignal_new(base,signum,cb,arg) \
    event_new(base,signum,EV_SIGNAL|EV_PERSIST,cb,arg)
#define evsignal_assign(ev,base,signum,cb,arg) \
    event_assign(ev,base,signum,EV_SIGNAL|EV_PERSIST,cb,arg)
#define evsignal_add(ev,tv) \
    event_add((ev),(tv))
#define evsignal_del(ev) \
    event_del(ev)
#define evsignal_pending(ev,what,tv_out) \
    event_pending((ev),(what),(tv_out))
  • 示例
#include <stdio.h>
#include <event2/event.h>
#include <signal.h>

void signal_cb(evutil_socket_t fd, short event, void *arg){
        struct event *signal = (struct event*)arg;
        printf("signal_cb: got signal %d\n", event_get_signal(signal));
}
int main(int argc, char **argv) {
        struct event_base* base = event_base_new();
        struct event *signal_int = evsignal_new(base, SIGINT, signal_cb, event_self_cbarg());
        event_add(signal_int, NULL);
        event_base_dispatch(base);
        event_free(signal_int);
        event_base_free(base);
}

11. 废弃/老版的函数

头文件:event.h

No. 函数 功能
1 event_init() 初始化事件
2 event_dispatch() 事件分发
3 event_set() 初始化设置事件
  • 示例
#include <stdlib.h>  
#include <stdio.h>  
#include <sys/time.h>  
#include <event.h>  
 
// 定时事件回调函数   
void onTime(int sock, short event, void *arg)  {   
    printf("Hello,World!\n");  
    
    // 事件间隔
    struct timeval tv;   
    tv.tv_sec = 1;   
    tv.tv_usec = 0;   
     
    // 重新添加定时事件(定时事件触发后默认自动删除)   
    event_add((struct event*)arg, &tv);   
}   
      
int main()  {   
    // 初始化   
    event_init();   
      
    // 设置定时事件  
    struct event ev_time;    
    evtimer_set(&ev_time, onTime, &ev_time);   
      
    // 事件间隔
    struct timeval tv;   
    tv.tv_sec = 1;   
    tv.tv_usec = 0;   

    // 添加定时事件   
    event_add(&ev_time, &tv);   
      
    // 事件循环   
    event_dispatch();   
      
    return 0;   
}  

new event_baseevent_init()区别:event_init()是创建一个全局的new event_base

实现相同的功能,即能用系统函数,又能用第三方库,使用那种方式实现?

参考资料

libevent参考手册(中文版)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342

推荐阅读更多精彩内容

  • Lua 5.1 参考手册 by Roberto Ierusalimschy, Luiz Henrique de F...
    苏黎九歌阅读 13,727评论 0 38
  • 1.ios高性能编程 (1).内层 最小的内层平均值和峰值(2).耗电量 高效的算法和数据结构(3).初始化时...
    欧辰_OSR阅读 29,299评论 8 265
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 今年,沈阳经历了一个暖冬,不仅气温比往年同期要高,而且雪下的也少。自入冬以来,沈阳只下过两场零星小雪。往年一到元旦...
    萌牛约瑟阅读 458评论 0 3
  • 1.数据库设计三范式 满足的范式越高,数据库的数据冗余越少。(1)第一范式:数据库表不包含多值属性。(属性的值是不...
    伊凡的一天阅读 221评论 0 1