libev事件驱动网络库从安装到使用

article/2025/10/8 12:55:58

上次说到的select/poll模型(传送门)的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应。我们可以将这种模型归类为“事件驱动模型”。
相比其他模型,使用 select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

但这个模型依旧有着很多问题。

首先,select() 接口并不是实现“事件驱动”的最好选择。因为当需要探测的句柄值较大时,select() 接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如 linux 提供了 epoll,BSD 提供了 kqueue,Solaris 提供了 /dev/poll …。如果需要实现更高效的服务器程序,类似 epoll 这样的接口更被推荐。遗憾的是不同的操作系统特供的 epoll 接口有很大差异,所以使用类似于 epoll 的接口实现具有较好跨平台能力的服务器会比较困难

其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的,在很大程度上降低了事件探测的及时性。

幸运的是,有很多高效的事件驱动库可以屏蔽上述的困难,常见的事件驱动库有 libevent 库,还有作为 libevent 替代者的 libev 库。这些库会根据操作系统的特点选择最合适的事件探测接口,并且加入了信号 (signal) 等技术以支持异步响应,这使得这些库成为构建事件驱动模型的不二选择。下面将介绍如何使用 libev 库替换 select 或 epoll 接口,实现高效稳定的服务器模型。
Libev是一个基于Reactor模式的事件库,效率较高、代码精简(4.15版本8000多行,c语言编写),是一个值得学习的轻量级事件驱动库。
一、安装
首先下载官方源码(http://dist.schmorp.de/libev/libev-4.15.tar.gz)
也可以从github上下载,一样的,地址(https://github.com/daidaotian/libev)
安装过程如下:

wget http://dist.schmorp.de/libev/libev-4.15.tar.gz
tar -zxf libev-4.15.tar.gz
cd libev-4.15
./configure
make
make install

下面是安装信息:

Libraries have been installed in:/usr/local/libIf you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:- add LIBDIR to the `LD_LIBRARY_PATH' environment variableduring execution- add LIBDIR to the `LD_RUN_PATH' environment variableduring linking- use the `-Wl,-rpath -Wl,LIBDIR' linker flag- have your system administrator add LIBDIR to `/etc/ld.so.conf'See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------/bin/mkdir -p '/usr/local/include'/usr/bin/install -c -m 644 ev.h ev++.h event.h '/usr/local/include'/bin/mkdir -p '/usr/local/share/man/man3'/usr/bin/install -c -m 644 ev.3 '/usr/local/share/man/man3'

下一步按要求设置环境变量:

//这种只对当前shell本次有效,想要永久生效去在/etc/profile文件中添加变量
export LIBDIR=/usr/local/lib
export LD_LIBRARY_PATH=/usr/local/lib
export LD_RUN_PATH=/usr/local/lib

二、使用
libev 跟select一样,同样需要循环探测事件是否产生。Libev 的循环体用 ev_loop 结构来表达,并用 ev_loop( ) 来启动。Libev通过一个struct ev_loop结构表示一个事件驱动的框架。

 void ev_loop( ev_loop* loop, int flags ) 

Libev 支持多种事件类型,在ev_loop框架里面通过ev_xxx结构,ev_init、ev_xxx_set、ev_xxx_start接口向这个事件驱动的框架里面注册事件监控器,当相应的事件监控器的事件出现时,便会触发该事件监控器的处理逻辑,去处理该事件。处理完之后,这些监控器进入到下一轮的监控中。符合一个标准的事件驱动状态的模型。
  Libev 除了提供了基本的三大类事件(IO事件、定时器事件、信号事件)外还提供了周期事件、子进程事件、文件状态改变事件等多个事件。
例如一个 IO 事件:
用 ev_io 来表征,并用 ev_io_init() 函数来初始化:

 void ev_io_init(ev_io *io, callback, int fd, int events) 

初始化内容包括回调函数 callback,被探测的句柄 fd 和需要探测的事件,EV_READ 表“可读事件”,EV_WRITE 表“可写事件”。

现在,用户需要做的仅仅是在合适的时候,将某些 ev_io 从 ev_loop 加入或剔除。一旦加入,下个循环即会检查 ev_io 所指定的事件有否发生;如果该事件被探测到,则 ev_loop 会自动执行 ev_io 的回调函数 callback();如果 ev_io 被注销,则不再检测对应事件。

无论某 ev_loop 启动与否,都可以对其添加或删除一个或多个 ev_io,添加和删除的接口是 ev_io_start() 和 ev_io_stop()。

 void ev_io_start( ev_loop *loop, ev_io* io ) void ev_io_stop( EV_A_* ) 

下面是一个官方给出的例子:

#include <stdio.h>
#include <ev.h> //ev库头文件//定义一个ev_TYPE 的结构体
ev_io stdin_watcher;//定义一个stdin的观测者
ev_timer timeout_watcher;//所有的watcher的回调函数都有相似的特点
//当stdin有可读的数据时,将会调用下面这个回调函数
static void stdin_cb(EV_P_ ev_io *w,int revents)
{puts("stdin ready");//每一次时间都必须用对应的停止函数,手动的停止其watcherev_io_stop(EV_A_ w);//这将导致所有嵌套执行的ev_run停止监听ev_break(EV_A_ EVBREAK_ALL);
}//这是一个回调函数,用于定时器回调
static void timeout_cb(EV_P_ ev_timer *w,int revents)
{puts("timeout");//这将导致最早运行的ev_run停止监听ev_break(EV_A_ EVBREAK_ONE);
}int main(int argc,char **args)
{//使用一般默认的事件循环struct ev_loop *loop = EV_DEFAULT;//初始化一个I/O watcher,然后启动它ev_io_init(&stdin_watcher,stdin_cb,0,EV_READ);ev_io_start(loop,&stdin_watcher);//初始化一个定时器watcher,然后启动它,只有一次,没有重复的5.5秒定时ev_timer_init(&timeout_watcher,timeout_cb,5.5,0);ev_timer_start(loop,&timeout_watcher);//这里等待时间出发ev_run(loop,0);//0代表只循环一次//撤销监听退出程序return 0;
}

libev大致框架

上个例子没什么好讲的,下面看另一个例子:

#include <stdio.h>
#include <signal.h>
#include <string.h>
#include <sys/unistd.h>
#include <ev.h>void io_action(struct ev_loop *main_loop,ev_io *io_w,int e)
{int rst;char buf[1024];memset(buf,0,sizeof(buf));puts("In IO action");read(STDIN_FILENO,buf,sizeof(buf));buf[1023]='\0';printf("String: %s\n",buf);ev_io_stop(main_loop,io_w);
}void timer_action(struct ev_loop *main_loop,ev_timer *time_w,int e)
{puts("In Time action");ev_timer_stop(main_loop,time_w);
}void signal_action(struct ev_loop *main_loop,ev_signal *signal_w,int e)
{puts("In Signal action");ev_signal_stop(main_loop,signal_w);ev_break(main_loop,EVBREAK_ALL);
}int main(int argc,char **argv)
{ev_io io_w;ev_timer timer_w;ev_signal signal_w;struct ev_loop *main_loop = ev_default_loop(0);ev_init(&io_w,io_action);ev_io_set(&io_w,STDIN_FILENO,EV_READ);ev_init(&timer_w,timer_action);ev_timer_set(&timer_w,2,0);ev_init(&signal_w,signal_action);ev_signal_set(&signal_w,SIGINT);ev_io_start(main_loop,&io_w);ev_timer_start(main_loop,&timer_w);ev_signal_start(main_loop,&signal_w);ev_run(main_loop,0);return 0;
}

该程序一直处于监听状态,直到有调用信号然后回调signal_w函数,该函数会调用ev_break函数退出ev_run的调用,如果注释掉第30行的代码,那么程序会在调用三个回调函数后才会结束(外包引用计数为0),否则一直监听着。
具体ev_run和ev_break的参数说明如下:

void ev_run (EV_P_ int flags);
void ev_break (EV_P_ int how);

flags:
0:默认值。一直循环进行处理,直到外部引用计数==0或者是显示退出
EVRUN_NOWAIT:运行一次,poll时候不会等待。如果有pending事件则进行处理,否则立即返回。
EVRUN_ONCE:运行一次,poll时候会等待至少一个event发生,处理完成之后返回。

how:
EVBREAK_ONE:只是退出一次ev_run这个调用。通常来说使用这个就可以了。
EVBREAK_ALL:退出所有的ev_run调用。这种情况存在于ev_run在pending处理时候会递归调用。

创建一个struct ev_loop *结构体,上面我们给出 ev_default_loop(0) 进行创建。使用libev的核心是事件循环,可以用 ev_default_loop 或 ev_loop_new 函数创建循环,或者直接使用 EV_DEFAULT 宏。

创建子进程后,且想要使用事件循环时,需要先在子进程中调用 ev_default_forkev_loop_fork 来重新初始化后端的内核状态,它们分别对应 ev_default_loop 和 ev_loop_new 来使用。

ev_run 启动事件循环。它的第二个参数为0时,将持续运行并处理循环直到没有活动的事件观察器或者调用了 ev_break 。另外两个取值是 EVRUN_NOWAIT 和 EVRUN_ONCE 。

ev_break 跳出事件循环(在全部已发生的事件处理完之后)。第二个参数为 EVBREAK_ONE 或 EVBREAK_ALL 来指定跳出最内层的 ev_run 或者全部嵌套的 ev_run 。

ev_suspend 和 ev_resume 用来暂停和重启事件循环,比如在程序挂起的时候。

创建watcher,主要包括类型、触发条件和回调函数。将它注册到事件循环上,在满足注册的条件时,会触发观察器,调用它的回调函数(callback)。上面的例子中已经包含了IO观察器和计时观察器、信号观察器,此外还有周期观察器、文件状态观察器等等。
初始化和设置观察器使用 ev_init 和 ev_TYPE_set也可以直接使用 ev_TYPE_init
在特定事件循环上启动观察器使用 ev_TYPE_start 。 ev_TYPE_stop 停止观察器,并且会释放内存。
libev中将观察器分为4种状态:初始化、启动/活动、等待、停止。libev中的观察器还支持优先级。

观察器(watcher):

typedef void (*)(struct ev_loop *loop, ev_TYPE *watcher, int revents) callback; // callback都是这种类型
ev_init (ev_TYPE *watcher, callback); // 初始化watcher
ev_TYPE_set (ev_TYPE *watcher, [args]); // 设置watcher
ev_TYPE_init (ev_TYPE *watcher, callback, [args]); // 通常使用这个函数最方便,初始化和设置都在这里
ev_TYPE_start (loop, ev_TYPE *watcher); // 注册watcher
ev_TYPE_stop (loop, ev_TYPE *watcher); // 注销watcher
ev_set_priority (ev_TYPE *watcher, int priority); // 设置优先级
ev_feed_event (loop, ev_TYPE *watcher, int revents); // 这个做跨线程通知非常有用,相当于触发了某个事件。
bool ev_is_active (ev_TYPE *watcher); // watcher是否active.
bool ev_is_pending (ev_TYPE *watcher); // watcher是否pending.
int ev_clear_pending (loop, ev_TYPE *watcher); // 清除watcher pending状态并且返回事件

watcher的4种状态:

  (1) initialiased.调用init函数初始化
  (2) active.调用start进行注册
  (3) pending.已经触发事件但是没有处理
  (4) inactive.调用stop注销。这个状态等同于initialised这个状态。
  
典型watcher有:
ev_io(IO可读可写观察器),ev_signal(信号处理观察器),ev_timer(定时器),
ev_periodic(周期任务处理),ev_child(子进程状态变化观察器),ev_stat(文件属性变化观察器)。
ev_fork(创建的进程时的观察器),ev_async(异步调用观察器),ev_cleanup(event loop退出时触发事件),ev_prepare(每次event loop之前事件),ev_check(每次event loop之后事件),ev_idle(每次event loop空闲触发事件)
下面举几个栗子:
1、ev_io

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ev.h>static void stdin_callback(struct ev_loop *loop,ev_io *w,int revents)
{char str[1024];if(revents & EV_READ){//stdin might have data for usprintf("有数据可读\n");scanf("%s",str);ev_io_stop(loop,w);}else if(revents & EV_WRITE){//stdout might have data for usprintf("有数据输出\n");//ev_break(loop,EVBREAK_ONE);}printf("water:%d\n",ev_is_active(w));
}int main(int argc,char **argv)
{struct ev_loop * main_loop = ev_default_loop(0);/*这里的ev_default_loop可以使用ev_loop_new动态分配一个,然后使用ev_loop_destroy销毁。struct ev_loop * epoller = ev_loop_new(EVBACKEND_EPOLL | EVFLAG_NOENV);这里一般是使用EVBACKEND_EPOLL模型,同样的还有EVBACKEND_SELECT EVBACKEND_POLL EVBACKEND_KQUEUE EVBACKEND_DEVPOLL EVBACKEND_PORT 如果默认,那么ev会自动判断系统环境,选择最适合的模型,Linux一般为epoll */ev_io stdin_watcher;//定义一个watcherev_init(&stdin_watcher,stdin_callback);//初始化watcher,参数是回调函数ev_io_set(&stdin_watcher,STDIN_FILENO,EV_READ|EV_WRITE);//设置watcher,参数为要监听的描述符及事件ev_io_start(main_loop,&stdin_watcher);//注册watcher,参数为要注册到的循环//ev_run(main_loop,EVRUN_ONCE);//void ev_set_io_collect_interval (EV_P_ ev_tstamp interval);//这个是设置轮询的时间//typedef double ev_tstampev_set_io_collect_interval(main_loop,2.);//2秒ev_run(main_loop,0);//ev_is_active(ev_TYPE * watcher);//用于判断watcher是否为activeprintf("main:%d\n",ev_is_active(&stdin_watcher));//initialiased.调用init函数初始化//active.调用start进行注册//pending.已经触发事件但是没有处理//inactive.调用stop注销。这个状态等同于initialised这个状态return 0;
}

2.ev_signal

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <ev.h>static void sigint_callback(struct ev_loop * loop,ev_signal *w,int revents)
{if(revents & EV_SIGNAL)//用这个可以判断这次进来的是不是ev_signal 如果一个callback回调函数复用的话,就可以用这个来区分{printf("signal SIGINT\n");ev_break(loop, EVBREAK_ALL);}
}static void sigquit_callback(struct ev_loop * loop,ev_signal *w,int revents)
{printf("signal SIGQUIT\n");ev_break(loop, EVBREAK_ALL);
}int main(int argc, char **args)
{struct ev_loop * main_loop=ev_default_loop(0);ev_signal sigint_watcher;ev_signal sigquit_watcher;ev_init(&sigint_watcher,sigint_callback);ev_signal_set(&sigint_watcher,SIGINT/*Other want to catch*/);//这里多个信号不能用或符号| 连接起来ev_signal_start(main_loop,&sigint_watcher);ev_init(&sigquit_watcher,sigquit_callback);ev_signal_set(&sigquit_watcher,SIGQUIT/*Other want to catch*/);ev_signal_start(main_loop,&sigquit_watcher);ev_run(main_loop,0);return 0;
}

运行程序,输入Ctrl-C(中断)或Ctrl-\(quit)都是可以捕获的。
3、ev_child

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <ev.h>static void child_callback(struct ev_loop *loop,ev_child *w,int revents)
{ev_child_stop(loop,w);printf("Process %d exited with status %d\n",w->rpid,w->rstatus);
}int main(int argc, char **args)
{struct ev_loop * main_loop=ev_default_loop(0);pid_t pid;ev_child child_watcher;pid=fork();if(pid<0){printf("Fork Error\n");return -1;}else if(pid==0)//child{printf("child doing..\n");return 0;}else //father{sleep(2);//即使让子进程先执行,最后还是可以捕获到。ev_init(&child_watcher,child_callback);ev_child_set(&child_watcher,pid,0);//ev_child_start(EV_DEFAULT_ &child_watcher);ev_child_start(main_loop,&child_watcher);ev_run(main_loop,0);}//waitpid(pid,0,0);return 0;
}

主进程通过pid将子进程绑定到了child_callback事件中,当子进程挂掉后,主进程就能捕捉的信号,然后调用child_callback函数。
4、ev_stat

#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <ev.h>static void stat_callback(struct ev_loop *loop,ev_stat *w, int revents)
{if(w->attr.st_nlink){printf("The file size %ld\n",(long)w->attr.st_size);}else{printf("文件不存在\n");}
}int main(int argc, char **args)
{struct ev_loop *main_loop=ev_default_loop(0);ev_stat stat_watcher;ev_init(&stat_watcher,stat_callback);ev_stat_set(&stat_watcher,"/home/myuser/hello.txt",0);ev_stat_start(main_loop,&stat_watcher);ev_run(main_loop,0);return 0;
}

如果文件有一点修改,无论是什么属性,都将触发这个回调函数。这个attr文件在这里可以获取到的属性成员。


http://chatgpt.dhexx.cn/article/W2v4Ksw5.shtml

相关文章

Libev documentation

2019独角兽企业重金招聘Python工程师标准>>> NAME libev - a high performance full-featured event loop written in C SYNOPSIS #include <ev.h> EXAMPLE PROGRAM // a single header file is required #include <ev.h>#include <stdio.h> // f…

libev库使用教程

libev是用C语言编写的高性能、全功能事件循环库&#xff0c;支持select&#xff0c;poll模型&#xff0c;也支持linux特定的epoll模型&#xff0c;一个小巧、易用的库。 环境准备&#xff1a;ubuntu 一、libev库支持的功能 官方文档&#xff1a;http://pod.tst.eu/http://cvs…

偷懒的网络框架libevent、libev框架介绍

libevent、libev框架介绍 前言概述libevent编译安装libeventlibevent的封装层次 IO事件检测的封装与api介绍事件管理器event_base构建事件管理器event_base_new释放事件管理器event_base_freeevent_reinitevent_get_supported_methodsevent_base_get_method事件循环event_base_…

libev实现分析

libev实现分析 libev是一个事件驱动库&#xff0c;底层是基于select、epoll、kqueue等I/O复用接口。所谓事件驱动库&#xff0c;就是用户定义一个事件以及改事件发生时调用的函数&#xff0c;该库会监听该事件&#xff0c;并在事件发生时调用相应的函数。 libev提供了很多事件…

libev源码解读

1、源码&#xff1a; 源码参见官网或者我的github上 2、安装使用&#xff1a; 安装与基本使用参见我的另一篇博客&#xff1a;传送门 3、架构 Libev通过一个 struct ev_loop结结构表示一个事件驱动的框架。在这个框架里面通过ev_xxx结构&#xff0c;ev_init、ev_xxx_set、…

libev库

libev库 概念数据结构watch集合watch结构 全局触发事件集合数据结构事件触发之IO事件定时器原理ev_run函数 概念 libev是一个轻量级的事件通知库&#xff0c;具备支持多种事件通知能力。 数据结构 在熟悉代码之前先了解其相关数据结构往往更加方便后续代码的阅读。 在libev中…

libev 源码解析

一 libev简介 libev是一个轻量级的事件通知库&#xff0c;具备支持多种事件通知能力&#xff0c;通过对libev的源码的阅读&#xff0c;可以清楚了解事件通知实现内部机制。 二 核心数据结构 在libev中关键的数据结构是&#xff0c;loop结构体&#xff0c;该结构体定义的字段较…

android libev 编译,移植libev事件库到Android中

因为libev库是使用C语言写的&#xff0c;所以在Android项目中使用此库的方法是把libev编译成.so文件&#xff0c;在Android中使用jni方式来调用libev的.so文件。 我们大家都知道android的ndk开发可以编译c,c代码&#xff0c;不过需要自己写Android.mk文件。但是对于大多数开源项…

libev

开始之前先看一下libevent libev libuv&#xff0c;参考附录1. 本着我自己的个性&#xff0c;我喜欢简单的东西&#xff0c;越简单越好&#xff0c;越傻越好&#xff0c;所以在此我考虑libev&#xff0c;只是tmd&#xff0c;libev的官网打不开&#xff0c;真是无语了。 上例子…

libev:详解

事件库之Libev&#xff08;一&#xff09; 使用Libev Libev的作者写了一份很好的官方Manual,比较的齐全&#xff0c;即介绍了Libev的设计思想&#xff0c;也介绍了基本使用还包括内部各类事件详细介绍。这里略微赘述一下。Libev通过一个 struct ev_loop 结结构表示一个事件驱动…

事件驱动库 libev 使用详解

C/CLinux服务器开发/后台架构师知识体系 libev 是一个通过 C 语言编写的&#xff0c;高性能的事件循环库&#xff0c;支持多种事件类型&#xff0c;与此类似的事件循环库还有 libevent、libubox 等&#xff0c;在此详细介绍下 libev 相关的内容。 简介 这是一个简单而且高性…

数据库和实例

这篇文章跟数据库开发有什么关系呢&#xff1f;我感觉呢只从字面上看确实没有什么关系&#xff0c;可是了解的话跟大牛讨论时他们最起码不会被鄙视、面试时可能也会有用。如果你再深入的钻研下去你就会发现里面的内容好“丰满”。作为一个菜鸟&#xff08;指本人&#xff09;以…

【达梦数据库实例创建】

达梦数据库实例创建 系统环境&#xff1a;银河麒麟V10 数据库版本&#xff1a;dm8 一、创建数据库实例 1.通过命令切换到路径/dm8/tool 2.执行命令进入配置助手 ./dbca.sh 选择一般用途&#xff0c;自动调整数据库性能参数&#xff0c;进行下一步操作&#xff1a; 设置数据…

SQL 经典实例

《SQL经典实例》 1&#xff0c;在WHERE子句中引用别名列 直接引用-报错 把查询包装为一个内嵌视图&#xff0c;这样就可以引用别名列了 select * from ( select sal as salary, comm as commission from emp ) x where salary < 5000同理&#xff1a;当你想在 WHERE 子句中…

SQL示例数据库

在本教程中&#xff0c;我们将向您介绍在整个教程中使用的SQL示例数据库。以下数据库关系图 - HR 示例数据库&#xff1a; 在这个HR 示例数据库有7个表&#xff1a; employees表存储员工的数据信息。 jobs表存储工作数据信息&#xff0c;包括职位和工资范围。 departments表…

数据库案例

目录 微信朋友圈设计 用户及用户关系 发朋友圈 CDN 发布表 相册表 时间线 刷朋友圈 删除、拉黑、标签、不让他看、三天可见该怎么办 谁可以看 标签 第二步的权限控制 评论和赞 微信朋友圈设计 用户及用户关系 肯定有用户表作为基础 用户关系表&#xff0c;用户…

SQL数据库编写及示例

一、 数据库编写 1、数据库常用约束 主键约束: primary key 外键约束: foreign key (references) 唯一值约束: unique 默认值约束: default 检查约束: check 非空约束: not null 标识列: identity 2、创建数据表注意事项 主外键数据类型必须一致 列与列之间用&#xff0c;间隔…

sqlserver:什么是数据库实例?

环境&#xff1a; window server 2019 datacentersqlserver2014 x64 问题&#xff1a; 什么是SQL server实例&#xff1f;数据库的对象架构是怎样设计的&#xff1f; 先把官方的解释贴出来&#xff1a; https://docs.microsoft.com/zh-cn/sql/relational-databases/databases…

【转】数据库设计实例一学习

​​​​​​数据库1​​​​​​​​​​​​​​​​​​​​​对多&#xff0c;1对1&#xff0c;M对N学习 以RBAC为例。​​​​​​​​​​​​​​ 于 RBAC&#xff08;Role-based Access Control&#xff09;权限访问控制。也就是说一个用户可以有多个角色&#xff…

sql server 数据库设计实例

本实例为综合实例,考察数据库原理中的,sql脚本的编写,创建——增删改查,视图和索引的创建等;数据库ER图,关系模式;以及一些高级的应用包括:触发器,函数和存储过程。 (一). 数据库设计题目如下 有一个图书出版发行管理系统,其主要业务规则如下: 一个作者可以编写多…