目录

1. Libevent介绍

1.1 安装Libevent

2. 事件处理框架 - event_base

2.1 event_base API函数

event_base和fork(进程)关系:

3. 事件循环

3.1 设置事件循环

3.2 终止事件循环

4. 事件

4.1 事件基本操作

事件的创建  event_new

事件的释放

事件的添加、删除

4.2 事件的优先级设置

例子:使用event实现有名管道的进程间通信myfifo

5. 带缓冲区的事件

概念理解:

创建/释放基于套接字的bufferevent     bufferevent_socket_new

在bufferevent上启动连接服务器函数  bufferevent_socket_connect

bufferevent读写缓冲区回调操作    bufferevent_setcb

禁用、启用缓冲区

操作bufferevent中的数据  bufferevent_write   bufferevent_read

6. 链接监听器

创建和释放evconnlistener

启用和禁用 evconnlistener

调整 evconnlistener 的回调函数

7. 例子:用event实现服务器和客户端tcp通信

服务器server代码:

客户端client代码:

8. 总结:

处理不带缓冲区的事件:

处理带缓冲区的事件:


1. Libevent介绍

Libevent 是一个用C语言编写的、轻量级的开源高性能事件通知库,主要有以下几个亮点:

  • > - 事件驱动( event-driven),高性能;
  • > - 轻量级,专注于网络;
  • > - 源代码相当精炼、易读;
  • > - 跨平台,支持 Windows、 Linux、 BSD(是Unix的衍生系统) 和 Mac OS;
  • > - 支持多种 I/O 多路复用技术, epoll、 poll、 select 和 kqueue 等;
  • > - 支持 I/O,定时器和信号等事件;
  • > - 支持注册事件优先级。

1.1 安装Libevent

  • 下载地址: http://libevent.org/
  • 安装步骤 -> 源码安装的方式(终端打开下载目录)
          1. 可执行程序: configure./configure         // 检测安装环境, 并且生成一个makefile文件2. 根据makefile中的构建规则编译源代码make     3. 安装sudo make install   //将得到可执行程序/动态库/静态库/头文件拷贝到系统目录
  • 动态库找不到问题解决:

1. 通过find命令查找对应的库的位置
      find 搜索目录 -name "libevent.so"
          得到结果: /usr/local/lib/libevent.so
  2. 通过vi 打开/etc/ld.so.conf文件
      sudo /etc/ld.so.conf
      将/usr/local/lib/放到文件的最后一行, 保存
  3. 执行命令: sudo ldconfig
  
编译要加上动态库event  hello.c
  gcc hello.c -o hello -levent

2. 事件处理框架 - event_base

使用 libevent函数之前需要分配一个或者多个 event_base 结构体。每个event_base 结构体持有一个事件集合,可以检测以确定哪个事件是激活的。每个 event_base 都有一种用于检测哪种事件已经就绪的 “方法”。

2.1 event_base API函数

// 头文件#include <event2/event.h>// 操作函数struct event_base * event_base_new(void);          //创建事件处理框架void event_base_free(struct event_base * base);    //释放事件处理框架// 检查event_base的后端方法const char** event_get_supported_methods(void);const char *event_base_get_method(const struct event_base *base);

event_base和fork(进程)关系:

  1. 子进程创建成功之后, 父进程可以继续使用event_base
  2. 子进程中需要继续使用event_base需要用下面函数,重新初始化
int event_reinit(struct event_base* base);

例子:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>int main()
{// 1. 创建事件处理框架struct event_base* base = event_base_new();// 打印支持的IO转接函数const char** method = event_get_supported_methods();for(int i=0; method[i] != NULL; ++i){printf("%s\n", method[i]);}printf("current method: %s\n", event_base_get_method(base));// 创建子进程pid_t pid = fork();if(pid == 0){// 子进程中event_base也会被复制,在使用这个base时候要重新初始化event_reinit(base); }// 2. 释放资源event_base_free(base);return 0;
}

3. 事件循环

event_base不停的检测委托的检测是实际是不是发生了, 如果发生了, event_base会调用对应的回调函数, 这个回调函数的用户委托检测事件的时候给的.

3.1 设置事件循环

如果委托了event_base检测某些事件, 不停的进行循环检测
结束检测时间: 所有要检测的事件都被触发, 并且处理完毕。

// 头文件#include <event2/event.h>// 操作函数#define EVLOOP_ONCE          0x01#define EVLOOP_NONBLOCK         0x02#define EVLOOP_NO_EXIT_ON_EMPTY 0x04int event_base_loop(struct event_base *base, int flags);参数:- base: 通过 event_base_new(void)得到的- flags:- EVLOOP_ONCE: 一直检测某个事件, 当事件被触发了, 停止事件循环- EVLOOP_NONBLOCK: 非阻塞的方式检测, 当事件被触发了, 停止事件循环- EVLOOP_NO_EXIT_ON_EMPTY: 一直进行事件检测, 如果没有要检测的事件, 不退出int event_base_dispatch(struct event_base* base);    // 一般使用这个函数参数:- base: 通过 event_base_new(void)得到的

3.2 终止事件循环

// 头文件#include <event2/event.h>struct timeval {long    tv_sec;                    long    tv_usec;    // 微秒        };// 在 tv 时长之后退出循环, 如果这个参数为空NULL, 直接退出事件循环// 事件循环: 检测对应的事件是否被触发了// 如果事件处理函数正在被执行, 执行完毕之后才终止int event_base_loopexit(struct event_base * base, const struct timeval * tv);// 马上终止int event_base_loopbreak(struct event_base * base);

4. 事件

4.1 事件基本操作

  • 事件的创建  event_new

  #include <event2/event.h>//要检测事件   what:#define EV_TIMEOUT    0x01#define EV_READ         0x02#define EV_WRITE        0x04#define EV_SIGNAL   0x08#define EV_PERSIST  0x10    // 修饰某个事件是持续触发的#define EV_ET        0x20    // 边沿模式//回调函数格式:typedef void (*event_callback_fn)(evutil_socket_t,short,void *);参数:- 第一个参数: event_new的第二个参数- 第二个参数: 实际触发的事件- 第三个参数: event_new的最后一个参数// 创建事件struct event* event_new(struct event_base * base,evutil_socket_t fd,short what,event_callback_fn cb,void * arg);参数:- base: event_base_new得到的- fd: 文件描述符, 检测这个fd对应的事件- what: 监测fd的什么事件 - cb: 回调函数, 当前检测的事件被触发, 这个函数被调用- arg: 给回调函数传参
  • 事件的释放

#include <event2/event.h>
// 释放事件资源void event_free(struct event * event);
  • 事件的添加、删除

事件被new出之后, 不能直接被event_base进行检测event_add之后event_base就可以对事件进行检测

#include <event2/event.h>
int  event_add(struct event * ev,const  struct timeval * tv);参数: tv-> 超时时间, 如果这个值> 0, 比如 == 3检测fd的读事件, 在三秒之内没有触发该事件 -> 超时-> 超时之后, 事件对应的回调函数会被强制调用如果该参数为NULL, 不会做超时检测// 删除要检测的事件int  event_del(struct event * ev);

4.2 事件的优先级设置

// 头文件#include <event2/event.h>// EVENT_MAX_PRIORITIES == 256     最大的初始化事件优先级int event_base_priority_init(struct event_base * base,int n_priorities);参数:- n_priorities: 等级的个数, 假设 == 6也就是说有6个等级: 0,1,2,3,4,5, 0优先级最高// 获取当前可用的等的个数int event_base_get_npriorities(struct event_base * base);// 给事件设置等级int event_priority_set(struct event *event, int priority);参数:- event: 创建的事件- priority: 要设置的等级

例子:使用event实现有名管道的进程间通信myfifo

myfifo管道文件要提前创造好

读端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <fcntl.h>//读的回调函数
void read_cb(evutil_socket_t fd, short what, void* arg)
{char buf[128];int count = read(fd, buf, sizeof(buf)+1);printf("read data: %s, %d\n", buf, count);printf("read event: %s\n", what & EV_READ ? "yes" : "no");printf("what: %d\n\n", what);
}int main()
{int fd = open("myfifo", O_RDONLY);if (fd == -1){perror("open");exit(0);}struct event_base* base = event_base_new();//创建事件struct event* ev = event_new(base, fd, EV_READ|EV_PERSIST, read_cb, NULL);//添加事件event_add(ev, NULL);//事件循环检测event_base_dispatch(base);//释放资源event_free(ev);event_base_free(base);return 0;
}

写端:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <event2/event.h>
#include <fcntl.h>//读的回调函数
void write_cb(evutil_socket_t fd, short what, void* arg)
{char buf[128];static int num = 0;sprintf(buf, "hello, %d\n", num++);int count = write(fd, buf, sizeof(buf)+1);printf("wirte data: %s, %d\n", buf, count);printf("wirte event: %s\n", what & EV_WRITE ? "yes" : "no");printf("what: %d\n\n", what);sleep(3);
}int main()
{int fd = open("myfifo", O_WRONLY);if (fd == -1){perror("open");exit(0);}struct event_base* base = event_base_new();//创建事件struct event* ev = event_new(base, fd, EV_WRITE|EV_PERSIST, write_cb, NULL);//添加事件event_add(ev, NULL);//事件循环检测event_base_dispatch(base);//释放资源event_free(ev);event_base_free(base);return 0;
}

5. 带缓冲区的事件

  • 概念理解:

1. bufferevent 理解:

  • >    - 是libevent为IO缓冲区操作提供的一种通用机制
  • >    - bufferevent 由一个底层的传输端口(如套接字 ),  一个读取缓冲区和一个写入缓冲区组成。
  • >    - 与通常的事件在底层传输端口已经就绪, 可以读取或者写入的时候执行回调不同的是, bufferevent在读取或者写入了足够量的数据之后调用用户提供的回调

2. - 每个 bufferevent 有两个数据相关的回调

  • >      - 读取回调: 从底层传输端口读取了任意量的数据之后会调用读取回调(默认)
  • >      - 写入回调: 输出缓冲区中足够量的数据被清空到底层传输端口后写入回调会被调用(默认)
  • 创建/释放基于套接字的bufferevent     bufferevent_socket_new

主要应用于网络套接字通信   -> socket()

struct bufferevent *bufferevent_socket_new(struct event_base *base,evutil_socket_t fd,enum bufferevent_options options); 参数:- base: 处理事件的- fd: 通信的文件描述符- options: BEV_OPT_CLOSE_ON_FREE -> 自动释放底层资源返回值: 得到带缓冲区的事件变量// 释放资源void bufferevent_free(struct bufferevent *bev);
  • 在bufferevent上启动连接服务器函数  bufferevent_socket_connect

1. 如果还没有为bufferevent 设置套接字,调用函数将为其分配一个新的流套接字,并且设置为非阻塞的
          例子:bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
2. 如果已经为 bufferevent 设置套接字,调用bufferevent_socket_connect() 将告知 libevent 套接字还未连接,直到连接成功之前不应该对其进行读取或者写入操作。
3. 连接完成之前可以向输出缓冲区添加数据。

int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen); 参数:- bev: 带缓冲区的事件, 里边封装 fd- address: 要连接的服务器的IP和端口- addrlen: address结构体的内存大小
  • bufferevent读写缓冲区回调操作    bufferevent_setcb

//读、写事件触发之后的回调函数格式
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void *ctx);参数:- bev: 从bufferevent_setcb函数中的第一个参数传入的- ctx: 从bufferevent_setcb函数中的最后第一个参数传入的//特殊事件的回调函数格式
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);参数:- bev: 从bufferevent_setcb函数中的第一个参数传入的- events: 可以检测到的事件EV_EVENT_READING:读取操作时发生某事件,具体是哪种事件请看其他标志。BEV_EVENT_WRITING:写入操作时发生某事件,具体是哪种事件请看其他标志。BEV_EVENT_ERROR:操作时发生错误。关于错误的更多信息,请调用 EVUTIL_SOCKET_ERROR()。BEV_EVENT_TIMEOUT:发生超时。BEV_EVENT_EOF:遇到文件结束指示。BEV_EVENT_CONNECTED:请求的连接过程已经完成 void bufferevent_setcb(struct bufferevent *bufev, bufferevent_data_cb readcb,        bufferevent_data_cb writecb, bufferevent_event_cb eventcb, void *cbarg
);参数:- bufev: 带缓冲区的事件- readcb: 读事件触发之后的回调函数- writecb: 写事件触发之后的回调函数- eventcb: 特殊事件的回调函数- cbarg: 给回调函数传参
  • 禁用、启用缓冲区

可以启用或者禁用 bufferevent 上的 EV_READ、EV_WRITE 或者 EV_READ | EV_WRITE 事件。
没有启用读取或者写入事件时, bufferevent 将不会试图进行数据读取或者写入。

写缓冲区默认是有效的,读缓冲区默认无效

// 设置某个事件有效void bufferevent_enable(struct bufferevent *bufev, short events); // 设置某个事件无效void bufferevent_disable(struct bufferevent *bufev, short events);// 获取缓冲区对应的有效事件short bufferevent_get_enabled(struct bufferevent *bufev);
  • 操作bufferevent中的数据  bufferevent_write   bufferevent_read

// 向bufferevent的输出缓冲区添加数据int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);// 从bufferevent的输入缓冲区移除数据size_t bufferevent_read(struct bufferevent *bufev, void *data, size_t size);

6. 链接监听器

  • 创建和释放evconnlistener

#include <event2/listener.h> //回调函数格式typedef void (*evconnlistener_cb)(struct evconnlistener *listener,   evutil_socket_t sock,   struct sockaddr *addr, int len, void *ptr); 参数:- listener: evconnlistener_new_bind 返回的地址- sock: 用于通信的fd- addr: 客户端的地址信息- ptr: 外部传进来的参数, evconnlistener_new_bind的第三个参数// 创建监听的套接字, 绑定, 设置监听, 等待并接受连接请求struct evconnlistener *evconnlistener_new_bind(struct event_base *base,    evconnlistener_cb cb,            // 接受新连接之后的回调函数void *ptr,                      // 回调函数参数unsigned flags, int backlog,                   // listen()中的第二参数,最多的监听数量,小于128的整数const struct sockaddr *sa,     // 本地的IP和端口int socklen                   // struct sockaddr结构体大小);参数:- flags:LEV_OPT_CLOSE_ON_FREE: 自动关闭底层套接字LEV_OPT_REUSEABLE: 设置端口复用
// 释放void evconnlistener_free(struct evconnlistener *lev); 
  • 启用和禁用 evconnlistener

设置无效之后, 就不监听连接请求了

  #include <event2/listener.h> int evconnlistener_disable(struct evconnlistener *lev);int evconnlistener_enable(struct evconnlistener *lev);
  • 调整 evconnlistener 的回调函数

#include <event2/listener.h>void evconnlistener_set_cb(struct evconnlistener *lev, evconnlistener_cb cb, void *arg);

7. 例子:用event实现服务器和客户端tcp通信

服务器使用链接监听器、带缓冲区的事件

客户端使用带缓冲区的事件

服务器server代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{// 读缓冲区的数据char buf[128];int len = bufferevent_read(bev, buf, sizeof(buf));printf("read data: len = %d, str = %s\n", len, buf);// 回复数据bufferevent_write(bev, buf, len);printf("数据发送完毕...\n");
}// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{printf("arg value: %s\n", (char*)arg);printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{if(event & BEV_EVENT_ERROR){printf("some error happened ...\n");}else if(event & BEV_EVENT_EOF){printf("server disconnect ...\n");}// 终止连接bufferevent_free(bev);
}// 接收连接请求之后的回调
void listener_cb(struct evconnlistener *listener,   evutil_socket_t sock,   struct sockaddr *addr, int len, void *ptr)
{// 通信// 使用带缓冲区的事件对sock进行包装struct event_base* base = (struct event_base*)ptr;struct bufferevent* bev = bufferevent_socket_new(base, sock, BEV_OPT_CLOSE_ON_FREE);// 设置回调bufferevent_setcb(bev, read_cb, write_cb, events_cb, NULL);bufferevent_enable(bev, EV_READ);
}int main()
{struct event_base * base = event_base_new();// 1. 创建监听的套接字, 绑定, 设置监听, 等待并接受连接请求struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(9898);    // 服务器监听的端口addr.sin_addr.s_addr = INADDR_ANY;struct evconnlistener* listener = evconnlistener_new_bind(base, listener_cb, base, LEV_OPT_CLOSE_ON_FREE | LEV_OPT_REUSEABLE,100, (struct sockaddr*)&addr, sizeof(addr)); event_base_dispatch(base);evconnlistener_free(listener);event_base_free(base);return 0;
}

客户端client代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <event2/event.h>
#include <event2/bufferevent.h>// read缓冲区的回调
void read_cb(struct bufferevent* bev, void* arg)
{printf("arg value: %s\n", (char*)arg);// 读缓冲区的数据char buf[128];int len = bufferevent_read(bev, buf, sizeof(buf));printf("read data: len = %d, str = %s\n", len, buf);// 回复数据bufferevent_write(bev, buf, len);printf("数据发送完毕...\n");
}// 写缓冲区的回调
// 调用的时机: 写缓冲区中的数据被发送出去之后, 该函数被调用
void write_cb(struct bufferevent* bev, void* arg)
{printf("arg value: %s\n", (char*)arg);printf("数据已经发送完毕...xxxxxxxxxxxx\n");
}// 事件回调
void events_cb(struct bufferevent* bev, short event, void* arg)
{if(event & BEV_EVENT_ERROR){printf("some error happened ...\n");}else if(event & BEV_EVENT_EOF){printf("server disconnect ...\n");}// 终止连接bufferevent_free(bev);
}void send_msg(evutil_socket_t fd, short ev, void * arg)
{// 将写入到终端的数据读出char buf[128];int len = read(fd, buf, sizeof(buf));// 发送给服务器struct bufferevent* bev = (struct bufferevent*)arg;bufferevent_write(bev, buf, len);
}int main()
{struct event_base * base = event_base_new();// 1. 创建通信的套接字struct bufferevent* bufev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);// 2. 连接服务器struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(9898);    // 服务器监听的端口inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);// 这个函数调用成功, == 服务器已经成功连接bufferevent_socket_connect(bufev, (struct sockaddr*)&addr, sizeof(addr));// 3. 通信// 给bufferevent的缓冲区设置回调bufferevent_setcb(bufev, read_cb, write_cb, events_cb, (void*)"hello, world");bufferevent_enable(bufev, EV_READ);// 创建一个普通的输入事件struct event* myev = event_new(base, STDIN_FILENO, EV_READ|EV_PERSIST, send_msg, bufev);event_add(myev, NULL);event_base_dispatch(base);event_free(myev);event_base_free(base);return 0;
}

8. 总结:

处理不带缓冲区的事件:

  1. 创建事件处理框架event_base                                                         event_base_new()
  2. 创建新事件event                                                                              event_new()
  3. 将事件添加到事件处理框架event_base上                                       event_add()
  4. 启动事件循环检测                                                                            event_base_dispatch()
  5. 循环结束之后释放资源                                                                     event_base_free() 、event_free()

处理带缓冲区的事件:

  1. 创建事件处理框架event_base                                                              event_base_new()
  2. 服务器端:
    1. 创建连接监听器(在回调函数得到fd)                                        evconnlistener_new_bind()
    2. 将通信fd包装                                                                                bufferevent_socket_new()
    3. 使用bufferevent通信:给bufferevent读写缓冲区设置回调函数    bufferevent_setcb()
    4. 设置读缓冲区可用                                                                        bufferevent_enable()
    5. 对缓冲区数据操作                                                                        bufferevent_write()、bufferevent_read()
  3. 客户端:
    1. 创建通信用的fd并且使用bufferevent包装                                      bufferevent_socket_new()
    2. 连接服务器                                                                                    bufferevent_socket_connect()
    3. 使用bufferevent通信:给bufferevent读写缓冲区设置回调函数     bufferevent_setcb()
    4. 设置读缓冲区可用                                                                         bufferevent_enable()
    5. 对缓冲区数据操作                                                                         bufferevent_write()、bufferevent_read()

c/c++:Libevent应用(Libevent介绍、 事件处理框架 - event_base、事件循环、事件、带缓冲区的事件、链接监听器)相关推荐

  1. 【Libevent】Libevent学习笔记(三):事件循环

    00. 目录 文章目录 00. 目录 01. event_base_loop函数 02. event_base_dispatch函数 03. event_base_loopexit函数 04. eve ...

  2. 【传智播客】Libevent学习笔记(三):事件循环

    目录 00. 目录 01. event_base_loop函数 02. event_base_dispatch函数 03. event_base_loopexit函数 04. event_base_l ...

  3. Libevent库的介绍与应用

    Libevent库 Libevent概述 Libevent使用模型 Libevent库使用示例 Libevent事件类型和框架结构 使用Libevent完成tcp服务端 Libevent概述 Libe ...

  4. 【libevent】libevent库学习总结(一)——基础

    libevent库学习总结(一)--基础 一.基础 1.1. 介绍 Libevent是一个用于开发可伸缩网络服务器的事件通知库.Libevent API提供了一种机制来执行回调函数,当某个特定事件发生 ...

  5. 【Libevent】Libevent学习笔记(一):简介和安装

    00. 目录 文章目录 00. 目录 01. libevent简介 02. Libevent的好处 03. Libevent的安装和测试 04. Libevent成功案例 05. 参考资料 01. l ...

  6. 【Libevent】Libevent学习笔记(二):创建event_base

    00. 目录 文章目录 00. 目录 01. 简介 02. 创建默认的event_base 03. 创建复杂的event_base 3.1 event_config_new函数 3.2 event_b ...

  7. 【libevent】libevent库学习总结(二)——编程步骤

    一.libevent编程步骤 0.分配并初始化event_base,两种方法 0.1 event_base_new:线程安全,代替event_init: 0.2 event_init:线程不安全,不推 ...

  8. 基于Vue的Quasar Framework 介绍 这个框架UI组件很全面

    基于Vue的Quasar Framework 介绍 这个框架UI组件很全面 基于Vue的Quasar Framework 中文网 http://www.quasarchs.com/ quasarfra ...

  9. openhub_介绍OpenHub框架

    openhub 本文介绍OpenHub框架 -基于Apache Camel的新的开源集成解决方案. 本文回答了一些问题,为什么您应该关心另一个集成框架,强弱属性以及如何使用OpenHub启动新项目. ...

最新文章

  1. Nutanix企业云助力广播传媒的融合媒体发展之路
  2. C#进阶系列——DDD领域驱动设计初探(五):AutoMapper使用
  3. boost::mpl模块实现replace_if相关的测试程序
  4. 举例讲清楚模型树和回归树的区别
  5. 我是如何用JSP在网络上架构一个网上招标系统,以推进网站无纸化,过程电子化,管理智能化的发展
  6. Python+socket完美实现TCP长连接保持存活
  7. IDEA的热部署配置
  8. Hive group by后面不能用列别名
  9. No.025:Reverse Nodes in k-Group
  10. 视频教程-【深入理解计算机网络】入门计算机网络基础视频课程(下)-计算机网络
  11. OPNsense - 多功能高可靠易使用的防火墙(五)
  12. Apache服务器下载
  13. C语言实现简易五子棋
  14. object标签属性详解
  15. 使用图灵机器人实现微信消息的智能回复
  16. 判断给出的秒数是几天几小时几分几秒
  17. 矩阵快速幂 51nod
  18. sqlserver 日期比较
  19. sql临时表_SQL临时表
  20. 安卓上比较好的python开发软件-三款可以在安卓手机上运行Python代码的软件

热门文章

  1. 报刊杂志发行管理软件——安氏软件
  2. 基于java+springboot+mybatis+vue+elementui的B2C购物电商平台设计与实现
  3. linux如何运行sshpass,sshpass命令常用使用操作
  4. ireport报表自定义字体
  5. SIP和ISUP协议互通的研究
  6. 影像科dsa为什么必须买维修保险_【科普】你不得不了解的“医学影像科设备及耗材大...
  7. CAD看图软件如何删除CAD图纸内容?
  8. Windows phone 8 JumpStart--Async
  9. 如何免费使用https
  10. 软件工程——读书笔记(八/九/十)