May
16
为了论文搞了把机器学习的东西,虽然了解得非常肤浅,但是窥探了一下这个领域也还是很有收获。
对于遇到的问题,传统的思路是通过建模,然后使用对应的算法予以解决。但是对于很多问题,建模本身是不实际的,例如语音识别、计算机视觉等等。而机器学习算法的思路则不同,通过对现有的数据进行分析和统计,得到一组参数来逼近真实的模型,从而能够处理未知的数据。
我的论文里主要是使用SVM来解决简单的二分类问题。SVM,Support Vector Machine的简写,也就是“支持向量机”,很早以前有“听说过”,但是之前完全没有概念。这次在yihong妹妹的推荐下,看了faruto大牛写的《SVM入门精品系列讲解》,能大致在原理上明白svm分类的机制。之所以称faruto为大牛,主要是因为这个讲解系列非常地浅显易懂,没有卖弄玄虚,即使是我这样没学好数学的人,也能够非常容易地弄懂。
由我来归纳的话,svm的基本思路应该是,将每个样本x当作一个N维向量(也就是N维空间中的一个点),通过某种方式找到该空间中的一个超平面w * x + b = 0,将样本分成两类。例如二维空间中的点,可以用一条直线分成两类,而三维空间的点,可以用一个平面来分。由于并不是所有问题中,样本在N维空间中都可以被超平面分为两类,因此通过使用引入核函数将样本映射到更高维的空间、并引入松弛变量以忽略噪音数据等方式,达到对数据进行分类的目的。
可能看起来有点抽象?没关系,把那个系列(并不是很长)看完就懂了,其实不难理解。在此基础上,svm方法还有许多扩充,例如对不平衡样本集的处理、One-Class SVM、在线SVM训练等等。
想要使用svm算法的话,非常幸运,台湾大学林智仁(Lin Chih-Jen)副教授主持的 libsvm 项目提供了c/java/python/matlab 的接口,直接拿来就能用了,非常方便。
在学习svm的过程中,也顺便看了一些其他的机器学习算法,这里也大致列一下。
HMM,隐马尔可夫模型。李开复的主要学术成就(之一?),就是使用了HMM开发出世界上第一个大词汇量连续语音识别系统 Sphinx。根据Google研究员吴军的数学之美 系列三 -- 隐含马尔可夫模型在语言处理中的应用,使用HMM来进行语音识别是李开复的师兄提出的。
HMM算法是基于贝叶斯公式的。贝叶斯公式在机器学习中是一个非常基础的理论。关于这个,推荐阅读《数学之美番外篇:平凡而又神奇的贝叶斯方法》
神经网络算法,通过模拟神经元的工作方式来对数据进行学习,使用多个神经元构成一个网络,并适当加入反馈机制。详情参考神经网络编程入门。
遗传算法,通过模拟染色体复制、基因变异等机制,使状态不断”进化“,从而尽量逼近最优值。详情参考遗传算法入门。
模拟退火,非常简洁、实用的一个算法,基于“爬山算法”(不断逼近离当前点最近的极值,贪心)改进而来,通过引入随机化以获得跳跃到其他极值区域的机会,从而尽可能获得更高的极值点。详情可参考《大白话解析模拟退火算法》。
此外还看到了决策树、K-mean聚类等算法,不过没有细看,只是大致扫了一眼,就不扯了。
以上给出的链接大都是讲解得非常浅显易懂的文章,非常推荐阅读。
对于遇到的问题,传统的思路是通过建模,然后使用对应的算法予以解决。但是对于很多问题,建模本身是不实际的,例如语音识别、计算机视觉等等。而机器学习算法的思路则不同,通过对现有的数据进行分析和统计,得到一组参数来逼近真实的模型,从而能够处理未知的数据。
我的论文里主要是使用SVM来解决简单的二分类问题。SVM,Support Vector Machine的简写,也就是“支持向量机”,很早以前有“听说过”,但是之前完全没有概念。这次在yihong妹妹的推荐下,看了faruto大牛写的《SVM入门精品系列讲解》,能大致在原理上明白svm分类的机制。之所以称faruto为大牛,主要是因为这个讲解系列非常地浅显易懂,没有卖弄玄虚,即使是我这样没学好数学的人,也能够非常容易地弄懂。
由我来归纳的话,svm的基本思路应该是,将每个样本x当作一个N维向量(也就是N维空间中的一个点),通过某种方式找到该空间中的一个超平面w * x + b = 0,将样本分成两类。例如二维空间中的点,可以用一条直线分成两类,而三维空间的点,可以用一个平面来分。由于并不是所有问题中,样本在N维空间中都可以被超平面分为两类,因此通过使用引入核函数将样本映射到更高维的空间、并引入松弛变量以忽略噪音数据等方式,达到对数据进行分类的目的。
可能看起来有点抽象?没关系,把那个系列(并不是很长)看完就懂了,其实不难理解。在此基础上,svm方法还有许多扩充,例如对不平衡样本集的处理、One-Class SVM、在线SVM训练等等。
想要使用svm算法的话,非常幸运,台湾大学林智仁(Lin Chih-Jen)副教授主持的 libsvm 项目提供了c/java/python/matlab 的接口,直接拿来就能用了,非常方便。
在学习svm的过程中,也顺便看了一些其他的机器学习算法,这里也大致列一下。
HMM,隐马尔可夫模型。李开复的主要学术成就(之一?),就是使用了HMM开发出世界上第一个大词汇量连续语音识别系统 Sphinx。根据Google研究员吴军的数学之美 系列三 -- 隐含马尔可夫模型在语言处理中的应用,使用HMM来进行语音识别是李开复的师兄提出的。
HMM算法是基于贝叶斯公式的。贝叶斯公式在机器学习中是一个非常基础的理论。关于这个,推荐阅读《数学之美番外篇:平凡而又神奇的贝叶斯方法》
神经网络算法,通过模拟神经元的工作方式来对数据进行学习,使用多个神经元构成一个网络,并适当加入反馈机制。详情参考神经网络编程入门。
遗传算法,通过模拟染色体复制、基因变异等机制,使状态不断”进化“,从而尽量逼近最优值。详情参考遗传算法入门。
模拟退火,非常简洁、实用的一个算法,基于“爬山算法”(不断逼近离当前点最近的极值,贪心)改进而来,通过引入随机化以获得跳跃到其他极值区域的机会,从而尽可能获得更高的极值点。详情可参考《大白话解析模拟退火算法》。
此外还看到了决策树、K-mean聚类等算法,不过没有细看,只是大致扫了一眼,就不扯了。
以上给出的链接大都是讲解得非常浅显易懂的文章,非常推荐阅读。
Apr
20
很多应用层协议都有HeartBeat机制,通常是客户端每隔一小段时间向服务器发送一个数据包,通知服务器自己仍然在线,并传输一些可能必要的数据。使用心跳包的典型协议是IM,比如QQ/MSN/飞信等协议。
学过TCP/IP的同学应该都知道,传输层的两个主要协议是UDP和TCP,其中UDP是无连接的、面向packet的,而TCP协议是有连接、面向流的协议。
所以非常容易理解,使用UDP协议的客户端(例如早期的“OICQ”,听说OICQ.com这两天被抢注了来着,好古老的回忆)需要定时向服务器发送心跳包,告诉服务器自己在线。
然而,MSN和现在的QQ往往使用的是TCP连接了,尽管TCP/IP底层提供了可选的KeepAlive(ACK-ACK包)机制,但是它们也还是实现了更高层的心跳包。似乎既浪费流量又浪费CPU,有点莫名其妙。
具体查了下,TCP的KeepAlive机制是这样的,首先它貌似默认是不打开的,要用setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1才是打开,并且可以设置三个参数tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl,分别表示连接闲置多久开始发keepalive的ack包、发几个ack包不回复才当对方死了、两个ack包之间间隔多长,在我测试的Ubuntu Server 10.04下面默认值是7200秒(2个小时,要不要这么蛋疼啊!)、9次、75秒。于是连接就了有一个超时时间窗口,如果连接之间没有通信,这个时间窗口会逐渐减小,当它减小到零的时候,TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔intvl的时间再发ACK,如果连续probes个包都被无视了,说明连接被断开了。
这里有一篇非常详细的介绍文章: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO ,包括了KeepAlive的介绍、相关内核参数、C编程接口、如何为现有应用(可以或者不可以修改源码的)启用KeepAlive机制,很值得详读。
这篇文章的2.4节说的是“Preventing disconnection due to network inactivity”,阻止因网络连接不活跃(长时间没有数据包)而导致的连接中断,说的是,很多网络设备,尤其是NAT路由器,由于其硬件的限制(例如内存、CPU处理能力),无法保持其上的所有连接,因此在必要的时候,会在连接池中选择一些不活跃的连接踢掉。典型做法是LRU,把最久没有数据的连接给T掉。通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被T掉的风险,当然,这样的代价是额外的网络和CPU负担。
前面说到,许多IM协议实现了自己的心跳机制,而不是直接依赖于底层的机制,不知道真正的原因是什么。
就我看来,一些简单的协议,直接使用底层机制就可以了,对上层完全透明,降低了开发难度,不用管理连接对应的状态。而那些自己实现心跳机制的协议,应该是期望通过发送心跳包的同时来传输一些数据,这样服务端可以获知更多的状态。例如某些客户端很喜欢收集用户的信息……反正是要发个包,不如再塞点数据,否则包头又浪费了……
大概就是这样吧,如果有大牛知道真正的原因,还望不吝赐教。
@2012-04-21
p.s. 通过咨询某个做过IM的同事,参考答案应该是,自己实现的心跳机制通用,可以无视底层的UDP或TCP协议。如果只是用TCP协议的话,那么直接使用KeepAlive机制就足够了。
@2015-09-14
补充一下 @Jack的回复:
“心跳除了说明应用程序还活着(进程还在,网络通畅),更重要的是表明应用程序还能正常工作。而 TCP keepalive 有操作系统负责探查,即便进程死锁,或阻塞,操作系统也会如常收发 TCP keepalive 消息。对方无法得知这一异常。摘自《Linux 多线程服务端编程》”
学过TCP/IP的同学应该都知道,传输层的两个主要协议是UDP和TCP,其中UDP是无连接的、面向packet的,而TCP协议是有连接、面向流的协议。
所以非常容易理解,使用UDP协议的客户端(例如早期的“OICQ”,听说OICQ.com这两天被抢注了来着,好古老的回忆)需要定时向服务器发送心跳包,告诉服务器自己在线。
然而,MSN和现在的QQ往往使用的是TCP连接了,尽管TCP/IP底层提供了可选的KeepAlive(ACK-ACK包)机制,但是它们也还是实现了更高层的心跳包。似乎既浪费流量又浪费CPU,有点莫名其妙。
具体查了下,TCP的KeepAlive机制是这样的,首先它貌似默认是不打开的,要用setsockopt将SOL_SOCKET.SO_KEEPALIVE设置为1才是打开,并且可以设置三个参数tcp_keepalive_time/tcp_keepalive_probes/tcp_keepalive_intvl,分别表示连接闲置多久开始发keepalive的ack包、发几个ack包不回复才当对方死了、两个ack包之间间隔多长,在我测试的Ubuntu Server 10.04下面默认值是7200秒(2个小时,要不要这么蛋疼啊!)、9次、75秒。于是连接就了有一个超时时间窗口,如果连接之间没有通信,这个时间窗口会逐渐减小,当它减小到零的时候,TCP协议会向对方发一个带有ACK标志的空数据包(KeepAlive探针),对方在收到ACK包以后,如果连接一切正常,应该回复一个ACK;如果连接出现错误了(例如对方重启了,连接状态丢失),则应当回复一个RST;如果对方没有回复,服务器每隔intvl的时间再发ACK,如果连续probes个包都被无视了,说明连接被断开了。
这里有一篇非常详细的介绍文章: http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO ,包括了KeepAlive的介绍、相关内核参数、C编程接口、如何为现有应用(可以或者不可以修改源码的)启用KeepAlive机制,很值得详读。
这篇文章的2.4节说的是“Preventing disconnection due to network inactivity”,阻止因网络连接不活跃(长时间没有数据包)而导致的连接中断,说的是,很多网络设备,尤其是NAT路由器,由于其硬件的限制(例如内存、CPU处理能力),无法保持其上的所有连接,因此在必要的时候,会在连接池中选择一些不活跃的连接踢掉。典型做法是LRU,把最久没有数据的连接给T掉。通过使用TCP的KeepAlive机制(修改那个time参数),可以让连接每隔一小段时间就产生一些ack包,以降低被T掉的风险,当然,这样的代价是额外的网络和CPU负担。
前面说到,许多IM协议实现了自己的心跳机制,而不是直接依赖于底层的机制,不知道真正的原因是什么。
就我看来,一些简单的协议,直接使用底层机制就可以了,对上层完全透明,降低了开发难度,不用管理连接对应的状态。而那些自己实现心跳机制的协议,应该是期望通过发送心跳包的同时来传输一些数据,这样服务端可以获知更多的状态。例如某些客户端很喜欢收集用户的信息……反正是要发个包,不如再塞点数据,否则包头又浪费了……
大概就是这样吧,如果有大牛知道真正的原因,还望不吝赐教。
@2012-04-21
p.s. 通过咨询某个做过IM的同事,参考答案应该是,自己实现的心跳机制通用,可以无视底层的UDP或TCP协议。如果只是用TCP协议的话,那么直接使用KeepAlive机制就足够了。
@2015-09-14
补充一下 @Jack的回复:
“心跳除了说明应用程序还活着(进程还在,网络通畅),更重要的是表明应用程序还能正常工作。而 TCP keepalive 有操作系统负责探查,即便进程死锁,或阻塞,操作系统也会如常收发 TCP keepalive 消息。对方无法得知这一异常。摘自《Linux 多线程服务端编程》”
Apr
18
163邮箱之所以没落,不是因为腾讯太能抄,实在是因为产品经理太不行啊。
系统中只有“收件箱”里有“举报垃圾邮件”按钮,通过点击这个按钮,可以选择将发件人加入黑名单(拒收);
而收到的广告和垃圾邮件会被自动分类到对应的文件夹,没有举报按钮。
也就是说,我想要把发件人加入黑名单,只有两种方式:
1. 拷贝发件人地址,进入设置->黑名单,添加
2. 选择“这不是垃圾邮件”,回到收件箱,选择该邮件,点击“举报垃圾邮件”。
建议选择第二种方式,更快,更蛋疼。
p.s. 对于有强迫症的我来说,还需要再进入垃圾邮箱,全选、彻底删除。
系统中只有“收件箱”里有“举报垃圾邮件”按钮,通过点击这个按钮,可以选择将发件人加入黑名单(拒收);
而收到的广告和垃圾邮件会被自动分类到对应的文件夹,没有举报按钮。
也就是说,我想要把发件人加入黑名单,只有两种方式:
1. 拷贝发件人地址,进入设置->黑名单,添加
2. 选择“这不是垃圾邮件”,回到收件箱,选择该邮件,点击“举报垃圾邮件”。
建议选择第二种方式,更快,更蛋疼。
p.s. 对于有强迫症的我来说,还需要再进入垃圾邮箱,全选、彻底删除。
Apr
8
翻译自:How To Read C Declarations 英文原文
p.s. 以前还真没注意到这篇文章最后提到的vtable是啥意思……
就算是非常有经验的C程序员,也对那些比简单数组/指针更复杂一些的声明感到头疼。比如说,下面这个是一个指针的数组,还是一个数组的指针?
下面这货到底是什么?
当然了,这货是一个指针,指向一个数组,这个数组的每个元素是一个指针,指向一个函数,函数的返回值类型是int :)
这篇短文希望能够教会你一个非常简单地读懂复杂声明的方法。我99%肯定我在80年代读过这篇,但是不记得具体是在什么地方读到的了。我怀疑是我自己发现这个的(尽管我总会被计算机语言结构和神秘的事物搞得很兴奋)。然而我的确记得,能够写出一个程序,将任何声明转换成英语。
== 黄金法则 ==
这个法则是这样说的:
最简单的情况是这样的:
从 i 开始,你向右看,啥都没看到;然后就向左看,看到了int,说出来:i是一个int。
然后看个复杂一点的:
从 a 开始:向右看,说“是一个包含3个元素的数组”;向左看,说“数组的每个元素是指针”;向右看,啥都没;向左看,说“指针指向int”。综合起来就是: a 是一个包含3个元素的数组,每个元素是一个指针,指向int。
加上一对括号让它看起来更怪异点儿:
像在普通表达式中一样,括号改变了阅读/计算的顺序。从 a 开始:向右看,遇到括号了,往回;向左看,说“是一个指针”,遇到(括号,跳出来;向右看,[3],说“指向一个包含3个元素的数组”;向左看,int,说“数组的每个元素是int”。综合起来:a是一个指针,指向一个包含3个元素的数组,数组的每个元素是一个int。
好,再来看看这个:
赞,你说:foo是一个函数,返回一个指针,指向int。
接下来跳一步:就像我们可以定义一个指向int的指针,我们也可以定义一个指向函数的指针。在这种情况下,不需要extern了(因为不是函数的前向引用声明),而是一个变量的定义。这是一个基本的函数指针:
从foo开始:向右看,遇到括号,往回;向左看,*,说“是一个指针”,遇到左括号,跳出来;向右看,(),说“指向一个函数”;向左看,int,说“函数返回int”。综合起来:foo是一个指针,指向一个函数,函数返回int。
下面是一个数组,每个元素是一个指针,指向函数,函数返回int:
你还需要最后一个,诡异的难以置信的声明:
这是一个指针,指向一个数组,数组的每个元素是个指针,指向一个函数,函数的返回值是int。发现了吗?这货就是上面那个object_vtable的指针,也就是你定义的每一个对象需要的虚函数表(vtable)的指针。
这个指向vtable的指针是一个vtable的地址,例如,&Truck_vtable (就是某个Truck类的实例虚函数表的指针)。
== 总结 ==
接下来的例子总结了所有C++为了实现多态性所建造的虚函数表需要的所有情形(就像最初的C Front - C++转C翻译器)。
p.s. 以前还真没注意到这篇文章最后提到的vtable是啥意思……
就算是非常有经验的C程序员,也对那些比简单数组/指针更复杂一些的声明感到头疼。比如说,下面这个是一个指针的数组,还是一个数组的指针?
int *a[10];
下面这货到底是什么?
int (*(*vtable)[])();
当然了,这货是一个指针,指向一个数组,这个数组的每个元素是一个指针,指向一个函数,函数的返回值类型是int :)
这篇短文希望能够教会你一个非常简单地读懂复杂声明的方法。我99%肯定我在80年代读过这篇,但是不记得具体是在什么地方读到的了。我怀疑是我自己发现这个的(尽管我总会被计算机语言结构和神秘的事物搞得很兴奋)。然而我的确记得,能够写出一个程序,将任何声明转换成英语。
== 黄金法则 ==
这个法则是这样说的:
引用
从标识符开始(或者最内层的结构,如果不存在标识符的话,通常出现于函数指针),首先向右看,直到遇到 ) 括号或者结束,看到什么就说出来;然后向左看,直到遇到 ( 括号或者回到行首,看到什么就说出来。跳出一层括号,重复上述过程:右看看,说出来;左看看,说出来。直到你说出变量的类型或者返回值(针对函数指针),也就表示你把声明都读完了。
最简单的情况是这样的:
int i;
从 i 开始,你向右看,啥都没看到;然后就向左看,看到了int,说出来:i是一个int。
然后看个复杂一点的:
int *a[3];
从 a 开始:向右看,说“是一个包含3个元素的数组”;向左看,说“数组的每个元素是指针”;向右看,啥都没;向左看,说“指针指向int”。综合起来就是: a 是一个包含3个元素的数组,每个元素是一个指针,指向int。
加上一对括号让它看起来更怪异点儿:
int (*a)[3];
像在普通表达式中一样,括号改变了阅读/计算的顺序。从 a 开始:向右看,遇到括号了,往回;向左看,说“是一个指针”,遇到(括号,跳出来;向右看,[3],说“指向一个包含3个元素的数组”;向左看,int,说“数组的每个元素是int”。综合起来:a是一个指针,指向一个包含3个元素的数组,数组的每个元素是一个int。
好,再来看看这个:
extern int *foo();
赞,你说:foo是一个函数,返回一个指针,指向int。
接下来跳一步:就像我们可以定义一个指向int的指针,我们也可以定义一个指向函数的指针。在这种情况下,不需要extern了(因为不是函数的前向引用声明),而是一个变量的定义。这是一个基本的函数指针:
int (*foo)();
从foo开始:向右看,遇到括号,往回;向左看,*,说“是一个指针”,遇到左括号,跳出来;向右看,(),说“指向一个函数”;向左看,int,说“函数返回int”。综合起来:foo是一个指针,指向一个函数,函数返回int。
下面是一个数组,每个元素是一个指针,指向函数,函数返回int:
int (*Object_vtable[])();
你还需要最后一个,诡异的难以置信的声明:
int (*(*vtable)[])();
这是一个指针,指向一个数组,数组的每个元素是个指针,指向一个函数,函数的返回值是int。发现了吗?这货就是上面那个object_vtable的指针,也就是你定义的每一个对象需要的虚函数表(vtable)的指针。
这个指向vtable的指针是一个vtable的地址,例如,&Truck_vtable (就是某个Truck类的实例虚函数表的指针)。
== 总结 ==
接下来的例子总结了所有C++为了实现多态性所建造的虚函数表需要的所有情形(就像最初的C Front - C++转C翻译器)。
int *ptr_to_int;
int *func_returning_ptr_to_int();
int (*ptr_to_func_returning_int)();
int (*array_of_ptr_to_func_returning_int[])();
int (*(*ptr_to_an_array_of_ptr_to_func_returning_int)[])();
int *func_returning_ptr_to_int();
int (*ptr_to_func_returning_int)();
int (*array_of_ptr_to_func_returning_int[])();
int (*(*ptr_to_an_array_of_ptr_to_func_returning_int)[])();
Mar
23
以前一直纳闷,非特权用户不能编辑 /etc/rc.local ,应该如何实现开机启动任务的功能。。
刚刚详读了下 man 5 crontab ,才知道crontab的前五个参数除了可以用分时日月周之外,
还可以用一些预定义的类型(叫做Vixie cron),其中一个特殊的就是 @reboot :
也就是说,只要运行crontab -e,加入一行
@reboot /home/username/my_rc.local
就可以实现个人用户的 rc.local 啦!
刚刚详读了下 man 5 crontab ,才知道crontab的前五个参数除了可以用分时日月周之外,
还可以用一些预定义的类型(叫做Vixie cron),其中一个特殊的就是 @reboot :
引用
Instead of the first five fields, one of eight special strings may appear:
string meaning
------ -------
@reboot Run once, at startup.
@yearly Run once a year, "0 0 1 1 *".
......
string meaning
------ -------
@reboot Run once, at startup.
@yearly Run once a year, "0 0 1 1 *".
......
也就是说,只要运行crontab -e,加入一行
@reboot /home/username/my_rc.local
就可以实现个人用户的 rc.local 啦!
Feb
27
vps在国外,延迟总有那么200~300ms,一来一回,500ms是免不了了。可是默认情况下,你每输入一个字符,ssh客户端(openssh/putty/securecrt)都会发送给服务器,然后服务器将响应返回。
典型ssh情况下是执行命令,比如ls,网络交互是:发送 l 给svr, svr返回 l ,显示 l ,发送 s 给svr,svr返回 s ,显示 s ,发送回车给svr,svr执行 ls ,返回 ls 的输出。也就是说,光输入一个ls命令就至少需要1s+的时间。但如果是要输入一个很复杂的命令,也许还没输入完,你就崩溃了。
采用putty(windows版ok,linux版未测试)内建的Local Echo和Local Line Editing支持,可以部分地解决这个问题:默认配置下,登录以后点击左上角的Putty图标,选择change settings=>Terminal,将Local Echo和Local line editing改成force on,就可以允许你在本地编辑一行命令,按下回车,然后命令才被发送到服务器。结果是服务器接收一整条命令,然后显示一整条命令,然后再输出这条命令的执行结果。
相应的代价就是:
1. 没法使用自动补全和其他bash/readline的快捷键了;
2. 使用vi这类程序的时候,就没法正常编辑了,这时需要再把这两个选项关闭。。。(为什么没有快捷键………………)
典型ssh情况下是执行命令,比如ls,网络交互是:发送 l 给svr, svr返回 l ,显示 l ,发送 s 给svr,svr返回 s ,显示 s ,发送回车给svr,svr执行 ls ,返回 ls 的输出。也就是说,光输入一个ls命令就至少需要1s+的时间。但如果是要输入一个很复杂的命令,也许还没输入完,你就崩溃了。
采用putty(windows版ok,linux版未测试)内建的Local Echo和Local Line Editing支持,可以部分地解决这个问题:默认配置下,登录以后点击左上角的Putty图标,选择change settings=>Terminal,将Local Echo和Local line editing改成force on,就可以允许你在本地编辑一行命令,按下回车,然后命令才被发送到服务器。结果是服务器接收一整条命令,然后显示一整条命令,然后再输出这条命令的执行结果。
相应的代价就是:
1. 没法使用自动补全和其他bash/readline的快捷键了;
2. 使用vi这类程序的时候,就没法正常编辑了,这时需要再把这两个选项关闭。。。(为什么没有快捷键………………)
Feb
26
数据存在别人那里,总还是不太放心,再加上uyan用起来效果并没有预期那么好,所以决定回归boblog的原始评论系统。
早料到有这么一天的,不过uyan没有提供迁移评论数据的接口,只能自己动手了。
打开chrome的开发人员工具->Network,登录uyan.cc的管理首页,可以看到对 http://uyan.cc/index.php/youyan_admin/getMoreCommentsByDomain/0 的请求,也就是所有通过uyan的评论。把response拷贝出来,大概处理一下,每行一个json object这样,并根据comment_id进行排序(比如 sort -nk4 -t\" replies.php > replies_sorted.txt),然后用一个php脚本转换成对应的sql,最后通过mysql commandline的source命令导入,搞定。
p.s. 刚刚又加了几行代码到 inc/securitycode.php ,把原来的验证码改成表达式格式了,嘿嘿,希望垃圾评论会减少啦~
pps. 又加了几行代码,对评论的回复会有邮件通知啦~
php代码大致如下:
早料到有这么一天的,不过uyan没有提供迁移评论数据的接口,只能自己动手了。
打开chrome的开发人员工具->Network,登录uyan.cc的管理首页,可以看到对 http://uyan.cc/index.php/youyan_admin/getMoreCommentsByDomain/0 的请求,也就是所有通过uyan的评论。把response拷贝出来,大概处理一下,每行一个json object这样,并根据comment_id进行排序(比如 sort -nk4 -t\" replies.php > replies_sorted.txt),然后用一个php脚本转换成对应的sql,最后通过mysql commandline的source命令导入,搞定。
p.s. 刚刚又加了几行代码到 inc/securitycode.php ,把原来的验证码改成表达式格式了,嘿嘿,希望垃圾评论会减少啦~
pps. 又加了几行代码,对评论的回复会有邮件通知啦~
php代码大致如下:
Feb
25
花了两天的时间在libevent上,想总结下,就以写简单tutorial的方式吧,貌似没有一篇简单的说明,让人马上就能上手用的。
首先给出官方文档吧: http://libevent.org ,首页有个Programming with Libevent,里面是一节一节的介绍libevent,但是感觉信息量太大了,而且还是英文的-。-(当然,如果想好好用libevent,看看还是很有必要的),还有个Reference,大致就是对各个版本的libevent使用doxgen生成的文档,用来查函数原型和基本用法什么的。
下面假定已经学习过基本的socket编程(socket,bind,listen,accept,connect,recv,send,close),并且对异步/callback有基本认识。
基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem),例如内存(默认情况下每个线程需要占用2~8M的栈空间),以及进程切换带来的原因等。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。
libevent大概是这样的:
默认情况下是单线程的(可以配置成多线程,如果有需要的话),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。
event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有一个/一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类(这里只讲fd了,其他的看manual吧)。struct event使用event_new来创建和绑定,使用event_add来启用:
注:libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)
(a) EV_TIMEOUT: 超时
(b) EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
(c) EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
(d) EV_SIGNAL: POSIX信号量,参考manual吧
(e) EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
(f) EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET
然后需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。
接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。其原型是:
对于一个服务器而言,上面的流程大概是这样组合的:
1. listener = socket(),bind(),listen(),设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,实际上libevent提供了统一的包装evutil_make_socket_nonblocking)
2. 创建一个event_base
3. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说,只需要监听EV_READ|EV_PERSIST
4. 启用该事件
5. 进入事件循环
---------------
6. (异步) 当有client发起请求的时候,调用该回调函数,进行处理。
问题:为什么不在listen完马上调用accept,获得客户端连接以后再丢给event_base呢?这个问题先想想噢。
回调函数要做什么事情呢?当然是处理client的请求了。首先要accept,获得一个可以与client通信的sockfd,然后……调用recv/send吗?错!大错特错!如果直接调用recv/send的话,这个线程就阻塞在这个地方了,如果这个客户端非常的阴险(比如一直不发消息,或者网络不好,老是丢包),libevent就只能等它,没法处理其他的请求了——所以应该创建一个新的event来托管这个sockfd。
在老版本libevent上的实现,比较罗嗦[如果不想详细了解的话,看下一部分]。
对于服务器希望先从client获取数据的情况,大致流程是这样的:
1. 将这个sockfd设置为nonblocking
2. 创建2个event:
event_read,绑上sockfd的EV_READ|EV_PERSIST,设置回调函数和参数(后面提到的struct)
event_write,绑上sockfd的EV_WRITE|EV_PERSIST,设置回调函数和参数(后面提到的struct)
3. 启用event_read事件
------
4. (异步) 等待event_read事件的发生, 调用相应的回调函数。这里麻烦来了:回调函数用recv读入的数据,不能直接用send丢给sockfd了事——因为sockfd是nonblocking的,丢给它的话,不能保证正确(为什么呢?)。所以需要一个自己管理的缓存用来保存读入的数据中(在accept以后就创建一个struct,作为第2步回调函数的arg传进来),在合适的时间(比如遇到换行符)启用event_write事件【event_add(event_write, NULL)】,等待EV_WRITE事件的触发
------
5. (异步) 当event_write事件的回调函数被调用的时候,往sockfd写入数据,然后删除event_write事件【event_del(event_write)】,等待event_read事件的下一次执行。
以上步骤比较晦涩,具体代码可参考官方文档里面的【Example: A low-level ROT13 server with Libevent】
由于需要自己管理缓冲区,且过程晦涩难懂,并且不兼容于Windows的IOCP,所以libevent2开始,提供了bufferevent这个神器,用来提供更加优雅、易用的API。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input, *output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当output被输出完的时候,write_cb被调用;在网络IO操作出现错误的情况(连接中断、超时、其他错误),error_cb被调用。于是上一部分的步骤被简化为:
1. 设置sockfd为nonblocking
2. 使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base
3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数
4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件
------
5. (异步)
在read_cb里面从input读取数据,处理完毕后塞到output里(会被自动写入到sockfd)
在write_cb里面(需要做什么吗?对于一个echo server来说,read_cb就足够了)
在error_cb里面处理遇到的错误
*. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。
*. read_cb和write_cb的原型是
void read_or_write_callback(struct bufferevent *bev, void *arg)
error_cb的原型是
void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型
可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据,详情RTFM~
于是代码简化到只需要几行的read_cb和error_cb函数即可:
于是一个支持大并发量的echo server就成型了!下面附上无注释的echo server源码,110行,多抄几遍,就能完全弄懂啦!更复杂的例子参见官方文档里面的【Example: A simpler ROT13 server with Libevent】
首先给出官方文档吧: http://libevent.org ,首页有个Programming with Libevent,里面是一节一节的介绍libevent,但是感觉信息量太大了,而且还是英文的-。-(当然,如果想好好用libevent,看看还是很有必要的),还有个Reference,大致就是对各个版本的libevent使用doxgen生成的文档,用来查函数原型和基本用法什么的。
下面假定已经学习过基本的socket编程(socket,bind,listen,accept,connect,recv,send,close),并且对异步/callback有基本认识。
基本的socket编程是阻塞/同步的,每个操作除非已经完成或者出错才会返回,这样对于每一个请求,要使用一个线程或者单独的进程去处理,系统资源没法支撑大量的请求(所谓c10k problem),例如内存(默认情况下每个线程需要占用2~8M的栈空间),以及进程切换带来的原因等。posix定义了可以使用异步的select系统调用,但是因为其采用了轮询的方式来判断某个fd是否变成active,效率不高[O(n)],连接数一多,也还是撑不住。于是各系统分别提出了基于异步/callback的系统调用,例如Linux的epoll,BSD的kqueue,Windows的IOCP。由于在内核层面做了支持,所以可以用O(1)的效率查找到active的fd。基本上,libevent就是对这些高效IO的封装,提供统一的API,简化开发。
libevent大概是这样的:
默认情况下是单线程的(可以配置成多线程,如果有需要的话),每个线程有且只有一个event_base,对应一个struct event_base结构体(以及附于其上的事件管理器),用来schedule托管给它的一系列event,可以和操作系统的进程管理类比,当然,要更简单一点。当一个事件发生后,event_base会在合适的时间(不一定是立即)去调用绑定在这个事件上的函数(传入一些预定义的参数,以及在绑定时指定的一个参数),直到这个函数执行完,再返回schedule其他事件。
//创建一个event_base
struct event_base *base = event_base_new();
assert(base != NULL);
struct event_base *base = event_base_new();
assert(base != NULL);
event_base内部有一个循环,循环阻塞在epoll/kqueue等系统调用上,直到有一个/一些事件发生,然后去处理这些事件。当然,这些事件要被绑定在这个event_base上。每个事件对应一个struct event,可以是监听一个fd或者POSIX信号量之类(这里只讲fd了,其他的看manual吧)。struct event使用event_new来创建和绑定,使用event_add来启用:
//创建并绑定一个event
struct event *listen_event;
//参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置)
event_add(listen_event, NULL);
struct event *listen_event;
//参数:event_base, 监听的fd,事件类型及属性,绑定的回调函数,给回调函数的参数
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, callback_func, (void*)base);
//参数:event,超时时间(struct timeval *类型的,NULL表示无超时设置)
event_add(listen_event, NULL);
注:libevent支持的事件及属性包括(使用bitfield实现,所以要用 | 来让它们合体)
(a) EV_TIMEOUT: 超时
(b) EV_READ: 只要网络缓冲中还有数据,回调函数就会被触发
(c) EV_WRITE: 只要塞给网络缓冲的数据被写完,回调函数就会被触发
(d) EV_SIGNAL: POSIX信号量,参考manual吧
(e) EV_PERSIST: 不指定这个属性的话,回调函数被触发后事件会被删除
(f) EV_ET: Edge-Trigger边缘触发,参考EPOLL_ET
然后需要启动event_base的循环,这样才能开始处理发生的事件。循环的启动使用event_base_dispatch,循环将一直持续,直到不再有需要关注的事件,或者是遇到event_loopbreak()/event_loopexit()函数。
//启动事件循环
event_base_dispatch(base);
event_base_dispatch(base);
接下来关注下绑定到event的回调函数callback_func:传递给它的是一个socket fd、一个event类型及属性bit_field、以及传递给event_new的最后一个参数(去上面几行回顾一下,把event_base给传进来了,实际上更多地是分配一个结构体,把相关的数据都撂进去,然后丢给event_new,在这里就能取得到了)。其原型是:
typedef void(* event_callback_fn)(evutil_socket_t sockfd, short event_type, void *arg)
对于一个服务器而言,上面的流程大概是这样组合的:
1. listener = socket(),bind(),listen(),设置nonblocking(POSIX系统中可使用fcntl设置,windows不需要设置,实际上libevent提供了统一的包装evutil_make_socket_nonblocking)
2. 创建一个event_base
3. 创建一个event,将该socket托管给event_base,指定要监听的事件类型,并绑定上相应的回调函数(及需要给它的参数)。对于listener socket来说,只需要监听EV_READ|EV_PERSIST
4. 启用该事件
5. 进入事件循环
---------------
6. (异步) 当有client发起请求的时候,调用该回调函数,进行处理。
问题:为什么不在listen完马上调用accept,获得客户端连接以后再丢给event_base呢?这个问题先想想噢。
回调函数要做什么事情呢?当然是处理client的请求了。首先要accept,获得一个可以与client通信的sockfd,然后……调用recv/send吗?错!大错特错!如果直接调用recv/send的话,这个线程就阻塞在这个地方了,如果这个客户端非常的阴险(比如一直不发消息,或者网络不好,老是丢包),libevent就只能等它,没法处理其他的请求了——所以应该创建一个新的event来托管这个sockfd。
在老版本libevent上的实现,比较罗嗦[如果不想详细了解的话,看下一部分]。
对于服务器希望先从client获取数据的情况,大致流程是这样的:
1. 将这个sockfd设置为nonblocking
2. 创建2个event:
event_read,绑上sockfd的EV_READ|EV_PERSIST,设置回调函数和参数(后面提到的struct)
event_write,绑上sockfd的EV_WRITE|EV_PERSIST,设置回调函数和参数(后面提到的struct)
3. 启用event_read事件
------
4. (异步) 等待event_read事件的发生, 调用相应的回调函数。这里麻烦来了:回调函数用recv读入的数据,不能直接用send丢给sockfd了事——因为sockfd是nonblocking的,丢给它的话,不能保证正确(为什么呢?)。所以需要一个自己管理的缓存用来保存读入的数据中(在accept以后就创建一个struct,作为第2步回调函数的arg传进来),在合适的时间(比如遇到换行符)启用event_write事件【event_add(event_write, NULL)】,等待EV_WRITE事件的触发
------
5. (异步) 当event_write事件的回调函数被调用的时候,往sockfd写入数据,然后删除event_write事件【event_del(event_write)】,等待event_read事件的下一次执行。
以上步骤比较晦涩,具体代码可参考官方文档里面的【Example: A low-level ROT13 server with Libevent】
由于需要自己管理缓冲区,且过程晦涩难懂,并且不兼容于Windows的IOCP,所以libevent2开始,提供了bufferevent这个神器,用来提供更加优雅、易用的API。struct bufferevent内建了两个event(read/write)和对应的缓冲区【struct evbuffer *input, *output】,并提供相应的函数用来操作缓冲区(或者直接操作bufferevent)。每当有数据被读入input的时候,read_cb函数被调用;每当output被输出完的时候,write_cb被调用;在网络IO操作出现错误的情况(连接中断、超时、其他错误),error_cb被调用。于是上一部分的步骤被简化为:
1. 设置sockfd为nonblocking
2. 使用bufferevent_socket_new创建一个struct bufferevent *bev,关联该sockfd,托管给event_base
3. 使用bufferevent_setcb(bev, read_cb, write_cb, error_cb, (void *)arg)将EV_READ/EV_WRITE对应的函数
4. 使用bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST)来启用read/write事件
------
5. (异步)
在read_cb里面从input读取数据,处理完毕后塞到output里(会被自动写入到sockfd)
在write_cb里面(需要做什么吗?对于一个echo server来说,read_cb就足够了)
在error_cb里面处理遇到的错误
*. 可以使用bufferevent_set_timeouts(bev, struct timeval *READ, struct timeval *WRITE)来设置读写超时, 在error_cb里面处理超时。
*. read_cb和write_cb的原型是
void read_or_write_callback(struct bufferevent *bev, void *arg)
error_cb的原型是
void error_cb(struct bufferevent *bev, short error, void *arg) //这个是event的标准回调函数原型
可以从bev中用libevent的API提取出event_base、sockfd、input/output等相关数据,详情RTFM~
于是代码简化到只需要几行的read_cb和error_cb函数即可:
void read_cb(struct bufferevent *bev, void *arg) {
char line[256];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, 256), n > 0)
bufferevent_write(bev, line, n);
}
void error_cb(struct bufferevent *bev, short event, void *arg) {
bufferevent_free(bev);
}
char line[256];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, 256), n > 0)
bufferevent_write(bev, line, n);
}
void error_cb(struct bufferevent *bev, short event, void *arg) {
bufferevent_free(bev);
}
于是一个支持大并发量的echo server就成型了!下面附上无注释的echo server源码,110行,多抄几遍,就能完全弄懂啦!更复杂的例子参见官方文档里面的【Example: A simpler ROT13 server with Libevent】
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
void do_accept(evutil_socket_t listener, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
void write_cb(struct bufferevent *bev, void *arg);
int main(int argc, char *argv[])
{
int ret;
evutil_socket_t listener;
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
evutil_make_socket_nonblocking(listener);
struct event_base *base = event_base_new();
assert(base != NULL);
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}
void read_cb(struct bufferevent *bev, void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line[n] = '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
void write_cb(struct bufferevent *bev, void *arg) {}
void error_cb(struct bufferevent *bev, short event, void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#define LISTEN_PORT 9999
#define LISTEN_BACKLOG 32
void do_accept(evutil_socket_t listener, short event, void *arg);
void read_cb(struct bufferevent *bev, void *arg);
void error_cb(struct bufferevent *bev, short event, void *arg);
void write_cb(struct bufferevent *bev, void *arg);
int main(int argc, char *argv[])
{
int ret;
evutil_socket_t listener;
listener = socket(AF_INET, SOCK_STREAM, 0);
assert(listener > 0);
evutil_make_listen_socket_reuseable(listener);
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = 0;
sin.sin_port = htons(LISTEN_PORT);
if (bind(listener, (struct sockaddr *)&sin, sizeof(sin)) < 0) {
perror("bind");
return 1;
}
if (listen(listener, LISTEN_BACKLOG) < 0) {
perror("listen");
return 1;
}
printf ("Listening...\n");
evutil_make_socket_nonblocking(listener);
struct event_base *base = event_base_new();
assert(base != NULL);
struct event *listen_event;
listen_event = event_new(base, listener, EV_READ|EV_PERSIST, do_accept, (void*)base);
event_add(listen_event, NULL);
event_base_dispatch(base);
printf("The End.");
return 0;
}
void do_accept(evutil_socket_t listener, short event, void *arg)
{
struct event_base *base = (struct event_base *)arg;
evutil_socket_t fd;
struct sockaddr_in sin;
socklen_t slen = sizeof(sin);
fd = accept(listener, (struct sockaddr *)&sin, &slen);
if (fd < 0) {
perror("accept");
return;
}
if (fd > FD_SETSIZE) { //这个if是参考了那个ROT13的例子,貌似是官方的疏漏,从select-based例子里抄过来忘了改
perror("fd > FD_SETSIZE\n");
return;
}
printf("ACCEPT: fd = %u\n", fd);
struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
bufferevent_setcb(bev, read_cb, NULL, error_cb, arg);
bufferevent_enable(bev, EV_READ|EV_WRITE|EV_PERSIST);
}
void read_cb(struct bufferevent *bev, void *arg)
{
#define MAX_LINE 256
char line[MAX_LINE+1];
int n;
evutil_socket_t fd = bufferevent_getfd(bev);
while (n = bufferevent_read(bev, line, MAX_LINE), n > 0) {
line[n] = '\0';
printf("fd=%u, read line: %s\n", fd, line);
bufferevent_write(bev, line, n);
}
}
void write_cb(struct bufferevent *bev, void *arg) {}
void error_cb(struct bufferevent *bev, short event, void *arg)
{
evutil_socket_t fd = bufferevent_getfd(bev);
printf("fd = %u, ", fd);
if (event & BEV_EVENT_TIMEOUT) {
printf("Timed out\n"); //if bufferevent_set_timeouts() called
}
else if (event & BEV_EVENT_EOF) {
printf("connection closed\n");
}
else if (event & BEV_EVENT_ERROR) {
printf("some other error\n");
}
bufferevent_free(bev);
}