- 浏览: 409249 次
最新评论
-
iunknown:
909601686 写道我理解下来,之所以使用奇数应该不仅仅是 ...
以两军问题为背景来演绎Basic Paxos -
909601686:
我理解下来,之所以使用奇数应该不仅仅是为了判断谁赢。而是这样就 ...
以两军问题为背景来演绎Basic Paxos -
feclipse:
you are my hero! 看了你的图示之后,我的理解 ...
以两军问题为背景来演绎Basic Paxos -
lcc0739:
相当好!通俗易懂,看了好几篇paxos只有你这个最深入浅出!
以两军问题为背景来演绎Basic Paxos -
iunknown:
tangfu 写道hi,问一下,博主提供的库与pb兼容么,比如 ...
一个轻量的 wire format 解释器(google protobuf 的二进制格式)
spserver 是一个实现了半同步/半异步(Half-Sync/Half-Async)和领导者/追随者(Leader/Follower) 模式的服务器框架,能够简化 TCP server 的开发工作。
spserver 使用 c++ 实现,目前实现了以下功能:
1.封装了 TCP server 中接受连接的功能;
2.使用非阻塞型I/O和事件驱动模型,由主线程负责处理所有 TCP 连接上的数据读取和发送,因此连接数不受线程数的限制;
3.主线程读取到的数据放入队列,由一个线程池处理实际的业务。
4.一个 http 服务器框架,即嵌入式 web 服务器(请参考: SPWebServer:一个基于 SPServer 的 web 服务器框架)
0.6 版本之前只包含 Half-Sync/Half-Async 模式的实现,0.6 版本开始包含 Leader/Follower 模式的实现
0.7 版本开始支持 ssl 。把 socket 相关的操作抽象为一个 IOChannel 层,关于 openssl 的部分单独实现为一个 plugin 的形式,对于不使用 ssl 功能的用户,不需要引入 ssl 相关的头文件和库。
0.7.5 增加了一个 sptunnel 程序,是一个通用的 ssl proxy 。类似 stunnel 。
0.9.0 移植 spserver 到 windows 平台,需要在 windows 下编译 libevent 和 pthread 。
0.9.1 在 windows 平台,去掉了对 libevent 和 pthread 依赖,完全使用 iocp 和 windows 的线程机制实现了半同步半异步的框架。
0.9.2 移植了所有的功能到 windows 平台,同时新增加了 xyssl 的插件。
主页:
http://code.google.com/p/spserver/
源代码下载:
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz
http://code.google.com/p/spserver/downloads/list
在实现并发处理多事件的应用程序方面,有如下两种常见的编程模型:
ThreadPerConnection的多线程模型和事件驱动的单线程模型。
ThreadPerConnection的多线程模型
优点:简单易用,效率也不错。在这种模型中,开发者使用同步操作来编写程序,比如使用阻塞型I/O。使用同步操作的程序能够隐式地在线程的运行堆栈中维护应用程序的状态信息和执行历史,方便程序的开发。
缺点:没有足够的扩展性。如果应用程序只需处理少量的并发连接,那么对应地创建相应数量的线程,一般的机器都还能胜任;但如果应用程序需要处理成千上万个连接,那么为每个连接创建一个线程也许是不可行的。
事件驱动的单线程模型
优点:扩展性高,通常性能也比较好。在这种模型中,把会导致阻塞的操作转化为一个异步操作,主线程负责发起这个异步操作,并处理这个异步操作的结果。由于所有阻塞的操作都转化为异步操作,理论上主线程的大部分时间都是在处理实际的计算任务,少了多线程的调度时间,所以这种模型的性能通常会比较好。
缺点:要把所有会导致阻塞的操作转化为异步操作。一个是带来编程上的复杂度,异步操作需要由开发者来显示地管理应用程序的状态信息和执行历史。第二个是目前很多广泛使用的函数库都很难转为用异步操作来实现,即是可以用异步操作来实现,也将进一步增加编程的复杂度。
并发系统通常既包含异步处理服务,又包含同步处理服务。系统程序员有充分的理由使用异步特性改善性能。相反,应用程序员也有充分的理由使用同步处理简化他们的编程强度。
针对这种情况,ACE 的作者提出了 半同步/半异步 (Half-Sync/Half-Async) 模式。
《POSA2》上对这个模式的描述如下:
半同步/半异步 体系结构模式将并发系统中的异步和同步服务处理分离,简化了编程,同时又没有降低性能。该模式介绍了两个通信层,一个用于异步服务处理,另一个用于同步服务处理。
目标:
需要同步处理的简易性的应用程序开发者无需考虑异步的复杂性。同时,必须将性能最大化的系统开发者不需要考虑同步处理的低效性。让同步和异步处理服务能够相互通信,而不会使它们的编程模型复杂化或者过度地降低它们的性能。
解决方案:
将系统中的服务分解成两层,同步和异步,并且在它们之间增加一个排队层协调异步和同步层中的服务之间的通信。在独立线程或进程中同步地处理高层服务(如耗时长的数据库查询或文件传输),从而简化并发编程。相反,异步地处理底层服务(如从网络连接上读取数据),以增强性能。如果驻留在相互独立的同步和异步层中的服务必须相互通信或同步它们的处理,则应允许它们通过一个排队层向对方传递消息。
模式原文:Half-Sync/Half-Async: An Architectural Pattern for Efficient and Well-structured Concurrent I/O
http://www.cs.wustl.edu/~schmidt/PDF/HS-HA.pdf
中文翻译:http://blog.chinaunix.net/u/31756/showart_245841.html
如果上面关于 半同步/半异步 的说明过于抽象,那么可以看一个《POSA2》中提到的例子:
许多餐厅使用 半同步/半异步 模式的变体。例如,餐厅常常雇佣一个领班负责迎接顾客,并在餐厅繁忙时留意给顾客安排桌位,为等待就餐的顾客按序排队是必要的。领班由所有顾客“共享”,不能被任何特定顾客占用太多时间。当顾客在一张桌子入坐后,有一个侍应生专门为这张桌子服务。
下面来看一个使用 spserver 实现的简单的 line echo server 。
在最简单的情况下,使用 spserver 实现一个 TCP server 需要实现两个类:SP_Handler 的子类 和 SP_HandlerFactory 的子类。
SP_Handler 的子类负责处理具体业务。
SP_HandlerFactory 的子类协助 spserver 为每一个连接创建一个 SP_Handler 子类实例。
1.SP_Handler 生命周期
SP_Handler 和 TCP 连接一对一,SP_Handler 的生存周期和 TCP 连接一样。
当 TCP 连接被接受之后,SP_Handler 被创建,当 TCP 连接断开之后,SP_Handler将被 destroy。
2.SP_Handler 函数说明
SP_Handler 有 5 个纯虚方法需要由子类来重载。这 5 个方法分别是:
start:当一个连接成功接受之后,将首先被调用。返回 0 表示继续,-1 表示结束连接。
handle:当一个请求包接收完之后,将被调用。返回 0 表示继续,-1 表示结束连接。
error:当在一个连接上读取或者发送数据出错时,将被调用。error 之后,连接将被关闭。
timeout:当一个连接在约定的时间内没有发生可读或者可写事件,将被调用。timeout 之后,连接将被关闭。
close:当一个 TCP 连接被关闭时,无论是正常关闭,还是因为 error/timeout 而关闭。
3.SP_Handler 函数调用时机
当需要调用 SP_Handler 的 start/handle/error/timeout 方法时,相关的参数将被放入队列,然后由线程池来负责执行 SP_Handler 对应的方法。因此在 start/handle/error/timeout 中可以使用同步操作来编程,可以直接使用阻塞型 I/O 。
在发生 error 和 timeout 事件之后,close 紧跟着这两个方法之后被调用。
如果是程序正常指示结束连接,那么在主线程中直接调用 close 方法。
4.高级功能--MsgDecoder
这个 line echo server 比起常见的 echo server 有一点不同:只有在读到一行时才进行 echo。
这个功能是通过一个称为 MsgDecoder 的接口来实现的。不同的 TCP server 在应用层的传输格式上各不相同。
比如在 SMTP/POP 这一类的协议中,大部分命令是使用 CRLF 作为分隔符的。而在 HTTP 中是使用 Header + Body 的形式。
为了适应不同的 TCP server,在 spserver 中有一个 MsgDecoder 接口,用来处理这些应用层的协议。
比如在这个 line echo server 中,把传输协议定义为:只有读到一行时将进行 echo 。
那么相应地就要实现一个 SP_LineMsgDecoder ,这个 LineMsgDecoder 负责判断目前的输入缓冲区中是否已经有完整的一行。
MsgDecoder 的接口如下:
decode 方法对 inBuffer 里面的数据进行检查,看是否符合特定的要求。如果已经符合要求,那么返回 eOK ;如果还不满足要求,那么返回 eMoreData。比如 LineMsgDecoder 的 decode 方法的实现为:
spserver 默认提供了几个 MsgDecoder 的实现:
SP_DefaultMsgDecoder :它的 decode 总是返回 eOK ,即只要有输入就当作是符合要求了。
如果应用不设置 SP_Request->setMsgDecoder 的话,默认使用这个。
SP_LineMsgDecoder : 检查到有一行的时候,返回 eOK ,按行读取输入。
SP_DotTermMsgDecoder :检查到输入中包含了特定的 <CRLF>.<CRLF> 时,返回 eOK。
具体的使用例子可以参考示例:testsmtp 。
5.高级功能--实现聊天室
spserver 还提供了一个广播消息的功能。使用消息广播功能可以方便地实现类似聊天室的功能。具体的实现可以参考示例:testchat 。
6.libevent
spserver 使用 c++ 实现,使用了一个第三方库--libevent,以便在不同的平台上都能够使用最有效的事件驱动机制(Currently, libevent supports /dev/poll, kqueue(2), select(2), poll(2) and epoll(4). )。
呵呵,这两天重新看 POSA2 关于 reactor/proactor 的论述,理解的更多了一点。对于 spserver ,我也有这种看法。
reactor 和 proactor 的主要区别:
proactor 是由于异步操作的完成事件触发 handler 的执行,reactor 是由于产生了可以进行非阻塞操作的事件触发 handler 的执行。
libevent 可以说是一个 reactor 的实现。这一点通过对比 reactor 的 Event_Handler 和 libevent 的 callback ,可以和清楚地看到是非常类似的。两者在接口上,体现的是 IO 句柄是否可读或可写。
spserver 也就是相当于基于 libevent 的 reactor 封装了一个 proactor 出来。也是从对比 proactor 和的 Complete_Handler 和 SP_Handler 可以看出来。两者在接口上,体现的是异步操作的结果:proactor 是 Async_Result ,spserver 是 SP_Request->getMsgDecoder() 。
PS:上次提到的实现 connector 的想法,看来可以通过重构 spserver 为一个 proactor 而实现。
找到了一个用模拟 async IO 来实现 Proactor 的文章。
Comparing Two High-Performance I/O Design Patterns
下面粗体字部分描述的做法,也正是 spserver 的做法。
第一次看到 SCTP, qiezi 能做多一些介绍吗?
从上面的描述来看,SCTP 看来比 TCP 功能强大,但是目前很多的应用需要用到 SCTP 的加强功能吗?
SCTP好像是从linux-kernel-2.6.11才开始加入,具体也不是很清楚,在ubuntu里面安装libsctp就可以了。
IBM developerWorks上面有些介绍:
http://www-128.ibm.com/developerworks/cn/linux/l-sctp/index.html
这个协议提出是满早的,实现出来还是有点晚,目前windows上好像只有商业实现,vista上就不知道有没有,这里有一些实现方面的信息:
http://www.sctp.org/implementations.html
相对于TCP它的好处还是比较多的:
1、多流。TCP是单流的,掉包会阻塞整个流。SCTP是多流的,掉包只阻塞一个流。
2、数据报模式。有点像是UDP,但SCTP是可靠连接,它提供的数据报模式很适合做网络游戏数据包,结合多流,真是个不错的选择。
3、心跳探测。目前长连接网络应用程序都会自己发心跳包探测连接情况,SCTP在协议上实现了。
4、多宿(host)。一个连接可以在多条线路上进行,比如某个服务器有2块网卡,分别连接内网、外网,另一服务器也有这样2个网卡,可以在一个连接上绑定多个IP,它会在其中一条路径失效时,选择另一条路径。
5、防止DDOS攻击。
6、最后一个小的好处。没有TIME_WAIT,对于大量频繁的短连接还是非常有用的。
http://www.ptsn.net.cn/xueyuan/technic/switch/comment.php3?id=387
不同于TCP的是,SCTP提供了许多对于信令传输很重要的功能,同时,对于其他一些对性能和可靠性有额外需要的应用,它能提供传输优势来满足这些需要。SCTP和TCP最大的区别在于SCTP对多宿(multihoming)和部分有序(partial ordering)的支持。SCTP的多宿使得每个端点可被多个传输地址访问到,选择不同传输地址会导致两个端点间不同的数据路径,理想情况是在每一条路径都建立一条独立的拥塞控制。所以,SCTP的多主机拥塞控制仍需改进。
第一次看到 SCTP, qiezi 能做多一些介绍吗?
从上面的描述来看,SCTP 看来比 TCP 功能强大,但是目前很多的应用需要用到 SCTP 的加强功能吗?
我对于模板也是又爱又恨,看到模板的灵活和产生代码的强大,就会赞叹不已,不过一编译起来那个恨啊,真是咬牙切齿。ACE在以前的台PC上编译一次要30分钟。
我对于模板的记忆来一是自于无法使用 purify 进行测试,当时搞得好痛苦。
第二也是编译 ACE,再一台比较旧的开发机上,编译了两个小时。
从那以后,了解到模板在某些工具的支持上还是有点不足的,也就对使用模板不是很有兴趣了。
从这两天读 reactor/proactor 的描述来看, ACE 的实现有一个比较大的问题就是不符合目前流行的 DI 要求。各个类之间存在循环依赖,即使在 POSA2 的书上是少量的代码和完整的文字描述,依然看得云里雾里。
使用 python 也有几年的时间了,不过都只是用来做一些比 shell 脚本稍微复杂的工作。还没用 python 做过服务器开发。twisted 也久闻大名了,有时间要认真看看。
呵呵,这两天重新看 POSA2 关于 reactor/proactor 的论述,理解的更多了一点。对于 spserver ,我也有这种看法。
reactor 和 proactor 的主要区别:
proactor 是由于异步操作的完成事件触发 handler 的执行,reactor 是由于产生了可以进行非阻塞操作的事件触发 handler 的执行。
libevent 可以说是一个 reactor 的实现。这一点通过对比 reactor 的 Event_Handler 和 libevent 的 callback ,可以和清楚地看到是非常类似的。两者在接口上,体现的是 IO 句柄是否可读或可写。
spserver 也就是相当于基于 libevent 的 reactor 封装了一个 proactor 出来。也是从对比 proactor 和的 Complete_Handler 和 SP_Handler 可以看出来。两者在接口上,体现的是异步操作的结果:proactor 是 Async_Result ,spserver 是 SP_Request->getMsgDecoder() 。
PS:上次提到的实现 connector 的想法,看来可以通过重构 spserver 为一个 proactor 而实现。
当时考虑到要使用一个对象来实现 requestRead 的功能,主要是考虑到实际的环境中,有很多情况是无法用长度来判断的(比如 XMPP,LDAP等协议)。所以就使用一个 MsgDecoder 对象,在这个对象中可以实现任意复杂的协议解码。当然,使用对象来实现,也相应增加了复杂度。这个复杂度可以通过提供一些常用的 MsgDecoder 来简化。
目前我打算第一个基本实现的handle类(对应于twisted的Protocol)是样的:
如果是无法用长度来判断还接收多少数据的,就在Protocol上派生类处理就行了,想让线程处理时调用requestThread就行。
对于有长度前缀的,可以使用另一个基类:
可以看出来这个Proactor完全是在reactor上模拟的一个小玩意。
libevent 只是把《Unix Network Programming》中提到的“迭代服务器程序”做了一个封装。
http://iunknown.iteye.com/blog/41077
也就是说,如果使用 libevent 来实现“迭代服务器程序”,那么是最自然,最方便的。
在 UNP 中还提到了其他的服务器程序架构,这些不同的服务器程序架构,对于 I/O 的处理流程都有所不同。
如果使用 libevent 来实现其他的服务器程序架构,那么就需要使用一些迂回的方法。比如:可以使用 libevent 来实现 ThreadPerConnection 的模型,每个线程一个独立的 event_base,而且这个 event_base 也可能就只有一个 IO 句柄。当然,如果迂回的太多,就会使得 libevent 带来的坏处超过好处,这个时候就不如放弃 libevent ,另寻解决方法。
spserver 可以使用 event_base_dispatch 。目前使用 event_base_loop( eventArg.mEventBase, EVLOOP_ONCE ) 主要就是为了能够控制程序自行退出。其实 event_base_dispatch 的实现也就一行代码:
通常持久存储记录的信息是一些比较高层次上的状态。比如对于 smtp 来说,记录的可能是“服务器是否已经把邮件完整接收了”这样的状态,而不是某个数据是否发送成功了。如果已经接收了,但是在正式投递的时候,smtp 服务器异常退出,那么在 smtp 重启之后,就能够从持久存储中获得相应的信息,然后重试之前未完成的任务。当然,smtp 可以这样做的原因,也在于 smtp 协议在协议层的规定:
所以,对于服务器可靠性的保证,还在于协议的设计。
的确是这样。这两天正在看 apache 的代码,apache 在这方面应该是做的比较好的,通过发送信号,让 apache 优雅地结束掉。
spserver 在这方面的实现的确不够。
遇到的主要问题是:SP_Handler 的子类需要参与到这件事情上去。因为对于有些长连接的协议,如果 SP_Handler 不在处理过程中指示要结束连接,那么会导致队列总是不为空。上面提到 apache 实现了“优雅地结束”,其中有一个原因就在于 http 协议比较简单,一个 request 过来之后,返回一个 response 就可以了。而对于像 smtp 这类协议,如果在发出结束信号的时候,协议交互才刚刚开始,那么就需要等待这个连接进行完一次完整的 smtp 协议交互。这样等待的时间就不可预测了。
所以最终 spserver 对于这一点,采取了不作为的态度。这个也是一直在考虑改进的功能点。曾经考虑的解决方法是在 shutdown 方法中增加一个 timeout 时间,如果能在 timeout 时间内等到队列为空,那么优雅地结束。如果超过 timeout 时间,队列都不为空,那么就强行退出。不过还是不够完整。
要实现“优雅地关闭”,主要的考虑应该是想保证服务的可靠性?之前看过关于 XMPP 协议在可靠性方面的讨论,提到只在网络接口这一层保证可靠还不足于保证可靠性,而需要在协议的定义上来保证。可以参考这个帖子中引用的URL:http://iunknown.iteye.com/blog/57430。
另外,如果需要最大限度地保证服务器的可靠性,那么最彻底的解决方法其实是服务器端把每个连接对应的处理状态保存到持久存储中。在这种情况下,即使服务器异常退出(core dump,或者直接被 kill -9 ),也能从持久存储中恢复处理的状态。就目前了解的情况来看,有些 SMTP 服务器是这样做的。曾经也考虑过 XMPP 的实现,如果要保证每条消息的可靠传输,那么需要在接收到消息之后,需要把消息保存起来,然后再进行转发,转发成功之后,删除消息。这种策略在 《POSA2》 关于 ACT (异步完成标记)的讨论中,也提到过。
关于 proactor ,按 <POSA2> 的说法,“主动器模式是同步反应器模式的异步变体”。在这本书的描述中,主动器专门指封装了 windows 的 IOCP 和 POSIX 的 aio_* API 系列异步 I/O 机制的框架。而在我们这里讨论的上下文中,都没有提到使用 IOCP 或者 aio_* ,严格来说,这里体现的不是 proactor 模式,仍然就是 reactor 模式。
不过楼上的说法是“用户接口是proactor方式”。如果从这个角度来说,可以认为是 proactor 。<POSA2>中提到,“如果操作系统不支持异步操作,可以在实现中使用多线程模拟主动器模式的语意”。
在 spserver 的结构中, event loop 线程可以认为就是用来模拟异步操作的。线程池就是异步操作的发起者,一方面,msgdecoder 可以看成是线程池向 event loop 发起的一个读操作,msgdecoder 判断到读操作完成时就回调线程池;另一方面,response 就是由线程池向 event loop 发起的写操作,写操作完成之后,通过 completionhandler 来回调线程池。
对于不需要线程池的应用,直接使用 libevent 就是了。
spserver 是基于 libevent 来实现的,对于单线程高并发的应用,直接使用 libevent 就非常好了。
spserver 的主要作用是在 libevent 的基础上,增加一个线程池,使得能够方便地实现需要线程来处理的应用。
前几天和一个朋友讨论到这个问题,他也是强调这一点,认为 spserver 的结构不够灵活,不能很好地优化不需要线程池的应用。但如果了解 spserver 和 libevent 各自的侧重点,那么对于不需要线程池的应用,直接使用 libevent 就是行了。只有需要线程池的应用才使用 spserver 。
spserver 使用 c++ 实现,目前实现了以下功能:
1.封装了 TCP server 中接受连接的功能;
2.使用非阻塞型I/O和事件驱动模型,由主线程负责处理所有 TCP 连接上的数据读取和发送,因此连接数不受线程数的限制;
3.主线程读取到的数据放入队列,由一个线程池处理实际的业务。
4.一个 http 服务器框架,即嵌入式 web 服务器(请参考: SPWebServer:一个基于 SPServer 的 web 服务器框架)
0.6 版本之前只包含 Half-Sync/Half-Async 模式的实现,0.6 版本开始包含 Leader/Follower 模式的实现
0.7 版本开始支持 ssl 。把 socket 相关的操作抽象为一个 IOChannel 层,关于 openssl 的部分单独实现为一个 plugin 的形式,对于不使用 ssl 功能的用户,不需要引入 ssl 相关的头文件和库。
0.7.5 增加了一个 sptunnel 程序,是一个通用的 ssl proxy 。类似 stunnel 。
0.9.0 移植 spserver 到 windows 平台,需要在 windows 下编译 libevent 和 pthread 。
0.9.1 在 windows 平台,去掉了对 libevent 和 pthread 依赖,完全使用 iocp 和 windows 的线程机制实现了半同步半异步的框架。
0.9.2 移植了所有的功能到 windows 平台,同时新增加了 xyssl 的插件。
主页:
http://code.google.com/p/spserver/
源代码下载:
http://spserver.googlecode.com/files/spserver-0.6.src.tar.gz
http://code.google.com/p/spserver/downloads/list
在实现并发处理多事件的应用程序方面,有如下两种常见的编程模型:
ThreadPerConnection的多线程模型和事件驱动的单线程模型。
ThreadPerConnection的多线程模型
优点:简单易用,效率也不错。在这种模型中,开发者使用同步操作来编写程序,比如使用阻塞型I/O。使用同步操作的程序能够隐式地在线程的运行堆栈中维护应用程序的状态信息和执行历史,方便程序的开发。
缺点:没有足够的扩展性。如果应用程序只需处理少量的并发连接,那么对应地创建相应数量的线程,一般的机器都还能胜任;但如果应用程序需要处理成千上万个连接,那么为每个连接创建一个线程也许是不可行的。
事件驱动的单线程模型
优点:扩展性高,通常性能也比较好。在这种模型中,把会导致阻塞的操作转化为一个异步操作,主线程负责发起这个异步操作,并处理这个异步操作的结果。由于所有阻塞的操作都转化为异步操作,理论上主线程的大部分时间都是在处理实际的计算任务,少了多线程的调度时间,所以这种模型的性能通常会比较好。
缺点:要把所有会导致阻塞的操作转化为异步操作。一个是带来编程上的复杂度,异步操作需要由开发者来显示地管理应用程序的状态信息和执行历史。第二个是目前很多广泛使用的函数库都很难转为用异步操作来实现,即是可以用异步操作来实现,也将进一步增加编程的复杂度。
并发系统通常既包含异步处理服务,又包含同步处理服务。系统程序员有充分的理由使用异步特性改善性能。相反,应用程序员也有充分的理由使用同步处理简化他们的编程强度。
针对这种情况,ACE 的作者提出了 半同步/半异步 (Half-Sync/Half-Async) 模式。
引用
《POSA2》上对这个模式的描述如下:
半同步/半异步 体系结构模式将并发系统中的异步和同步服务处理分离,简化了编程,同时又没有降低性能。该模式介绍了两个通信层,一个用于异步服务处理,另一个用于同步服务处理。
目标:
需要同步处理的简易性的应用程序开发者无需考虑异步的复杂性。同时,必须将性能最大化的系统开发者不需要考虑同步处理的低效性。让同步和异步处理服务能够相互通信,而不会使它们的编程模型复杂化或者过度地降低它们的性能。
解决方案:
将系统中的服务分解成两层,同步和异步,并且在它们之间增加一个排队层协调异步和同步层中的服务之间的通信。在独立线程或进程中同步地处理高层服务(如耗时长的数据库查询或文件传输),从而简化并发编程。相反,异步地处理底层服务(如从网络连接上读取数据),以增强性能。如果驻留在相互独立的同步和异步层中的服务必须相互通信或同步它们的处理,则应允许它们通过一个排队层向对方传递消息。
模式原文:Half-Sync/Half-Async: An Architectural Pattern for Efficient and Well-structured Concurrent I/O
http://www.cs.wustl.edu/~schmidt/PDF/HS-HA.pdf
中文翻译:http://blog.chinaunix.net/u/31756/showart_245841.html
如果上面关于 半同步/半异步 的说明过于抽象,那么可以看一个《POSA2》中提到的例子:
许多餐厅使用 半同步/半异步 模式的变体。例如,餐厅常常雇佣一个领班负责迎接顾客,并在餐厅繁忙时留意给顾客安排桌位,为等待就餐的顾客按序排队是必要的。领班由所有顾客“共享”,不能被任何特定顾客占用太多时间。当顾客在一张桌子入坐后,有一个侍应生专门为这张桌子服务。
下面来看一个使用 spserver 实现的简单的 line echo server 。
class SP_EchoHandler : public SP_Handler { public: SP_EchoHandler(){} virtual ~SP_EchoHandler(){} // return -1 : terminate session, 0 : continue virtual int start( SP_Request * request, SP_Response * response ) { request->setMsgDecoder( new SP_LineMsgDecoder() ); response->getReply()->getMsg()->append( "Welcome to line echo server, enter 'quit' to quit.\r\n" ); return 0; } // return -1 : terminate session, 0 : continue virtual int handle( SP_Request * request, SP_Response * response ) { SP_LineMsgDecoder * decoder = (SP_LineMsgDecoder*)request->getMsgDecoder(); if( 0 != strcasecmp( (char*)decoder->getMsg(), "quit" ) ) { response->getReply()->getMsg()->append( (char*)decoder->getMsg() ); response->getReply()->getMsg()->append( "\r\n" ); return 0; } else { response->getReply()->getMsg()->append( "Byebye\r\n" ); return -1; } } virtual void error( SP_Response * response ) {} virtual void timeout( SP_Response * response ) {} virtual void close() {} }; class SP_EchoHandlerFactory : public SP_HandlerFactory { public: SP_EchoHandlerFactory() {} virtual ~SP_EchoHandlerFactory() {} virtual SP_Handler * create() const { return new SP_EchoHandler(); } }; int main( int argc, char * argv[] ) { int port = 3333; SP_Server server( "", port, new SP_EchoHandlerFactory() ); server.runForever(); return 0; }
在最简单的情况下,使用 spserver 实现一个 TCP server 需要实现两个类:SP_Handler 的子类 和 SP_HandlerFactory 的子类。
SP_Handler 的子类负责处理具体业务。
SP_HandlerFactory 的子类协助 spserver 为每一个连接创建一个 SP_Handler 子类实例。
1.SP_Handler 生命周期
SP_Handler 和 TCP 连接一对一,SP_Handler 的生存周期和 TCP 连接一样。
当 TCP 连接被接受之后,SP_Handler 被创建,当 TCP 连接断开之后,SP_Handler将被 destroy。
2.SP_Handler 函数说明
SP_Handler 有 5 个纯虚方法需要由子类来重载。这 5 个方法分别是:
start:当一个连接成功接受之后,将首先被调用。返回 0 表示继续,-1 表示结束连接。
handle:当一个请求包接收完之后,将被调用。返回 0 表示继续,-1 表示结束连接。
error:当在一个连接上读取或者发送数据出错时,将被调用。error 之后,连接将被关闭。
timeout:当一个连接在约定的时间内没有发生可读或者可写事件,将被调用。timeout 之后,连接将被关闭。
close:当一个 TCP 连接被关闭时,无论是正常关闭,还是因为 error/timeout 而关闭。
3.SP_Handler 函数调用时机
当需要调用 SP_Handler 的 start/handle/error/timeout 方法时,相关的参数将被放入队列,然后由线程池来负责执行 SP_Handler 对应的方法。因此在 start/handle/error/timeout 中可以使用同步操作来编程,可以直接使用阻塞型 I/O 。
在发生 error 和 timeout 事件之后,close 紧跟着这两个方法之后被调用。
如果是程序正常指示结束连接,那么在主线程中直接调用 close 方法。
4.高级功能--MsgDecoder
这个 line echo server 比起常见的 echo server 有一点不同:只有在读到一行时才进行 echo。
这个功能是通过一个称为 MsgDecoder 的接口来实现的。不同的 TCP server 在应用层的传输格式上各不相同。
比如在 SMTP/POP 这一类的协议中,大部分命令是使用 CRLF 作为分隔符的。而在 HTTP 中是使用 Header + Body 的形式。
为了适应不同的 TCP server,在 spserver 中有一个 MsgDecoder 接口,用来处理这些应用层的协议。
比如在这个 line echo server 中,把传输协议定义为:只有读到一行时将进行 echo 。
那么相应地就要实现一个 SP_LineMsgDecoder ,这个 LineMsgDecoder 负责判断目前的输入缓冲区中是否已经有完整的一行。
MsgDecoder 的接口如下:
class SP_MsgDecoder { public: virtual ~SP_MsgDecoder(); enum { eOK, eMoreData }; virtual int decode( SP_Buffer * inBuffer ) = 0; };
decode 方法对 inBuffer 里面的数据进行检查,看是否符合特定的要求。如果已经符合要求,那么返回 eOK ;如果还不满足要求,那么返回 eMoreData。比如 LineMsgDecoder 的 decode 方法的实现为:
int SP_LineMsgDecoder :: decode( SP_Buffer * inBuffer ) { if( NULL != mLine ) free( mLine ); mLine = inBuffer->getLine(); return NULL == mLine ? eMoreData : eOK; }
spserver 默认提供了几个 MsgDecoder 的实现:
SP_DefaultMsgDecoder :它的 decode 总是返回 eOK ,即只要有输入就当作是符合要求了。
如果应用不设置 SP_Request->setMsgDecoder 的话,默认使用这个。
SP_LineMsgDecoder : 检查到有一行的时候,返回 eOK ,按行读取输入。
SP_DotTermMsgDecoder :检查到输入中包含了特定的 <CRLF>.<CRLF> 时,返回 eOK。
具体的使用例子可以参考示例:testsmtp 。
5.高级功能--实现聊天室
spserver 还提供了一个广播消息的功能。使用消息广播功能可以方便地实现类似聊天室的功能。具体的实现可以参考示例:testchat 。
6.libevent
spserver 使用 c++ 实现,使用了一个第三方库--libevent,以便在不同的平台上都能够使用最有效的事件驱动机制(Currently, libevent supports /dev/poll, kqueue(2), select(2), poll(2) and epoll(4). )。
评论
36 楼
qiezi
2007-06-28
我把原来那个单线程的reactor增加了线程,接口和前面描述的基本一样:
对于HTTP这种没有长度前缀的,最好还是用reactor方式,当然用上面的proactor方式也是可以的,因为有上readall参数可以设置为false。好像和原来的reactor又兼容得不好了,我想是不是可以在收到数据后判断是否有读请求,如果没有则直接调用dataReceived。不过感觉和requestRead这种请求/回调的思想不一致。也不想再维护一个单线程的reactor、一个多线程reactor和一个多线程的proactor。
class EchoProtocol : public Protocol { protected: virtual void connectionMade() { requestRead(this, 4, readPackageLength, true, false); } static void readPackageLength(void* pthis, const char* ptr, size_t len, bool finished) { assert(finished); int packlen = strtol(...) // 解出长度 requestRead(pthis, packlen, readPackage, true, true); } };
对于HTTP这种没有长度前缀的,最好还是用reactor方式,当然用上面的proactor方式也是可以的,因为有上readall参数可以设置为false。好像和原来的reactor又兼容得不好了,我想是不是可以在收到数据后判断是否有读请求,如果没有则直接调用dataReceived。不过感觉和requestRead这种请求/回调的思想不一致。也不想再维护一个单线程的reactor、一个多线程reactor和一个多线程的proactor。
35 楼
iunknown
2007-06-25
iunknown 写道
引用
可以看出来这个Proactor完全是在reactor上模拟的一个小玩意。
呵呵,这两天重新看 POSA2 关于 reactor/proactor 的论述,理解的更多了一点。对于 spserver ,我也有这种看法。
reactor 和 proactor 的主要区别:
proactor 是由于异步操作的完成事件触发 handler 的执行,reactor 是由于产生了可以进行非阻塞操作的事件触发 handler 的执行。
libevent 可以说是一个 reactor 的实现。这一点通过对比 reactor 的 Event_Handler 和 libevent 的 callback ,可以和清楚地看到是非常类似的。两者在接口上,体现的是 IO 句柄是否可读或可写。
spserver 也就是相当于基于 libevent 的 reactor 封装了一个 proactor 出来。也是从对比 proactor 和的 Complete_Handler 和 SP_Handler 可以看出来。两者在接口上,体现的是异步操作的结果:proactor 是 Async_Result ,spserver 是 SP_Request->getMsgDecoder() 。
PS:上次提到的实现 connector 的想法,看来可以通过重构 spserver 为一个 proactor 而实现。
找到了一个用模拟 async IO 来实现 Proactor 的文章。
Comparing Two High-Performance I/O Design Patterns
下面粗体字部分描述的做法,也正是 spserver 的做法。
引用
Proposed solution
In this section, we will propose a solution to the challenge of designing a portable framework for the Proactor and Reactor I/O patterns. To demonstrate this solution, we will transform a Reactor demultiplexor I/O solution to an emulated async I/O by moving read/write operations from event handlers inside the demultiplexor (this is "emulated async" approach). The following example illustrates that conversion for a read operation:
* An event handler declares interest in I/O events (readiness for read) and provides the demultiplexor with information such as the address of a data buffer, or the number of bytes to read.
* Dispatcher waits for events (for example, on select());
* When an event arrives, it awakes up the dispatcher. The dispatcher performs a non- blocking read operation (it has all necessary information to perform this operation) and on completion calls the appropriate handler.
* The event handler handles data from the user-defined buffer, declares new interest, along with information about where to put the data buffer and the number bytes to read in I/O events. The event handler then returns control to the dispatcher.
As we can see, by adding functionality to the demultiplexor I/O pattern, we were able to convert the Reactor pattern to a Proactor pattern. In terms of the amount of work performed, this approach is exactly the same as the Reactor pattern. We simply shifted responsibilities between different actors. There is no performance degradation because the amount of work performed is still the same. The work was simply performed by different actors. The following lists of steps demonstrate that each approach performs an equal amount of work:
Standard/classic Reactor:
* Step 1) wait for event (Reactor job)
* Step 2) dispatch "Ready-to-Read" event to user handler ( Reactor job)
* Step 3) read data (user handler job)
* Step 4) process data ( user handler job)
Proposed emulated Proactor:
* Step 1) wait for event (Proactor job)
* Step 2) read data (now Proactor job)
* Step 3) dispatch "Read-Completed" event to user handler (Proactor job)
* Step 4) process data (user handler job)
With an operating system that does not provide an async I/O API, this approach allows us to hide the reactive nature of available socket APIs and to expose a fully proactive async interface. This allows us to create a fully portable platform-independent solution with a common external interface.
In this section, we will propose a solution to the challenge of designing a portable framework for the Proactor and Reactor I/O patterns. To demonstrate this solution, we will transform a Reactor demultiplexor I/O solution to an emulated async I/O by moving read/write operations from event handlers inside the demultiplexor (this is "emulated async" approach). The following example illustrates that conversion for a read operation:
* An event handler declares interest in I/O events (readiness for read) and provides the demultiplexor with information such as the address of a data buffer, or the number of bytes to read.
* Dispatcher waits for events (for example, on select());
* When an event arrives, it awakes up the dispatcher. The dispatcher performs a non- blocking read operation (it has all necessary information to perform this operation) and on completion calls the appropriate handler.
* The event handler handles data from the user-defined buffer, declares new interest, along with information about where to put the data buffer and the number bytes to read in I/O events. The event handler then returns control to the dispatcher.
As we can see, by adding functionality to the demultiplexor I/O pattern, we were able to convert the Reactor pattern to a Proactor pattern. In terms of the amount of work performed, this approach is exactly the same as the Reactor pattern. We simply shifted responsibilities between different actors. There is no performance degradation because the amount of work performed is still the same. The work was simply performed by different actors. The following lists of steps demonstrate that each approach performs an equal amount of work:
Standard/classic Reactor:
* Step 1) wait for event (Reactor job)
* Step 2) dispatch "Ready-to-Read" event to user handler ( Reactor job)
* Step 3) read data (user handler job)
* Step 4) process data ( user handler job)
Proposed emulated Proactor:
* Step 1) wait for event (Proactor job)
* Step 2) read data (now Proactor job)
* Step 3) dispatch "Read-Completed" event to user handler (Proactor job)
* Step 4) process data (user handler job)
With an operating system that does not provide an async I/O API, this approach allows us to hide the reactive nature of available socket APIs and to expose a fully proactive async interface. This allows us to create a fully portable platform-independent solution with a common external interface.
34 楼
qiezi
2007-06-23
iunknown 写道
第一次看到 SCTP, qiezi 能做多一些介绍吗?
从上面的描述来看,SCTP 看来比 TCP 功能强大,但是目前很多的应用需要用到 SCTP 的加强功能吗?
SCTP好像是从linux-kernel-2.6.11才开始加入,具体也不是很清楚,在ubuntu里面安装libsctp就可以了。
IBM developerWorks上面有些介绍:
http://www-128.ibm.com/developerworks/cn/linux/l-sctp/index.html
这个协议提出是满早的,实现出来还是有点晚,目前windows上好像只有商业实现,vista上就不知道有没有,这里有一些实现方面的信息:
http://www.sctp.org/implementations.html
相对于TCP它的好处还是比较多的:
1、多流。TCP是单流的,掉包会阻塞整个流。SCTP是多流的,掉包只阻塞一个流。
2、数据报模式。有点像是UDP,但SCTP是可靠连接,它提供的数据报模式很适合做网络游戏数据包,结合多流,真是个不错的选择。
3、心跳探测。目前长连接网络应用程序都会自己发心跳包探测连接情况,SCTP在协议上实现了。
4、多宿(host)。一个连接可以在多条线路上进行,比如某个服务器有2块网卡,分别连接内网、外网,另一服务器也有这样2个网卡,可以在一个连接上绑定多个IP,它会在其中一条路径失效时,选择另一条路径。
5、防止DDOS攻击。
6、最后一个小的好处。没有TIME_WAIT,对于大量频繁的短连接还是非常有用的。
33 楼
iunknown
2007-06-23
qiezi 写道
再补一个。
现在TCP是主流,以后如果SCTP成主流了,现有的各种应用层协议不知道会不会修改,我是比较看好SCTP,比起TCP来说省事太多了。
现在TCP是主流,以后如果SCTP成主流了,现有的各种应用层协议不知道会不会修改,我是比较看好SCTP,比起TCP来说省事太多了。
引用
http://www.ptsn.net.cn/xueyuan/technic/switch/comment.php3?id=387
不同于TCP的是,SCTP提供了许多对于信令传输很重要的功能,同时,对于其他一些对性能和可靠性有额外需要的应用,它能提供传输优势来满足这些需要。SCTP和TCP最大的区别在于SCTP对多宿(multihoming)和部分有序(partial ordering)的支持。SCTP的多宿使得每个端点可被多个传输地址访问到,选择不同传输地址会导致两个端点间不同的数据路径,理想情况是在每一条路径都建立一条独立的拥塞控制。所以,SCTP的多主机拥塞控制仍需改进。
第一次看到 SCTP, qiezi 能做多一些介绍吗?
从上面的描述来看,SCTP 看来比 TCP 功能强大,但是目前很多的应用需要用到 SCTP 的加强功能吗?
qiezi 写道
我对于模板也是又爱又恨,看到模板的灵活和产生代码的强大,就会赞叹不已,不过一编译起来那个恨啊,真是咬牙切齿。ACE在以前的台PC上编译一次要30分钟。
我对于模板的记忆来一是自于无法使用 purify 进行测试,当时搞得好痛苦。
第二也是编译 ACE,再一台比较旧的开发机上,编译了两个小时。
从那以后,了解到模板在某些工具的支持上还是有点不足的,也就对使用模板不是很有兴趣了。
32 楼
iunknown
2007-06-23
关于实现 connector 的计划,今天尝试了一下。
实现的结果不是一个 connector ,而是一个 dispatcher 。
增加了一个 SP_Dispatcher 类,有相应的使用 SP_Dispatcher 来实现 Echo Server 的例子。
testdispatcher.cpp
最新的代码
http://spserver.googlecode.com/files/spserver-0.5.src.tar.gza
实现的结果不是一个 connector ,而是一个 dispatcher 。
增加了一个 SP_Dispatcher 类,有相应的使用 SP_Dispatcher 来实现 Echo Server 的例子。
testdispatcher.cpp
最新的代码
http://spserver.googlecode.com/files/spserver-0.5.src.tar.gza
31 楼
qiezi
2007-06-23
再补一个。
现在TCP是主流,以后如果SCTP成主流了,现有的各种应用层协议不知道会不会修改,我是比较看好SCTP,比起TCP来说省事太多了。
现在TCP是主流,以后如果SCTP成主流了,现有的各种应用层协议不知道会不会修改,我是比较看好SCTP,比起TCP来说省事太多了。
30 楼
qiezi
2007-06-23
ACE是自己写了一套基础库,因为实现比较早,另外模板应用也非常广泛,模板实现通常又不能写到CPP文件里,所以它使用了一些技巧,看起来也显得乱,这跟C++语言本身也有很大关系,基本上可以说C++是成也模板(有点夸大),败也模板(不过分)。
ACE的基础库也很有可取之处,至少它没有回避多线程问题,这在STL里面可是得处处小心。<<Modern C++ Design>>里提到的一些模板策略模式,ACE也有用到,特别是把锁类型作为模板参数。我对于模板也是又爱又恨,看到模板的灵活和产生代码的强大,就会赞叹不已,不过一编译起来那个恨啊,真是咬牙切齿。ACE在以前的台PC上编译一次要30分钟。
ACE对于网络应用架构模式的应用通常都被称为典范,它支持多种网络模型、协议、线程模型、IO模型、操作系统平台,这点来说其它网络框架无人能及,不过正是大而全,作为应用框架来说还是显得原始,使用它要解决的问题比自己从头实现通常不会少。
POSA2这本书还没看过,看目录讲得很系统啊,一定要看看。以前只是看过ACE方面的书,实例为主。模式这东西,通常写过几年代码差不多都自然用到了(除非总在Ctrl+C,Ctrl+V),再看这方面的书一点就通,只是人家总结出来了想想的确是这么回事,可以作为架构和开发人员之间交流用的通用语言。
关于这个话题我没什么多的建议了,下面先抽时间看看POSA2再说
ACE的基础库也很有可取之处,至少它没有回避多线程问题,这在STL里面可是得处处小心。<<Modern C++ Design>>里提到的一些模板策略模式,ACE也有用到,特别是把锁类型作为模板参数。我对于模板也是又爱又恨,看到模板的灵活和产生代码的强大,就会赞叹不已,不过一编译起来那个恨啊,真是咬牙切齿。ACE在以前的台PC上编译一次要30分钟。
ACE对于网络应用架构模式的应用通常都被称为典范,它支持多种网络模型、协议、线程模型、IO模型、操作系统平台,这点来说其它网络框架无人能及,不过正是大而全,作为应用框架来说还是显得原始,使用它要解决的问题比自己从头实现通常不会少。
POSA2这本书还没看过,看目录讲得很系统啊,一定要看看。以前只是看过ACE方面的书,实例为主。模式这东西,通常写过几年代码差不多都自然用到了(除非总在Ctrl+C,Ctrl+V),再看这方面的书一点就通,只是人家总结出来了想想的确是这么回事,可以作为架构和开发人员之间交流用的通用语言。
关于这个话题我没什么多的建议了,下面先抽时间看看POSA2再说
29 楼
iunknown
2007-06-22
引用
ACE这个原始却笨重的框架
从这两天读 reactor/proactor 的描述来看, ACE 的实现有一个比较大的问题就是不符合目前流行的 DI 要求。各个类之间存在循环依赖,即使在 POSA2 的书上是少量的代码和完整的文字描述,依然看得云里雾里。
引用
twisted的架构是非常先进的,我感觉作为reactor实现,它已经超过ACE这个原始却笨重的框架了,另外它还提供了很多延迟回调(异步)方法,这和它的语法上的优势有很大关系。
使用 python 也有几年的时间了,不过都只是用来做一些比 shell 脚本稍微复杂的工作。还没用 python 做过服务器开发。twisted 也久闻大名了,有时间要认真看看。
28 楼
qiezi
2007-06-22
connector最主要的挑战在于异步,另外还有个容易忽略的东西就是host解析,所以实际上connector有个2阶段异步操作。在twisted里面,它先发起一个解析host的异步操作,解析完成时再发起connect异步操作。
twisted的架构是非常先进的,我感觉作为reactor实现,它已经超过ACE这个原始却笨重的框架了,另外它还提供了很多延迟回调(异步)方法,这和它的语法上的优势有很大关系。
twisted的架构是非常先进的,我感觉作为reactor实现,它已经超过ACE这个原始却笨重的框架了,另外它还提供了很多延迟回调(异步)方法,这和它的语法上的优势有很大关系。
27 楼
iunknown
2007-06-22
引用
可以看出来这个Proactor完全是在reactor上模拟的一个小玩意。
呵呵,这两天重新看 POSA2 关于 reactor/proactor 的论述,理解的更多了一点。对于 spserver ,我也有这种看法。
reactor 和 proactor 的主要区别:
proactor 是由于异步操作的完成事件触发 handler 的执行,reactor 是由于产生了可以进行非阻塞操作的事件触发 handler 的执行。
libevent 可以说是一个 reactor 的实现。这一点通过对比 reactor 的 Event_Handler 和 libevent 的 callback ,可以和清楚地看到是非常类似的。两者在接口上,体现的是 IO 句柄是否可读或可写。
spserver 也就是相当于基于 libevent 的 reactor 封装了一个 proactor 出来。也是从对比 proactor 和的 Complete_Handler 和 SP_Handler 可以看出来。两者在接口上,体现的是异步操作的结果:proactor 是 Async_Result ,spserver 是 SP_Request->getMsgDecoder() 。
PS:上次提到的实现 connector 的想法,看来可以通过重构 spserver 为一个 proactor 而实现。
26 楼
qiezi
2007-06-21
iunknown 写道
当时考虑到要使用一个对象来实现 requestRead 的功能,主要是考虑到实际的环境中,有很多情况是无法用长度来判断的(比如 XMPP,LDAP等协议)。所以就使用一个 MsgDecoder 对象,在这个对象中可以实现任意复杂的协议解码。当然,使用对象来实现,也相应增加了复杂度。这个复杂度可以通过提供一些常用的 MsgDecoder 来简化。
目前我打算第一个基本实现的handle类(对应于twisted的Protocol)是样的:
class Protocol { void dataReceived(void* p, size_t len); void requestThread(); }
如果是无法用长度来判断还接收多少数据的,就在Protocol上派生类处理就行了,想让线程处理时调用requestThread就行。
对于有长度前缀的,可以使用另一个基类:
class Proactor : public Protocol { void dataReceived(void* p, size_t len) { 取request列表,回调相应接口,根据要求调用requestThread } void requestRead(...); }
可以看出来这个Proactor完全是在reactor上模拟的一个小玩意。
25 楼
iunknown
2007-06-20
qiezi 写道
libevent把线程给阻隔了,通常操作系统提供的接口比如epoll/select还有各个socket操作函数都支持多个线程的,libevent把这个优点给掩盖了,当然使用它的理由也很充分,自己做超时处理还是比较麻烦,如果不做超时处理,我宁愿自己使用epoll了。多线程实现起来是点麻烦,ACE的线程池Reactor在处理每一个有事件的handle时,都会暂时清除掉该handle上的事件,所以它可以用多个线程跑事件循环,不过估计效率会比单线程低,事件部分还是单线程比较好。
libevent 只是把《Unix Network Programming》中提到的“迭代服务器程序”做了一个封装。
http://iunknown.iteye.com/blog/41077
也就是说,如果使用 libevent 来实现“迭代服务器程序”,那么是最自然,最方便的。
在 UNP 中还提到了其他的服务器程序架构,这些不同的服务器程序架构,对于 I/O 的处理流程都有所不同。
如果使用 libevent 来实现其他的服务器程序架构,那么就需要使用一些迂回的方法。比如:可以使用 libevent 来实现 ThreadPerConnection 的模型,每个线程一个独立的 event_base,而且这个 event_base 也可能就只有一个 IO 句柄。当然,如果迂回的太多,就会使得 libevent 带来的坏处超过好处,这个时候就不如放弃 libevent ,另寻解决方法。
qiezi 写道
spserver目前是自己处理事件循环的,使用event_dispatch应该也可以吧?自己处理有什么优点吗?当然其中一个好处是其它线程也可以调用shutdown。使用event_dispatch就会一直循环,这正是想要的。当需要退出时,只需要先关闭侦听端口,由于spserver使用了msgqueue,所以总是会有事件,等到各个线程处理完毕并且也没有其它WRITE事件了,就可以调用event_loopexit了。
spserver 可以使用 event_base_dispatch 。目前使用 event_base_loop( eventArg.mEventBase, EVLOOP_ONCE ) 主要就是为了能够控制程序自行退出。其实 event_base_dispatch 的实现也就一行代码:
int event_base_dispatch(struct event_base *event_base) { return (event_base_loop(event_base, 0)); }
qiezi 写道
持久存储是一个方案,不过通常服务器结束时都希望它把已经接收到的连接处理完毕,并不希望下次还要重启才能处理完成,重启后socket连接已经失效,就算把数据包给处理了也没办法回复。
通常持久存储记录的信息是一些比较高层次上的状态。比如对于 smtp 来说,记录的可能是“服务器是否已经把邮件完整接收了”这样的状态,而不是某个数据是否发送成功了。如果已经接收了,但是在正式投递的时候,smtp 服务器异常退出,那么在 smtp 重启之后,就能够从持久存储中获得相应的信息,然后重试之前未完成的任务。当然,smtp 可以这样做的原因,也在于 smtp 协议在协议层的规定:
引用
RFC2821
In sending a positive completion reply to the end of data indication, the receiver takes full responsibility for the message (see section 6.1).
In sending a positive completion reply to the end of data indication, the receiver takes full responsibility for the message (see section 6.1).
所以,对于服务器可靠性的保证,还在于协议的设计。
24 楼
qiezi
2007-06-20
libevent把线程给阻隔了,通常操作系统提供的接口比如epoll/select还有各个socket操作函数都支持多个线程的,libevent把这个优点给掩盖了,当然使用它的理由也很充分,自己做超时处理还是比较麻烦,如果不做超时处理,我宁愿自己使用epoll了。多线程实现起来是点麻烦,ACE的线程池Reactor在处理每一个有事件的handle时,都会暂时清除掉该handle上的事件,所以它可以用多个线程跑事件循环,不过估计效率会比单线程低,事件部分还是单线程比较好。
spserver目前是自己处理事件循环的,使用event_dispatch应该也可以吧?自己处理有什么优点吗?当然其中一个好处是其它线程也可以调用shutdown。使用event_dispatch就会一直循环,这正是想要的。当需要退出时,只需要先关闭侦听端口,由于spserver使用了msgqueue,所以总是会有事件,等到各个线程处理完毕并且也没有其它WRITE事件了,就可以调用event_loopexit了。
持久存储是一个方案,不过通常服务器结束时都希望它把已经接收到的连接处理完毕,并不希望下次还要重启才能处理完成,重启后socket连接已经失效,就算把数据包给处理了也没办法回复。
spserver目前是自己处理事件循环的,使用event_dispatch应该也可以吧?自己处理有什么优点吗?当然其中一个好处是其它线程也可以调用shutdown。使用event_dispatch就会一直循环,这正是想要的。当需要退出时,只需要先关闭侦听端口,由于spserver使用了msgqueue,所以总是会有事件,等到各个线程处理完毕并且也没有其它WRITE事件了,就可以调用event_loopexit了。
持久存储是一个方案,不过通常服务器结束时都希望它把已经接收到的连接处理完毕,并不希望下次还要重启才能处理完成,重启后socket连接已经失效,就算把数据包给处理了也没办法回复。
23 楼
iunknown
2007-06-20
引用
目前看到的很多网络服务器框架都有一个共同的问题,退出比较草率,直接结束掉事件循环。。。。。。
在检测到退出标志后,关闭侦听端口,再等待队列处理空,然后结束掉线程池
在检测到退出标志后,关闭侦听端口,再等待队列处理空,然后结束掉线程池
的确是这样。这两天正在看 apache 的代码,apache 在这方面应该是做的比较好的,通过发送信号,让 apache 优雅地结束掉。
spserver 在这方面的实现的确不够。
遇到的主要问题是:SP_Handler 的子类需要参与到这件事情上去。因为对于有些长连接的协议,如果 SP_Handler 不在处理过程中指示要结束连接,那么会导致队列总是不为空。上面提到 apache 实现了“优雅地结束”,其中有一个原因就在于 http 协议比较简单,一个 request 过来之后,返回一个 response 就可以了。而对于像 smtp 这类协议,如果在发出结束信号的时候,协议交互才刚刚开始,那么就需要等待这个连接进行完一次完整的 smtp 协议交互。这样等待的时间就不可预测了。
所以最终 spserver 对于这一点,采取了不作为的态度。这个也是一直在考虑改进的功能点。曾经考虑的解决方法是在 shutdown 方法中增加一个 timeout 时间,如果能在 timeout 时间内等到队列为空,那么优雅地结束。如果超过 timeout 时间,队列都不为空,那么就强行退出。不过还是不够完整。
要实现“优雅地关闭”,主要的考虑应该是想保证服务的可靠性?之前看过关于 XMPP 协议在可靠性方面的讨论,提到只在网络接口这一层保证可靠还不足于保证可靠性,而需要在协议的定义上来保证。可以参考这个帖子中引用的URL:http://iunknown.iteye.com/blog/57430。
另外,如果需要最大限度地保证服务器的可靠性,那么最彻底的解决方法其实是服务器端把每个连接对应的处理状态保存到持久存储中。在这种情况下,即使服务器异常退出(core dump,或者直接被 kill -9 ),也能从持久存储中恢复处理的状态。就目前了解的情况来看,有些 SMTP 服务器是这样做的。曾经也考虑过 XMPP 的实现,如果要保证每条消息的可靠传输,那么需要在接收到消息之后,需要把消息保存起来,然后再进行转发,转发成功之后,删除消息。这种策略在 《POSA2》 关于 ACT (异步完成标记)的讨论中,也提到过。
22 楼
qiezi
2007-06-19
我大致看了一下代码,还有一点疑问,也是我比较关心的。
目前看到的很多网络服务器框架都有一个共同的问题,退出比较草率,直接结束掉事件循环,实际上有些正在接收、正在处理或正在发送中的操作都没有优雅地结束掉,所以实际在编写服务器程序时,我宁愿自己写这部分,使用每连接一线程方式做这种还是比较简单的,当然可能性能问题比较严重,不过最近做的工作都是TCP短连接的,同时忙的线程也不会太多,问题还不大。
通常在结束时先关闭侦听端口,再等待处理中的操作结束,这个结束当然包括它处理完了并且发送完回复,使用libevent的event_loopexit,或者是spserver的shutdown,我都没有看到这样一个优雅的关闭过程。我自己也使用了libevent,发现还是比较难操作,特别交给线程池处理的操作,使用event_loopexit可能不行,像spserver一样自己处理循环应该还是可以做到的,只需要在检测到退出标志后,关闭侦听端口,再等待队列处理空,然后结束掉线程池。
目前看到的很多网络服务器框架都有一个共同的问题,退出比较草率,直接结束掉事件循环,实际上有些正在接收、正在处理或正在发送中的操作都没有优雅地结束掉,所以实际在编写服务器程序时,我宁愿自己写这部分,使用每连接一线程方式做这种还是比较简单的,当然可能性能问题比较严重,不过最近做的工作都是TCP短连接的,同时忙的线程也不会太多,问题还不大。
通常在结束时先关闭侦听端口,再等待处理中的操作结束,这个结束当然包括它处理完了并且发送完回复,使用libevent的event_loopexit,或者是spserver的shutdown,我都没有看到这样一个优雅的关闭过程。我自己也使用了libevent,发现还是比较难操作,特别交给线程池处理的操作,使用event_loopexit可能不行,像spserver一样自己处理循环应该还是可以做到的,只需要在检测到退出标志后,关闭侦听端口,再等待队列处理空,然后结束掉线程池。
21 楼
iunknown
2007-06-18
关于 MsgDecoder 再作一些说明。
在 qiezi 的实现中,使用 requestRead 来指定需要读取的内容。这个函数的用意和 MsgDecoder 是一样的。
只不过 requestRead 里有指定是否采用 threading 的参数,MsgDecoder 没有。
当时考虑到要使用一个对象来实现 requestRead 的功能,主要是考虑到实际的环境中,有很多情况是无法用长度来判断的(比如 XMPP,LDAP等协议)。所以就使用一个 MsgDecoder 对象,在这个对象中可以实现任意复杂的协议解码。当然,使用对象来实现,也相应增加了复杂度。这个复杂度可以通过提供一些常用的 MsgDecoder 来简化。
同时,在具体的使用过程中,很多时候是否采用 threading ,不一定能够在读取内容之前就决定,而是需要在读取内容之后才能决定。曾经考虑在 MsgDecoder 中加入指定是否需要 threading 的接口。主要是担心在应用程序不断修改演进的过程中,可能原来不使用 block system call 的功能,后来又使用了,如果实现者忘记修改关于 threading 的设定,会导致整个程序出现问题。所以目前还是没有在 MsgDecoder 中加入这个功能。
class Protocol { /** * bytes: 要读取的字节数 * callback: 读取完成时的回调委托函数 * recvall: 是否要读取所有请求的字节数才回调 * threading: 是否交由线程池 * * void delegate(char[] data, bool finished) 参数: * data: 数据 * finished: 是否已经接收完成。对于recvall为true的,回调时这个参数肯定为true,否则可能为true或false */ void requestRead(size_t bytes, void delegate(char[], bool) callback, bool recvall, bool threading); }
在 qiezi 的实现中,使用 requestRead 来指定需要读取的内容。这个函数的用意和 MsgDecoder 是一样的。
只不过 requestRead 里有指定是否采用 threading 的参数,MsgDecoder 没有。
当时考虑到要使用一个对象来实现 requestRead 的功能,主要是考虑到实际的环境中,有很多情况是无法用长度来判断的(比如 XMPP,LDAP等协议)。所以就使用一个 MsgDecoder 对象,在这个对象中可以实现任意复杂的协议解码。当然,使用对象来实现,也相应增加了复杂度。这个复杂度可以通过提供一些常用的 MsgDecoder 来简化。
同时,在具体的使用过程中,很多时候是否采用 threading ,不一定能够在读取内容之前就决定,而是需要在读取内容之后才能决定。曾经考虑在 MsgDecoder 中加入指定是否需要 threading 的接口。主要是担心在应用程序不断修改演进的过程中,可能原来不使用 block system call 的功能,后来又使用了,如果实现者忘记修改关于 threading 的设定,会导致整个程序出现问题。所以目前还是没有在 MsgDecoder 中加入这个功能。
20 楼
qiezi
2007-06-17
我说的是我上面的想法,是reactor作底层;处理部分是半同步/半异步;requestRead这种算是proactor的做法,请求/回调,当然我那个想法里面回调可以是反应器所在的线程,也可以是用来处理的线程池,原理上和proactor是相近的。
19 楼
iunknown
2007-06-17
引用
应该算是reactor + proactor + 你这个半同步/半异步。
实现方式是reactor,反应式的;用户接口则是以proactor方式来调用,请求/回调;处理部分算是半同步/半异步。
实现方式是reactor,反应式的;用户接口则是以proactor方式来调用,请求/回调;处理部分算是半同步/半异步。
关于 proactor ,按 <POSA2> 的说法,“主动器模式是同步反应器模式的异步变体”。在这本书的描述中,主动器专门指封装了 windows 的 IOCP 和 POSIX 的 aio_* API 系列异步 I/O 机制的框架。而在我们这里讨论的上下文中,都没有提到使用 IOCP 或者 aio_* ,严格来说,这里体现的不是 proactor 模式,仍然就是 reactor 模式。
不过楼上的说法是“用户接口是proactor方式”。如果从这个角度来说,可以认为是 proactor 。<POSA2>中提到,“如果操作系统不支持异步操作,可以在实现中使用多线程模拟主动器模式的语意”。
在 spserver 的结构中, event loop 线程可以认为就是用来模拟异步操作的。线程池就是异步操作的发起者,一方面,msgdecoder 可以看成是线程池向 event loop 发起的一个读操作,msgdecoder 判断到读操作完成时就回调线程池;另一方面,response 就是由线程池向 event loop 发起的写操作,写操作完成之后,通过 completionhandler 来回调线程池。
18 楼
iunknown
2007-06-16
qiezi 写道
你目前的实现方式是收到数据后,由msgdecoder来判断是否完成(是这样没错吧?),然后放入线程池我觉得欠点灵活性。
比如我有这样一个应用A,它非常简单,不需要放入线程池,收到后只需要根据长度就可以判断是否收完了,而且不耗CPU,所以不需要线程池,这时msgdecoder也是多余的了。
另一个应用B收到数据后要计算,所以交由线程池处理。
比如我有这样一个应用A,它非常简单,不需要放入线程池,收到后只需要根据长度就可以判断是否收完了,而且不耗CPU,所以不需要线程池,这时msgdecoder也是多余的了。
另一个应用B收到数据后要计算,所以交由线程池处理。
对于不需要线程池的应用,直接使用 libevent 就是了。
spserver 是基于 libevent 来实现的,对于单线程高并发的应用,直接使用 libevent 就非常好了。
spserver 的主要作用是在 libevent 的基础上,增加一个线程池,使得能够方便地实现需要线程来处理的应用。
前几天和一个朋友讨论到这个问题,他也是强调这一点,认为 spserver 的结构不够灵活,不能很好地优化不需要线程池的应用。但如果了解 spserver 和 libevent 各自的侧重点,那么对于不需要线程池的应用,直接使用 libevent 就是行了。只有需要线程池的应用才使用 spserver 。
17 楼
qiezi
2007-06-14
应该算是reactor + proactor + 你这个半同步/半异步。
实现方式是reactor,反应式的;用户接口则是以proactor方式来调用,请求/回调;处理部分算是半同步/半异步。
实现方式是reactor,反应式的;用户接口则是以proactor方式来调用,请求/回调;处理部分算是半同步/半异步。
发表评论
-
把开源项目从googlecode转移到github
2015-03-14 23:07 1136前几天看到说 googlecode 准备关闭了,花一个晚上把以 ... -
spcached: memcached 的克隆实现,支持 windows 平台
2009-11-03 22:51 1669memcached 没有官方的 windows 发布版本,只有 ... -
一个轻量的 wire format 解释器(google protobuf 的二进制格式)
2009-10-07 21:38 5095google 的 protobuf 项目,底层的二进制格式设计 ... -
SPHiveDB: 基于 sqlite 的数据库服务器
2009-05-23 23:39 4283在 share nothing 的架构中,如果数据规模很大,为 ... -
spmemvfs: 在内存中加载/保存 sqlite3 数据库
2009-05-01 18:38 3520关于 sqlite3 有很多的介绍文章,这里就不提了。 说一 ... -
SPSmtpGate: SMTP 反垃圾邮件网关
2009-04-16 22:01 1461SPSmtpGate 是一个反垃圾 ... -
SPSmtpServer: 一个基于 SPServer 的 SMTP 服务器框架
2009-03-15 15:08 1558在 SPServer 中增加了一个 smtp 服务器框架。在框 ... -
SPDataPickle: c语言的结构体和 xml/json/protobuf 的自动转化
2009-01-20 00:20 7980SPDataPickle 是一种轻便高效的结构化数据和xml/ ... -
用 state pattern 简化 json 解释器的实现
2008-07-26 11:02 1559之前用 state pattern 实现过 xml 的解释器( ... -
SPNetKit:http/smtp/pop3/memcached 的客户端库
2008-01-13 17:41 3258SPNetKit 主要是一个常见应用层协议的客户端库,使用 C ... -
SPProcPool: Unix/Linux 上的进程池服务器框架
2007-12-09 11:30 4736SPProcPool 是一个 linux/unix ... -
sptalk:基于 spserver/spxml/spdict 实现的 jabber 服务器
2007-05-25 14:50 3248在 3 年前因为工作需要,搞过一段时间 jabberd 1.4 ... -
spcached : memcached 的多线程实现
2007-05-15 14:43 9906实现 spcached 的目的:不是与 memcached 进 ... -
SPWebServer:一个基于 SPServer 的 web 服务器框架
2007-05-10 16:07 14047看到这个题目,估计很多人会问:为什么要再实现一个 web 服务 ... -
spdict:红黑树(RedBlackTree),平衡树(BalancedTree),SkipList 的实现
2007-04-27 12:11 5410对着 MIT 的 《Introduction to Algor ... -
spxml:使用 state pattern 实现 xml pull/dom parser
2007-01-17 22:26 6159spxml 是一个实现了 pull 和 dom 两种解释模型 ... -
Build a thread pool in C
2006-12-11 16:27 15142想找个轻便的 thread pool ...
相关推荐
Oracle用户密码 hash加密 C#实现方式 Oracle版本:Oracle11g以前的版本
THRIFT 是一个开源的跨语言服务开发框架,由 Facebook 开发并贡献给了 Apache 基金会。它允许开发者定义数据类型和服务接口,然后自动生成多种编程语言的代码,简化了分布式系统之间的通信。在 THRIFT 开发教程中,...
在这个场景中,"Scripps paramanipulaçãode hahs"可能是指利用特定参数对哈希值进行处理或操纵的过程,这可能是为了安全或数据验证的目的。 在密码学中,哈希函数被广泛用于密码存储。当用户创建一个新密码时,...
数据库基础测验20241113.doc
微信小程序下拉选择组件
DICOM文件+DX放射平片—数字X射线图像DICOM测试文件,文件为.dcm类型DICOM图像文件文件,仅供需要了解DICOM或相关DICOM开发的技术人员当作测试数据或研究使用,请勿用于非法用途。
<项目介绍> - 基于双流 Faster R-CNN 网络的 图像篡改检测 - 不懂运行,下载完可以私聊问,可远程教学 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 --------
c语言
# 基于Arduino的天文数据库管理系统 ## 项目简介 本项目是一个基于Arduino的天文数据库管理系统,旨在为Arduino设备提供一个完整的天文数据库,包括星星、星系、星团等天体数据。项目支持多种语言的星座名称,并提供了详细的天体信息,如赤道坐标、视星等。 ## 项目的主要特性和功能 星座目录包含88个星座,提供拉丁语、英语和法语的缩写和全名。 恒星目录包含494颗亮度达到4等的恒星。 梅西耶目录包含110个梅西耶天体。 NGC目录包含3993个NGC天体,亮度达到14等。 IC目录包含401个IC天体,亮度达到14等。 天体信息每个天体(不包括星座)提供名称、命名、相关星座、赤道坐标(J2000)和视星等信息。 恒星额外信息对于恒星,还提供每年在赤经和赤纬上的漂移以及视差。 ## 安装使用步骤 1. 安装库使用Arduino IDE的库管理器安装本项目的库。 2. 解压数据库将db.zip解压到SD卡中。
# 基于JSP和SQL Server的维修管理系统 ## 项目简介 本项目是一个基于JSP和SQL Server的维修管理系统,旨在提供一个高效、便捷的维修管理解决方案。系统涵盖了从维修订单的创建、管理到配件的录入、更新等多个功能模块,适用于各类维修服务行业。 ## 项目的主要特性和功能 1. 用户管理 管理员和客户的注册与登录。 管理员信息的管理与更新。 客户信息的创建、查询与更新。 2. 维修订单管理 维修订单的创建、查询与更新。 维修回执单的创建与管理。 3. 配件管理 配件信息的录入与更新。 配件库存的管理与查询。 4. 评价与反馈 客户对维修服务的评价记录。 系统反馈信息的收集与管理。 5. 数据加密与安全 使用MD5加密算法对用户密码进行加密存储。 通过过滤器实现登录验证,确保系统安全。 ## 安装使用步骤
HUAWEI DevEco Studio,以下简称DevEco Studio)是基于IntelliJ IDEA Community开源版本打造,为运行在HarmonyOS和OpenHarmony系统上的应用和服务(以下简称应用/服务)提供一站式的开发平台。 作为一款开发工具,除了具有基本的代码开发、编译构建及调测等功能外,DevEco Studio还具有如下特点: - 高效智能代码编辑:支持ArkTS、JS、C/C++等语言的代码高亮、代码智能补齐、代码错误检查、代码自动跳转、代码格式化、代码查找等功能,提升代码编写效率。更多详细信息,请参考[编辑器使用技巧] - 低代码可视化开发:丰富的UI界面编辑能力,支持自由拖拽组件和可视化数据绑定,可快速预览效果
《计算机视觉技术》实验报告-8.1提取车辆轮廓
随着现在网络的快速发展,网上管理系统也逐渐快速发展起来,网上管理模式很快融入到了许多生活之中,随之就产生了“小徐影城管理系统”,这样就让小徐影城管理系统更加方便简单。 对于本小徐影城管理系统的设计来说,系统开发主要是采用java语言技术,在整个系统的设计中应用MySQL数据库来完成数据存储,具体根据小徐影城管理系统的现状来进行开发的,具体根据现实的需求来实现小徐影城管理系统网络化的管理,各类信息有序地进行存储,进入小徐影城管理系统页面之后,方可开始操作主控界面,主要功能包括管理员:首页、个人中心、用户管理、电影类型管理、放映厅管理、电影信息管理、购票统计管理、系统管理、订单管理,用户前台;首页、电影信息、电影资讯、个人中心、后台管理、在线客服等功能。 本论文主要讲述了小徐影城管理系统开发背景,该系统它主要是对需求分析和功能需求做了介绍,并且对系统做了详细的测试和总结。具体从业务流程、数据库设计和系统结构等多方面的问题。望能利用先进的计算机技术和网络技术来改变目前的小徐影城管理系统状况,提高管理效率。
<项目介绍> - SIFT特征提取算法C++与Matlab实现 - 不懂运行,下载完可以私聊问,可远程教学 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载学习,也适合小白学习进阶,当然也可作为毕设项目、课程设计、作业、项目初期立项演示等。 3、如果基础还行,也可在此代码基础上进行修改,以实现其他功能,也可用于毕设、课设、作业等。 下载后请首先打开README.md文件(如有),仅供学习参考, 切勿用于商业用途。 --------
数据介绍 数据名称:国家自然、社科基金部分名单 数据年份:1991-2024年 样本数量:10万+ 数据格式:PDF、excel
卓晴
as-bundled-clients
学习时最后的资料包括面试等信息
# 基于Spring Boot和Ant Design的雨选课系统 ## 项目简介 雨选课系统是一个基于Spring Boot和Ant Design框架构建的前后端分离的选课系统。该系统实现了学生选课、成绩查询、教师成绩修改、课程编辑、课程新增等功能。登录信息使用Redis存储,并支持课程图片的上传功能。 ## 项目的主要特性和功能 1. 用户登录与权限管理 学生、教师和管理员分别有不同的登录权限。 登录信息使用Redis进行存储。 2. 课程管理 学生可以查看可选课程列表,并进行选课和退选操作。 教师可以查看自己教授的课程,并修改学生成绩。 管理员可以编辑和新增课程。 3. 成绩管理 学生可以查询自己的成绩。 教师可以修改学生的成绩。 4. 图片上传 支持课程图片的上传和展示。 5. 日志记录 系统记录请求和响应的日志信息,便于问题追踪和性能分析。
数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目),含有代码注释,满分大作业资源,新手也可看懂,期末大作业、课程设计、高分必看,下载下来,简单部署,就可以使用。该项目可以作为课程设计期末大作业使用,该系统功能完善、界面美观、操作简单、功能齐全、管理便捷,具有很高的实际应用价值。 数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅点餐系统源码+数据库+文档说明(高分项目)数据库期末作业基于Python+mysql的餐厅