【转载】使用事件模型 & libev学习
参考这篇文章:
http://www.ibm.com/developerworks/cn/linux/l-cn-edntwk/
这里面使用的是 libev ,不是libevent
Nodejs就是采用libev作为底层库。
先要进行安装,找到了这篇文章:
http://www.cnblogs.com/wunaozai/p/3950249.html
- 搜索了很多关于如何学网络编程的博客和问答。大致都是推荐学一个网络库,至于C++网络库有那么几个,各有各的好处。
这里就选这个代码量少了,方便入门,等有一定的基础后,再看看“学之者生,用之着死”的ace或者有可能成为C++标准网络库的boost::asio,这个都是后话了。
libev原地址国外的,访问不了。从csdn上下载了一份(link),要1分的,不过我下载过了,再下载就不用了。
拷贝到03机器上,解压。
因为libev是C写的,估计用C++的编译器也没啥用。所以看看默认的编译器行不行。
- ./configure --prefix=/home/work/data/installed/libev
- make
- make install
- 能够看到安装信息:
- ----------------------------------------------------------------------
- Libraries have been installed in:
- /home/work/data/installed/libev/lib
- If 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 variable
- during execution
- - add LIBDIR to the `LD_RUN_PATH' environment variable
- during 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() and ld.so() manual pages.
- ----------------------------------------------------------------------
- /bin/mkdir -p '/home/work/data/installed/libev/include'
- /usr/bin/install -c -m ev.h ev++.h event.h '/home/work/data/installed/libev/include'
- /bin/mkdir -p '/home/work/data/installed/libev/share/man/man3'
- /usr/bin/install -c -m ev. '/home/work/data/installed/libev/share/man/man3'
- make[]: Leaving directory `/home/work/data/installed/libev-4.15'
然后在代码目录/home/work/data/code/libev_demo里写了个示例程序libev_demo.c
- #include <stdio.h>
- #include <ev.h> //ev库头文件,注意这是C头文件,C++是ev++.h
- //定义一个ev_TYPE 的结构体
- ev_io stdin_watcher; //定义一个stdin的观测者
- ev_timer timeout_watcher; //定义一个timeout的观测者
- //所有的watcher的回调函数都有相似的特点
- //当stdin有可读的数据时,将会调用下面这个回调函数
- static void stdin_cb(EV_P_ ev_io *w, int revents) {
- puts("stdin ready");
- //每一次调用都必须用对应的停止函数,手动的停止其watcher
- //应该是表示处理过了
- ev_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, , EV_READ);
- ev_io_start(loop, &stdin_watcher);
- //初始化一个定时器watcher,然后启动它
- //只有一次,没有重复的5.5秒定时
- ev_timer_init(&timeout_watcher, timeout_cb, 5.5, );
- ev_timer_start(loop, &timeout_watcher);
- //这里开始loop,等待时间开始计时
- ev_run(loop, );
- return ;
- }
然后写个Makefile
- CXX=gcc
- INCPATH= \
- /home/work/data/installed/libev/include
- DEP_LDFLAGS= \
- -L/home/work/data/installed/libev/lib
- DEP_LDLIBS= \
- -lev
- TARGET= libev_demo
- all : $(TARGET)
- libev_demo : *.c
- $(CXX) -o $@ $^ -I$(INCPATH) $(DEP_LDFLAGS) $(DEP_LDLIBS)
- .PHONY : all clean
- clean :
- rm -rf $(TARGET)
然后编译并运行:
- $ make
- gcc -o libev_demo libev_demo.c -I/home/work/data/installed/libev/include -L/home/work/data/installed/libev/lib -lev
- $ ./libev_demo
- timeout
- $ ./libev_demo
- ls
- stdin ready
- $ ls
- libev_demo libev_demo.c Makefile
运行程序后,在超时或者输入字符时会中断退出。并且字符会继续传递到命令行。
另外,以上是C程序,文件后缀不能是cxx。对于cxx,要用C++的头文件。
下面在相同的目录里面写了一个C++的程序 libev_demo.cxx
- #include <ev++.h>
- #include <iostream>
- #include <unistd.h>
- using namespace std;
- class IOWatcher {
- public:
- IOWatcher(int fd, unsigned int events) {
- m_io.set(fd, events);
- m_io.set<IOWatcher, &IOWatcher::CallBack>(this);
- m_io.start();
- }
- void CallBack(ev::io &w, int revents) {
- cout << "In IOWatcher::CallBack" << endl;
- w.stop();
- }
- private:
- ev::io m_io;
- };
- int main() {
- ev::default_loop loop;
- IOWatcher my_io(STDIN_FILENO, ev::READ);
- loop.run();
- return ;
- }
然后Makefile也要改一下,包括依赖的文件和所使用的编译器:
- #CXX=gcc
- CXX=/opt/compiler/gcc-4.8.2/bin/g++
- INCPATH= \
- /home/work/data/installed/libev/include
- DEP_LDFLAGS= \
- -L/home/work/data/installed/libev/lib
- DEP_LDLIBS= \
- -lev -Wl,-rpath -Wl,LIBDIR
- TARGET= libev_demo
- all : $(TARGET)
- #libev_demo : *.c
- libev_demo : *.cxx
- $(CXX) -o $@ $^ -I$(INCPATH) $(DEP_LDFLAGS) $(DEP_LDLIBS)
- .PHONY : all clean
- clean :
- rm -rf $(TARGET)
编译并运行:
- $ make clean
- rm -rf libev_demo
- $ make
- /opt/compiler/gcc-4.8./bin/g++ -o libev_demo libev_demo.cxx -I/home/work/data/installed/libev/include
-L/home/work/data/installed/libev/lib -lev -Wl,-rpath -Wl,LIBDIR- $ ./libev_demo
- ls
- In IOWatcher::CallBack
- $ ls
- libev_demo libev_demo.c libev_demo.cxx Makefile
可以看到,C++的程序也能够正确编译和执行。
用gcc -E选项来编译前面的C程序,可以看到预处理的,带有宏展开的程序。
- gcc -E libev_demo.c -I/home/work/data/installed/libev/include > tmp
然后原来的c文件,展开成了2271行的文件。
其中main函数部分:
- int main(int argc, char **args) {
- struct ev_loop *loop = ev_default_loop ();
- do { do { ((ev_watcher *)(void *)((&stdin_watcher)))->active = ((ev_watcher *)(void *)((&stdin_watcher)))->pending = ; ( (ev_watcher *)(void *)(((&stdin_watcher))))->priority = (); (((&stdin_watcher)))->cb = ((stdin_cb)); } while (); do { ((&stdin_watcher))->fd = (()); ((&stdin_watcher))->events = ((EV_READ)) | EV__IOFDSET; } while (); } while ();
- ev_io_start(loop, &stdin_watcher);
- do { do { ((ev_watcher *)(void *)((&timeout_watcher)))->active = ((ev_watcher *)(void *)((&timeout_watcher)))->pending = ; ( (ev_watcher *)(void *)(((&timeout_watcher))))->priority = (); (((&timeout_watcher)))->cb = ((timeout_cb)); } while (); do { ((ev_watcher_time *)((&timeout_watcher)))->at = ((5.5)); ((&timeout_watcher))->repeat = (()); } while (); } while ();
- ev_timer_start(loop, &timeout_watcher);
- ev_run(loop, );
- return ;
- }
Libev通过一个struct ev_loop结构表示一个事件驱动的框架。
在这个框架里面通过ev_xxx结构,ev_init、ev_xxx_set、ev_xxx_start接口箱这个事件驱动的框架里面注册事件监控器,当相应的事件监控器的事件出现时,便会触发该事件监控器的处理逻辑,去处理该事件。处理完之后,这些监控器进入到下一轮的监控中。
符合一个标准的事件驱动状态的模型。
Libev 除了提供了基本的三大类事件(IO事件、定时器事件、信号事件)外还提供了周期事件、子进程事件、文件状态改变事件等多个事件,这里我们用三大基本事件写一个例子。
- #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[];
- memset(buf, , sizeof(buf));
- puts("In IO action");
- read(STDIN_FILENO, buf, sizeof(buf));
- buf[] = '\0';
- printf("String input: %s\n", buf);
- //ev_io_stop(main_loop, io_w); // 不注释的话,只会监测一遍IO输入
- }
- 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();
- ev_init(&io_w, io_action);
- ev_io_set(&io_w, STDIN_FILENO, EV_READ);
- ev_init(&timer_w, timer_action);
- // #define ev_timer_set(ev, after_, repeat_)
- ev_timer_set(&timer_w, , );
- ev_init(&signal_w, signal_action);
- // Ctrl+C
- 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, );
- }
其中主要的时间线循环如下:
- 使用libev的核心是事件循环,可以用 ev_default_loop 或 ev_loop_new 函数创建循环,或者直接使用 EV_DEFAULT 宏,
- 区别是 ev_default_loop 创建的事件循环不是线程安全的,而 ev_loop_new 创建的事件循环不能捕捉信号和子进程的观察器。
- 大多数情况下,可以像下面这样使用:
- if (!ev_default_loop ())
- fatal ("could not initialise libev, bad $LIBEV_FLAGS in environment?");
- 或者明确选择一个后端:
- struct ev_loop *epoller = ev_loop_new (EVBACKEND_EPOLL | EVFLAG_NOENV);
- if (!epoller)
- fatal ("no epoll found here, maybe it hides under your chair");
- 如果需要动态分配循环的话,建议使用 ev_loop_new 和 ev_loop_destroy 。
- 在创建子进程后,且想要使用事件循环时,需要先在子进程中调用 ev_default_fork 或 ev_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 用来暂停和重启事件循环,比如在程序挂起的时候。
- ev_run是libev的核心,他主要做了五件事情:
- 1.更新更改的FD事件
- 2.进行必要的sleep
- 3.backend_poll收集pending的IO事件
- 4.收集pending的timer事件
- 5.调用所有pending的事件
以上示例程序中,如果注释掉"ev_break(main_loop, EVBREAK_ALL);",那么程序会在调用三个回调函数后才会结束(所有的ev_xxx_stop都被调用了),否则一直监听着。也就是说,只要三种事件都发生,三个ev_xxx_stop都被调用了,那么就会出了event_loop循环。
具体ev_run和ev_break的参数说明如下:
- void ev_run (EV_P_ int flags);
- void ev_break (EV_P_ int how);
- 同样我们这里比较关注flags和how这两个参数。flags有下面这几个:
- :默认值。一直循环进行处理,直到外部引用计数==0或者是显示退出。
- EVRUN_NOWAIT:运行一次,poll时候不会等待。如果有pending事件进行处理,否则立即返回。
- EVRUN_ONCE:运行一次,poll时候会等待至少一个event发生,处理完成之后返回。
- 而how有下面这几个:
- EVBREAK_ONE:只是退出一次ev_run这个调用。通常来说使用这个就可以了。
- EVBREAK_ALL:退出所有的ev_run调用。这种情况存在于ev_run在pengding处理时候会递归调用。
使用libev的核心是事件循环,可以用 ev_default_loop 或 ev_loop_new 函数创建循环,或者直接使用 EV_DEFAULT 宏,区别是 ev_default_loop 创建的事件循环不是线程安全的,而 ev_loop_new 创建的事件循环不能捕捉信号和子进程的观察器。
各个观察器(watcher)
ev_io 获取标准输入
ev_timer 创建一个 x 秒之后启动的计时器
ev_periodic 创建一个小时为单位的周期定时器
- static void clock_cb (struct ev_loop *loop, ev_periodic *w, int revents)
- {
- // ... its now a full hour (UTC, or TAI or whatever your clock follows)
- }
- ev_periodic hourly_tick;
- ev_periodic_init (&hourly_tick, clock_cb, ., ., );
- ev_periodic_start (loop, &hourly_tick);
或者自定义周期方式:- #include <math.h>
- static ev_tstamp my_scheduler_cb (ev_periodic *w, ev_tstamp now)
- {
- return now + (. - fmod (now, .));
- }
- ev_periodic_init (&hourly_tick, clock_cb, ., ., my_scheduler_cb);
- 从当前时间开始:
- ev_periodic_init (&hourly_tick, clock_cb, fmod (ev_now (loop), 3600.), 3600., 0);
- ev_periodic_start (loop, &hourly_tick);
示例代码如下:
- #include <stdio.h>
- #include <string.h>
- #include <unistd.h>
- #include <ev.h>
- static void periodic_callback(struct ev_loop *loop,ev_periodic * w, int revents)
- {
- printf("every 3 seconds\n");
- //ev_break(loop,EVBREAK_ALL);
- }
- //ev_tstamp=double
- static ev_tstamp periodic_scheduler_callback(ev_periodic *w,ev_tstamp now)
- {
- return now+; //注意时间要加上个now,是一个绝对时间
- }
- int main(int argc, char **args)
- {
- struct ev_loop * main_loop=ev_default_loop();
- ev_periodic periodic_watcher;
//下面这个是第3个参数为3 是一个表达式- ev_init(&periodic_watcher, periodic_callback);
- ev_periodic_set(&periodic_watcher,,,);
- ev_periodic_start(main_loop,&periodic_watcher);
- ev_run(main_loop,);
//如果时间周期计算方式,不能通过一个表达式来表示,那么可以通过一个函数来表示,放在set的第4个参数- ev_init(&periodic_watcher,periodic_callback);
- ev_periodic_set(&periodic_watcher,,,periodic_scheduler_callback);
- ev_periodic_start(main_loop,&periodic_watcher);
- ev_run(main_loop,);
//注意上下两部分是等价的,注释掉一个就可以看到相同的效果- return ;
- }
ev_signal 在处理信号
ev_child fork 一个新进程,给它安装一个child处理器等待进程结束。
写一个程序来实验一下。开始怎么都写不对,看了该系列的下一篇以为因为没有set。其实不是,是因为没有ev_run(main_loop, 0);(link)
- #include <sys/types.h>
- #include <stdio.h>
- #include <unistd.h>
- #include <signal.h>
- #include <string.h>
- #include <sys/unistd.h>
- #include <ev.h>
- static void child_cb (EV_P_ ev_child *w, int revents)
- {
- ev_child_stop (EV_A_ w);
- printf ("process %d exited with status %x\n", w->rpid, w->rstatus);
- }
- int main(int argc, char** argv) {
- ev_child cw;
- struct ev_loop *main_loop = ev_default_loop();
- pid_t pid = fork ();
- if (pid < ) {
- // error
- }
- else if (pid == )
- {
- // the forked child executes here
- puts("I am here sleeping");
- sleep(); // seconds
- puts("I am waking up");
- //exit (1);
- }
- else
- {
- puts("F am here sleeping");
- sleep(); // let child go first
- puts("F am waking up");
- ev_child_init (&cw, child_cb, pid, );
- printf("F get, child: %d\n", pid);
- //ev_child_set(&cw, pid, 0);
- //ev_child_start (EV_DEFAULT_ &cw);
- ev_child_start(main_loop, &cw);
- ev_run(main_loop, );
- }
- return ;
- }
其中有一些细节。如果father在child之前醒来,比如上面的程序,那么表现正常:
- $ ./libev_demo
- F am here sleeping
- I am here sleeping
- F am waking up
- F get, child:
- I am waking up
- process exited with status
如果child在father之前醒来,那么程序表现是:father立刻会醒,并且进行处理。
这种现象,与子进程是否调用了exit无关。而且即使子进程不sleep,直接返回,父进程的sleep也无效,直接返回。总之,只要子进程退出了,父进程就会立即进行处理。
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)
- {
- printf("I'm here\n");
- if(w->attr.st_nlink)
- {
- printf("The file size %ld\n",(long)w->attr.st_size);
- }
- else
- {
- printf("No file here\n");
- }
- }
- int main(int argc, char **args)
- {
- struct ev_loop *main_loop=ev_default_loop();
- ev_stat stat_watcher;
- ev_init(&stat_watcher,stat_callback);
- ev_stat_set(&stat_watcher,"/home/work/data/code/libev_demo4/tmp",);
- ev_stat_start(main_loop,&stat_watcher);
- ev_run(main_loop,);
- return ;
- }
运行之后,在这个目录创建文件,或者删除文件,或者修改文件,都会触发到事件(虽然创建文件的触发,可能有一点延时,不清楚是不是轮询):
- $ ./libev_demo
- I'm here
- The file size
- I'm here
- No file here
- I'm here
- The file size
- I'm here
- No file here
- I'm here
- The file size
可以有如下的这些属性:
- if(w->attr.st_nlink)
- {
- printf("The file st_dev %d\n",w->attr.st_dev);
- printf("The file st_ino %d\n",w->attr.st_ino);
- printf("The file st_mode %d\n",w->attr.st_mode);
- printf("The file st_nlink %d\n",w->attr.st_nlink);
- printf("The file st_uid %d\n",w->attr.st_uid);
- printf("The file st_gid %d\n",w->attr.st_gid);
- printf("The file st_rdev %d\n",w->attr.st_rdev);
- printf("The file st_size %d\n",w->attr.st_size);
- printf("The file st_atime %d\n",w->attr.st_atime);
- printf("The file st_mtime %d\n",w->attr.st_mtime);
- printf("The file st_ctime %d\n",w->attr.st_ctime);
- }
- else
- {
- printf("文件不存在\n");
- }
上面了解了几个最主要的watcher了。ev_io ev_timer ev_periodic ev_signal ev_child ev_stat,
除了上面的几个外,还有下面这几个 ev_idle ev_prepare/ev_check ev_embed ev_fork ev_cleanup ev_async . 功能如下:
- ev_fork(创建的进程时的观察器),
ev_async(异步调用观察器),
ev_cleanup(event loop退出时触发事件),
ev_prepare(每次event loop之前事件),
ev_check(每次event loop之后事件),
ev_idle(每次event loop空闲触发事件).
其中ev_timer 和 ev_periodic 的区别在于 periodic是周期性的。虽然ev_timer有一个参数repeat,但是它的含义不是重复多少次,而是下一次按照多少间隔来处理,如下:
- ev_timer_set(&timer_w, , );
- 这样的话,第一次等5秒,然后后面每次等待1秒,就会触发事件。
- 而如果在事件处理函数里面调用了
- ev_timer_stop(main_loop, time_w);
- 这样的话,就只会处理第一次的5秒,后面的1秒都不会处理了。
而ev_periodic如再上面的示例程序,可以每隔多长时间循环处理;也可以自定义时间,来进行周期性处理。
至此,原文的系列基本学习完了。更多的内容,后面再学习。(原文地址:link)
(完)
【转载】使用事件模型 & libev学习的更多相关文章
- 轻松学习JavaScript二十七:DOM编程学习之事件模型
在介绍事件模型之前,我们先来看什么是事件和什么是event对象. 一事件介绍 JavaScript事件是由訪问Web页面的用户引起的一系列操作,使我们有能力创建动态页面.事件是能够被 JavaScri ...
- [JS学习笔记]浅谈Javascript事件模型
DOM0级事件模型 element.on[type] = function(){} 兼容性:全部支持 lay1 lay2 lay3 e.target:直接触发事件的元素[IE8及以下不支持tage ...
- Javascript事件模型系列(一)事件及事件的三种模型
一.开篇 在学习javascript之初,就在网上看过不少介绍javascript事件的文章,毕竟是js基础中的基础,文章零零散散有不少,但遗憾的是没有看到比较全面的系列文章.犹记得去年这个时候,参加 ...
- 隐马尔科夫模型HMM学习最佳范例
谷歌路过这个专门介绍HMM及其相关算法的主页:http://rrurl.cn/vAgKhh 里面图文并茂动感十足,写得通俗易懂,可以说是介绍HMM很好的范例了.一个名为52nlp的博主(google ...
- 编写高质量代码改善C#程序的157个建议[C#闭包的陷阱、委托、事件、事件模型]
前言 本文已更新至http://www.cnblogs.com/aehyok/p/3624579.html .本文主要学习记录以下内容: 建议38.小心闭包中的陷阱 建议39.了解委托的实质 建议40 ...
- [置顶] NB多项式事件模型、神经网络、SVM之函数/几何间隔——斯坦福ML公开课笔记6
转载请注明:http://blog.csdn.net/xinzhangyanxiang/article/details/9722701 本篇笔记针对斯坦福ML公开课的第6个视频,主要内容包括朴素贝叶斯 ...
- ExtJS框架基础:事件模型及其常用功能
前言 工作中用ExtJS有一段时间了,Ext丰富的UI组件大大的提高了开发B/S应用的效率.虽然近期工作中天天都用到ExtJS,但很少对ExtJS框架原理性的东西进行过深入学习,这两天花了些时间学习了 ...
- [转载]Deep Learning(深度学习)学习笔记整理
转载自:http://blog.csdn.net/zouxy09/article/details/8775360 感谢原作者:zouxy09@qq.com 八.Deep learning训练过程 8. ...
- [转载]Android开发者必须深入学习的10个应用开源项目
[转载]Android开发者必须深入学习的10个应用开源项目 原文地址:Android开发者必须深入学习的10个应用开源项目(http://blog.sina.com.cn/s/blog_7b8a63 ...
随机推荐
- python socket相关
套接字的工作流程(基于TCP和 UDP两个协议) TCP和UDP对比 TCP(Transmission Control Protocol)可靠的.面向连接的协议(eg:打电话).传输效率低全双工通信( ...
- VMware Workstation 14 PRO 下安装Ubuntu 16.04 LTS教程
一.准备好安装的VMware Workstation 14 PRO 1.VMware Workstation 14 PRO下载链接:http://rj.baidu.com/soft/detail/13 ...
- Learning Deconvolution Network for Semantic Segme小结
题目:Learning Deconvolution Network for Semantic Segmentation 作者:Hyeonwoo Noh, Seunghoon Hong, Bohyung ...
- spring AOP详解二
AOP实例(通过Proxy代理模式) Spring AOP使用纯java实现,不需要专门的编译过程和类装载器,它在运行期间通过代理方式向目标类织入增强代码,它更侧重于提供一种和Spring IoC容器 ...
- Linux Programming之MySQL
实验环境:Ubuntu13.04 在此之前有过一段使用MySQL数据库的经历,在Windows平台下使用GUI(当时是使用HeidiSQL和Workbench来管理数据库),并且有过使用Python中 ...
- PHP的发展史,功能与特点
web1.0时代:所有的代码都是在浏览器端执行的静态脚本,用户请求的也都是服务器上事先已经存在的静态网页,用户和服务器之间不能进行任何的交互!(不需要数据库的支持) web2.0时代:用户和服务器之间 ...
- python 缺少包
https://pypi.python.org/pypi/pdfminer/20140328 到这里下载相应的包,再进行安装. tar –xivf pybloomfilter-1.0 cd py ...
- setsockopt等高级使用
参考: setsockopt函数使用http://hi.baidu.com/yelangdefendou/item/74161d0f384abd3c4ac4a316http://blog.csdn.n ...
- ajax提交数据服务端返回报错
报错如下: if response.get('X-Frame-Options') is not None:AttributeError: 'str' object has no attribute ' ...
- bzoj 2791 [Poi2012]Rendezvous 基环森林
题目大意 给定一个n个顶点的有向图,每个顶点有且仅有一条出边. 对于顶点i,记它的出边为(i, a[i]). 再给出q组询问,每组询问由两个顶点a.b组成,要求输出满足下面条件的x.y: 从顶点a沿着 ...