免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: linux_c_py_php
打印 上一主题 下一主题

[C++] [如何用状态机思想降低Server端开发复杂度? (GIT分支提供http server的实现框架示例) [复制链接]

论坛徽章:
0
91 [报告]
发表于 2012-12-04 19:00 |只看该作者
本帖最后由 reiase 于 2012-12-04 19:24 编辑

回复 90# OwnWaterloo

这贴就是讨论异步和CPU多路复用的好吧...{:2_169:}

我不认为C语言控制结构少,coroutine在C里用静态变量很容易实现,比如strtok这个标准库函数。

你可以说,静态变量不是线程安全的...但是,使用coroutine的脚本语言基本上都不具备真正使用多核CPU的能力(比如python、lua;Perl和Ruby没研究过),所以coroutine不涉及多核多线程要面临的问题。某种程度上讲,coroutine真的没什么。我估计,你在C++里结合template和静态变量,也能实现可重入的strtok。

我主要是做图像视频处理的,偶尔写写服务器代码,并且一直喜欢select方式。今天看到LZ帖子,突发奇想,觉得其实select、多线程多进程、coroutine,其实本质上是一样的。都是调度多个任务,主要看任务调度由谁做,自己做、OS做还是编程语言做。问题的本质是调度,而不是执行路径。

补充一点,哥在Python引入generater那个版本开始用上yield了...

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
92 [报告]
发表于 2012-12-04 20:51 |只看该作者
lua的coroutine还没看懂, 谁给我讲讲, 虽然我买了一本中文lua程序设计, 但看了1/4不想看了, 开始质疑自己学那么多脚本不如赶紧把xmpp多琢磨琢磨.

OwnWaterloo 大神, 能不能讲一下coroutine呢, 比如它的callback在阻塞操作中yield 另一个corutine后返回了, 主程序怎么知道那个阻塞操作是否完成了呢, 不会还是轮询去问或者做个管道通知之类的吧, coroutine看你说应该是解放此类工作的机制啊.

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
93 [报告]
发表于 2012-12-05 00:59 |只看该作者
回复 91# reiase

>>     这贴就是讨论异步和CPU多路复用的好吧...

我本来就不是来讨论server的,而是被sw召唤于是进来(带有跑题嫌疑地)回了一帖。


>> 我不认为C语言控制结构少,coroutine在C里用静态变量很容易实现,比如strtok这个标准库函数。

是可以实现,strtok怎么实现就怎么实现。问题是这种重复性劳动太伤神。

如果大家都在钻木取火,那继续这种劳动可能就感觉是天经地义的。
而有些先驱发明了打火机。而我这种发明不了这些概念的,但看见其它一些人在用打火机了,于是每次钻木心里就乱烦。

strtok(还包括前面提到的iterator)是可以实现。当然可以,直接看源代码就是了。
它们太简单,需要分析的context太少,所以体现不出工作量。而gui的例子又稍显抽象。

我以前在CU举过一个rc4加密的例子。 有一些代码在这里: http://bbs.chinaunix.net/thread-3610928-6-1.html
但具体实现其实不是重点,重点是如何设计rc4加密的api。

rc4就是根据key产生一个"无限"的byte流。 用这个byte流与明文异或得密文。密文与相同的byte流(由同一个key产生)异或还原明文。
问题是怎么设计这个"无限"byte流的api?

我能想到的让使用者最舒服的api就是:

  1. typedef struct { ... } rc4_t;
  2. void rc4_init(rc4_t* self, char const* key, size_t len); // 初始化rc4状态
  3. unsigned char rc4_next(rc4_t* self); // 获取下一byte
复制代码
这样的api就只完成了"根据key产生一个无限byte流"的工作。 没有与明文byte流的来源、 以及与它异或的工作"绑死"在一起。
至于要加密的东西是字符串,还是文件,还是网络包,甚至某些协议格式只选择加密一部分,等等等等, 都可以使用这样的api。

但这样的api对实现者来说是很累的事情。wiki里给出的算法伪代码就是这样:

  1. i := 0
  2. j := 0
  3. while GeneratingOutput:
  4.     i := (i + 1) mod 256
  5.     j := (j + S[i]) mod 256
  6.     swap values of S[i] and S[j]
  7.     K := S[(S[i] + S[j]) mod 256]
  8.     output K
  9. endwhile
复制代码
每次output就产生一个byte。 下次从之前的"环境"继续执行。
如果有coroutine, 将output换成yield就基本完事了。
但如果没有, 就要"人肉"去分析哪些环境是恢复执行所必需的, 比如这里的i,j,S; 以及如何恢复, 比如那个while 死循环。


"没有coroutine就不知道怎么编程了"其实是夸张的吐槽而已。 如果必须在C里面做一个rc4的库, 那该怎么实现还是就怎么实现。
准确的意思是这样一次又一次的去重复人肉实现coroutine很累。。。 不过是在模拟其他语言里原本就有的特性。 能避免我就不愿意去碰。

而且这样的工作很多, strtok是, iterator是, mbstate_t(和它完成类似功能的结构体太多了)也是。
太多太多了。

是很容易实现,因为已经在C里习惯了被迫去做这些事情。 但如果有coroutine可以更容易的实现。



>> 我主要是做图像视频处理的,偶尔写写服务器代码,并且一直喜欢select方式。今天看到LZ帖子,突发奇想,觉得其实select、多线程多进程、coroutine,其实本质上是一样的。都是调度多个任务,主要看任务调度由谁做,自己做、OS做还是编程语言做。问题的本质是调度,而不是执行路径。

这是两个问题。 先要解决调度。
解决之后后续的工作里可能就会有需要从某个点"挂起",将控制权返回,并且还能从挂起点"恢复"执行。
这种工作太多了。。。



>> 补充一点,哥在Python引入generater那个版本开始用上yield了...

虽然都叫yield, 但generator并不是coroutine。 coroutine更高级一些。 因为coroutine可以用来实现generator, 反之不行。
前面的rc4的例子其实是一个generator。 但可以用coroutine实现。

而coroutine是可以"组合"的,是一个执行的thread。可以将coroutine.yield类比pthread_exit。 但coroutine.yield是可以恢复的, pthread_exit就停止了。

比如, f0调用f1, f1调用f2。 它们当中都可能有coroutine.yield。 用f0创建一个coroutine, 每次resume就可以得到f0或者f1或者f2中yield的值。
而generator就不行, f()必须"马上使用", for x in f() : ...  否则没法将f()产生的elements继续往上向f的调用者传递。

这种coroutine的用法可以用来实现"恢复语意"的异常。
用yield当作throw,祖先调用点根据resume得到的值选择适当的处理后恢复执行,或者放弃整个执行 —— 这就和C++,Java,C#,Python它们的异常都是终结语意相同了,一旦抛出就再也回不到抛出点。

还比如前面那个for_each(collection, on_elem)的例子。 coroutine可以将这个现有的函数直接转换成iterator, generator不行。
coroutine的这种用法可以扩展到lexer/parser上。
先写一个 lexer(stream, on_token),然后用coroutine将它转换成一次产生一个token。
这比直接写一个每次产生一个token的lexer要容易 —— 同前面的,这事不是不能做, 而是容易程度。
而且还存在某些现有的库, 它的lexer的api已经写成 lexer(stream, on_token)的形式了, 直接用coroutine转换就得到一次返回一个token的lexer了。

让lexer的编写更自然, "就像在处理整个文件一样", 然后将一次返回一个token的工作交给coroutine去完成。


Python里真正能称为coroutine的是green(还是叫green thread来着)。 不过和lua的也有区别。
green和fiber类似, 是对称的; lua是非对称的。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
94 [报告]
发表于 2012-12-05 01:42 |只看该作者
回复 92# linux_c_py_php

server我搞不懂。
coroutine没法解决调度的事,调度得自己去做。 pli里的例子也是自己维护了一个coroutine的表+select。

这事你可以找@starwing83, 我记得他给我说过用coroutine重构过一个server (#1) ,代码编写更容易了, 而且效率反而更高了。

#1: 我也只能说这是个server, 也许是和网络游戏相关的, 有专门的通信格式。
我不知道到底什么东西叫server。

emacs内建了网络编程的库, 并且附带了一个库, 可以监听某个端口。 而且还附带了一个emacsclient程序, 可以向emacs发送数据, 在这个emacs进程里执行lisp代码。
emacs把它叫server。

clojure有一个库叫nrepl(network read eval print loop)。 也是监听某个端口, 让其他客户端去连接, 并在这个JVM进程里执行代码。
客户端包括emacs/eclipse等等, 目的是辅助编程, code completion, document 等等。
还有jark, 这个客户端是由OCaml写的, 它会读取一个clojure源文件的内容送给server去执行。 目的是避免JVM重复启动, 太慢了。 OCaml是编译为本地代码, 相对快很多。
貌似nrepl不像emacs那样明确地提出, 但这叫不叫server?

goagent, ssh -D 开启的是不是也是server?

如果我写个程序(比如用emacs lisp或者clojure), 让它监听80端口, 并始终返回一个固定的response。 Content-Type text/html, body硬编码。
这是不是也是server?
如果更进一步, 根据http request里的uri产生不同的response, 这还叫server?
再进一步, 所有的response不全由emacs lisp产生, 部分可能由python什么的产生。 对某些uri, 可能调用某个python脚本, 用它的输出(stdout)作为response的body。
这依然是server?
最后, 这个emacs lisp的程序可能还不只是监听80端口, 可能还要监听25。 这个程序依然应该叫server?


于是, 当一个人说"我在开发一个server时", 到底是什么意思? 尤其是这怎么和mysql联系上的? mysql不是一个独立的server吗? 我彻底混乱了。

我接触网络方面的编程很晚。
以前有一个机会摆在面前没有去珍惜。
Windows程序设计的老师要求完成一个mspaint的类似物, 并且要求所有paint的进程看到的结果都是相同的。 这个诡异的要求本来是出于好意让我们练习网络编程的。
但我直接用内存映射去弄了, 因为那时候就只会这个。

之后唯一一个用C做的和网络相关的就是wake on lan。
再后来和网络相关的都不是用C在做了。 python, emacs lisp 而现在是clojure; 可能还会用haskell, 因为clojure发布得要求机器有jre。 但应该没机会用C去写。
总之, 不是从"正统"的"APUE"那条路过来的, 不知道"如果没提及上下文那server就是指..."这种行话。

有一些术语应该是通用的。
比如goagent那个应该都叫做"http proxy server", ssh -D那个是"socks4(a)/5 proxy server",

  1. request --server-> stdin+env --program-> stdout --server-> response
复制代码
应该是CGI。

其他更多的术语就没办法了。
比如一个haskell程序员给我说dedicated/standalone server,或者clojure程序员给我说ring,或者Python程序给我说WSGI ,或者Java程序说servelet 都行, 我知道他是什么意思。
但我估计C里面这些对应物都不叫这个名字。。。

这是我的错, 但目前暂时没有精力去修复了。。。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
95 [报告]
发表于 2012-12-05 02:01 |只看该作者
回复 92# linux_c_py_php

前面废话了这么多,主要是因为最近在做web(其实我也不知道应该叫web还是叫network还是叫front end)方面的事。
然后突然被打断了!!!  又得用C去做一些肮脏的事情。。。 (不是C做事很肮脏,是事情本身很肮脏)。

又都一知半解没什么好总结的, 于是占用CU的地盘胡乱写一通。


至于你说的coroutine与阻塞, 如果要我现想的话, 可能就会直接设置非阻塞读, 能读到数据就处理, 读不到就让步。
还是前面的:
1. 这事你得问sw, 他做过的, 我没做过这事。 而且我现在主要还是在Windows上, 在这上面学习网络编程是不是太非主流了? Windows下连signal都很少有机会用到。
2. "COoperative"-ROUTINE本身不解决调度的事。
某些本来应该是一个完整步骤的,因为特殊原因得拆分成若干次分批执行。 coroutine解决的是这个。
这个完整的步骤依然可以写成一个完整的步骤, 而不需要人肉去考虑保存那些context才能继续从"上次没做完的地方"继续, 以及如何继续。 然后用coroutine去将它们拆开。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
96 [报告]
发表于 2012-12-05 02:06 |只看该作者
回复 15# starwing83

mysql这里纰漏那里纰漏,为毛还这么流行?

PS:这纯粹是为了第5000回帖没话找话。。。

论坛徽章:
0
97 [报告]
发表于 2012-12-05 08:04 |只看该作者
回复 93# OwnWaterloo

1. rc4那个源码没时间细看了,出差呢。不过用静态变量不需要考虑怎么恢复,而是要考虑怎么重置。比如一个二维数组遍历的代码:
  1. #include <stdio.h>
  2. int getNext(int max_i, int max_j){
  3.         static int i, j, call;
  4.         if (max_i == -1){
  5.                 i = 0;
  6.                 j = 0;
  7.                 return;
  8.         }
  9.         for (;i<max_i;i++, j=0){
  10.                 for (;j<max_j;){
  11.                         printf("%d,%d\n", i, j);
  12.                         j++;
  13.                         return 0;
  14.                 }
  15.         }
  16. }

  17. int main(){
  18.         int I;
  19.         getNext(-1,-1);
  20.         getNext(3,3);
  21.         getNext(3,3);
  22.         getNext(3,3);
  23.         getNext(3,3);
  24.         getNext(3,3);
  25.         getNext(3,3);
  26.         getNext(3,3);
  27.         getNext(3,3);
  28.         getNext(3,3);
  29.         getNext(3,3);
  30.         getNext(3,3);
  31.         getNext(3,3);
  32.         return 0;
  33. }
复制代码
这是一个用静态变量遍历一个二维数组的例子,状态的复杂程度应该和rc4那个代码很接近,比lua那个例子多了几行重置静态变量的代码,但我不觉得真的困难。我之前用类似方法做过遍历图像中所有连通域。

2. generator和coroutine差不多吧,你说的只能遍历的是C++的iterator。generator靠next函数驱动状态转移,跟coroutine的resume类似...晚上回来再说了



论坛徽章:
0
98 [报告]
发表于 2012-12-05 21:18 |只看该作者
回复 93# OwnWaterloo


    继续话题,Python里边,yield没有那些伪装线程的API,比如running,status,create和resume等,因为Python有线程库,所以不需要用coroutine伪装线程。我去比较了下Python的yield和lua的coroutine,发现lua不能够向coroutine传值,只能重置。而python下generator对象有next()方法,类似resume,还有send(arg)方法,用来传值。先面给一个例子:
  1. def test_yield():
  2.         for x in range(10):
  3.                 y = yield x
  4.                 print "in test_yield ", y

  5. a = test_yield()
  6. print (a.next())
  7. print (a.send(20))
  8. print (a.next())
复制代码
输出为:
0
in test_yield  20
1
in test_yield  None
2
Python下的yield更为强大一些。

后面有些讨论,你太过于具体到某个问题了,我没看懂。

总结下我的观点吧:
1. 逻辑路径,只能针对单个任务讨论,逻辑路径是静态不变的,不可能2个任务一种逻辑路径,3个任务另外一种路径;
2. 所以,多任务讨论的就是调度,跨任务的逻辑路径绝对自找苦吃;
3. 并发的大前提是所有任务都假装自己独占设备和CPU。并发要实现的就是让多个任务都成功假装自己独占设备和CPU;
4. 对CPU就是时间分片和时间片分配。对于一般性问题,我倾向于认为操作系统研究了几十年,已经研究好了一般多任务是CPU多路复用;
5. 对于具体问题,coroutine和select只是针对具体问题的具体优化。

一句话来说,lua、coroutine都只是小玩意儿,哪有那么NB。最起码,如果一个不可靠的coroutine挂了,整个server都死在那边,你有办法workround这个问题吗

论坛徽章:
5
狮子座
日期:2013-08-20 10:12:24午马
日期:2013-11-23 18:04:102015年辞旧岁徽章
日期:2015-03-03 16:54:152015亚冠之德黑兰石油
日期:2015-06-29 18:11:1115-16赛季CBA联赛之新疆
日期:2024-02-21 10:00:53
99 [报告]
发表于 2012-12-06 15:28 |只看该作者
回复 96# OwnWaterloo


    看来我也得为3000贴努力了不是= =

我说的那个Server不是你想的那种……………………那个是一个客户端的Server的存根……

是这样的,客户端有一个对象叫做Server。然后客户端就直接和这个Server通讯,Server封装了通讯过程,本质上是Client而已……客户端的其他部分直接把Server当作一个本地的服务来用了,实际上后面是连着socket的。

不知道说清楚没……

论坛徽章:
4
水瓶座
日期:2013-09-06 12:27:30摩羯座
日期:2013-09-28 14:07:46处女座
日期:2013-10-24 14:25:01酉鸡
日期:2014-04-07 11:54:15
100 [报告]
发表于 2012-12-06 22:07 |只看该作者
最近在看lua程序设计, 终于算是看完了协程那一章了, 知道OwnWaterloo说的原理了,协程基本就像是一个操作系统的调度器,把本应并发的事情搞串行了,依次干完, 结果因为没有锁反而效率更高了.

resume就是把执行权交给别人, 阻塞等结果, yield就是把执行权向上返回, 同时返回结果给resume.
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP