关于sphinx引擎的一些想法说过用Sphinx给同事搭引擎,可是那是建立在之前的配置文件之上,我只要依葫芦画瓢,改一改路径以及查询语句就搞定了,实质上没学到什么东西。在我看来,要想真正了解它,还是得重新造轮子,从头到尾自己搭一遍,在这个过程中出现了许多奇怪的错误,在这里记录一下。

1.checking for clock_gettime in -lrt…
这是我遇到的第一个问题,事实证明,这根本不是问题。到Sphinx-for-chinese下载了编译包,开始编译,之后就卡在了这里。刚开始以为是缺少librt,然而我在lib中找到了这个链接库。将编译包放在其它机器上编译,又是可以通过的,百思不得其解。只好到Sphinx-fro-chinese的QQ群里发问,黑猫给出解答是要将librt所在路径加入到etc/ld.so.conf,并运行ldconfig命令。按照他的办法,结果运行ldconfig命令时卡住了,于是可以断定是机器的问题。

2.ERROR: cannot find MySQL include files.
这个问题比较好解决,就是缺少MySQL的库文件。因为虚拟机装的是Ubuntu,只要运行以下命令就好了。
sudo apt-get install libmysql++-dev libmysqlclient15-dev checkinstall
如果是其它系统,相信也是类似的方法。如果已经有库文件了,则只需要将路径加入到/etc/ld.so.conf中,并执行ldconfig命令

3.index ‘test1’: search error: query too complex, not enough stack (thread_stack=1217498K or higher required).
这也是一个很奇怪的错误。我是按照文档中给出的例子建好索引,之后用命令行工具,也就是search要搜索的,结果就出现了这个错误。在网上搜索这个错误,没找到有用的信息,于是又求助于Sphinx-for-chinese群,群里的人说是因为命令行存在问题,用客户端搜就没问题。于是用客户端搜果然没问题,可是我还是无法释怀,因为之前公司的引擎中,用命令行是没有问题的。于是对照着公司用的引擎中的配置文件,发现配置文件中没有这一行,在自己的配置文件中注释掉这行后,果然没问题了。
所以对于这个错误的解决办法就是,将sql_query_info = SELECT * FROM documents WHERE id=$id这行注释掉.
这个确实太坑人了,连官方的配置文件都会出错,得浪费多少人的时间。

4.ERROR: index ‘main’: No fields in schema - will not index.
光运行例子是不行的,还是得自己写一些东西,于是将自己的博客文章来搜索。用了Wordpress中wp_posts表中的数据,我只用的四个字段ID,post_title,post_content,post_modified,将post_title,post_content定义成sql_attr_string,sql_attr_timestamp,结果就出现了这个错误。在网上找了,发现在官方bug报告中有提到这个问题
http://sphinxsearch.com/bugs/view.php?id=1632
管理员说,引擎中需要一个全文索引字段,否则就没有东西需要索引了,这样它就不会建索引。管理员建议定义为sql_field_string,这样就会对这个字段既索引又保存内容。对于我的配置,我并不想保存post_content这个字段,所以不想将它定义为sql_field_string,那怎样才能让它只被所以呢?看过文档之后,才知道默认情况下,是会被索引。这也是为什么,在上面的帖子中,将sql_attr_string = text注释掉就可以建索引了。所以我只能说管理员也没有真正理解这个错误的原因,看来不能迷信权威啊。

5.FATAL: there must be 2 indexes to merge specified
这个是在测试Klist的时,出现的。文档中说,当合并两个索引时,使用–merge-klists就可以将两个索引的klist合并,于是我在合并时加上了这个参数。具体如下:

1
./indexer -c $conf --rotate --merge --merge-klists delta deltaTemp

运行时就出现这个错误,我纳闷了,明明官方文档中说加入这个参数是没问题的。到网上找资料,有人是用–merge-killlists这个参数,试过之后,同样报这个错误。无奈之际,将–merge-klist参数放到–rotate前面,
./indexer -c $conf –merge-klists –rotate –merge delta deltaTemp
奇迹出现了,这次没有报错。我只能说,这真是个坑。

《Introduction to Search with Sphinx》写的还是非常不错的,毕竟是Sphinx的作者,表达能力和写作能力自然非同凡响,关于Sphinx的知识,许多都来自本书。等有时间了,可以将引擎的搭建过程写一写,应该可以帮助一些人。这次搭建过程,我学到了许多,虽然用的是开源的引擎,但真要从头到尾搭建一个引擎,并提供可靠的服务,并不是那么容易的,还是得多实践才行。

联系作者

因为自留地越来越像我的心情博客了,于是决定将学习笔记都记录在这个博客上,于是将之前关于Sphinx的一些文章全部转移到这里。

最近一个星期都在看Sphinx搜索引擎的文档,并和组里的一个同事合作为公司的企业空间搜索建立索引,提供搜索服务,所以对于Sphinx有了一些了解,顺便几下来,以后用到了可以再看看。

先八卦一下,Sphinx首先是俄罗斯人Andrew Aksyonoff开发的全文搜索引擎,开源之后有其他人参与进来,功能更加强大了。俄罗斯人还是真是厉害,之前是Nginx,现在是Sphinx。可是Sphinx不支持中文,所以要下载Sphinxforchinese才可以用。

Sphinx的数据源主要来自数据库,如Mysql,这也是最常见的方式。以下主要写给公司配置引擎时的一些体会。

1.一般使用都是一个主索引和增量索引,主索引建立后一直不变,变化的是增量索引,搜索的结果为合并主索引和增量索引的搜索结果。每隔一段时间就到数据源中抓取数据,保存在一个tmp索引中,然后这个tmp索引和增量索引合并,当然也可以隔一段时间就将增量索引和主索引合并,但这个时间间隔最好长一些。

2.建索引需要的数据分布在许多个表上,所以要先写爬虫将这些表的数据从数据库中抓取出来,存到另一个表中。之后在Sphin的配置文件中,数据就可以来自这个新建的表。这个新建的表最好有一个自动变化的时间字段,也就是每次在这个表中插入数据或更新数据,这个时间自动都会变化,这个字段将用于增量索引。另外还需要建一个表来保存上一次抓取的时间,从这个时间往后,抓取新的数据。

3.默认情况下,从数据源中选出的数据都是建索引的。而默认情况下,对于建索引的数据,Sphinx将不会保存原始数据,如果需要Sphinx既建索引,又要保存数据,在配置文件中,将这个字段写为sql_field_string。对于时间类型,在sql_query中select数据时,就要用函数unix_timestamp将它转为整形的时间戳,在配置文件中,要将这个字段写为sql_attr_timestamp,这样在客户端中调用api转化时间时才会准确。

4.sql_query_post和sql_query_post_index是有区别的。前者是当Sphinx从数据库中得到数据后,立刻就会运行,而后者只有当索引真正成功建立后才会运行,这个区别还是很重要的。对于真正严格的程序,不应该在sql_query_pre和sql_query_post中更新增量时间,而应该在sql_query_post_index中更新增量时间。还有一个区别是sql_query_post和sql_query_post_index是存在与两个不同的tcp连接中,因为Sphinx从数据库中得到数据后去建索引,将会花费很长时间,所以它会将数据库连接关闭,等到索引建好之后,再去连接数据库,所以sql_query_post_index会在另一个连接中

5.对于可以使用id来做增量索引的数据,需要将这次增量的最大id保存到数据库中。一个很诱人的做法是,将这次增量的最大id保存在一个值中,然后在sql_query_post_index中将这个值保存到数据库中,这其实是不对的。因为上一条中说过,sql_query_post_index会在另一个连接中,所以之前连接中的值在这个连接中失效了。一个做法是将增量的最大id保存到数据库中一个tmp字段中,等到索引建成功后,在sql_query_post_index中,将这个最大id从数据库中读出,写到用于做增量索引的字段中。

6.事实上,比较难的一点是在于数据有更新的情况下,如何处理。当数据有更新时,在主索引中原来的数据将会失效,但是搜索时还是会搜到它。一个解决的办法是将原来的数据标示为删除,这就需要一个标示字段了。这个办法是我组长想出来的,在sql_query中就给它添加一个字段,标示未删除的。每次增量索引结束后,就通过每条记录的id(在Sphinx中,会给每条记录一个id),将主索引中相应的记录标示为删除。在搜索时,只需要搜索出标示为未删除的就可以了。对于官方文档中,我还没有看到如何解决这个问题的介绍。

7.如果能写一个程序来自动生成配置文件,那就再好不过了。上次我是手动输的,既容易出错,有耗眼力和精力。

整个过程最重要的还是将分布在多个表中的数据合并为一个表以及处理更新这两步上,如果能解决这两个问题,一个可用的全文搜索就完成了。暂时先写到这里,等以后有了新的体会再补充。

看来是我错了,文档中有说到数据更新这个问题,是用Klist,具体可以看文档。看Sphinx的源码很不舒服,因为可恶的匈牙利命名。

联系作者

这个问题对于学习编程的人来说不陌生,但并没有想象中那么简单,这里面还是很深的。一般来说是用比这个数小的素数去除,如果都不能整除,则是素数。可是这种方法必须要求你知道比这个数小的所有素数,所以我觉得不是很实用。

去年找实习时,微软的面试官也问了我这题。刚开始只想到最基础的方法,假设需要判断的是n, 如果n是2则是素数,如果n是1或者是大于2的偶数,则是素数,如果n是大于2的奇数,则从2开始到 n的平方根,如果n可以被其中任何一个整除则这个数不是素数,否则是素数。

之后面试官问还有没有更好的方法,考虑之后,想到《C语言名题百则精选中曾提到过一种方法,对于大于等于6的数,都可以表示成6n,6n + 1,6n + 2, 6n + 3, 6n + 4, 6n + 5,其中只有6n + 1和6n + 5是素数,所以对于大于6的数,如果不能被2 , 3 , 5整除,则只需用6n + 1和6n + 5这些数去除。

最近在做欧拉工程第58题,我先生成一个很大的素数表,然后来判断是否是素数,可是这个题目中用到的素数实在是太大了,这种方法不实际。之后用了前面两种方法,也是不行,速度太慢了。最后上网找方法,找到了米勒-拉宾素性测试法,终于把问题解决了。难道说,当时的面试官是想问这种方法?
以下内容来自http://en.wikipedia.org/wiki/Miller-Rabin_primality_test 。这个方法需要用到费马小定理,x^2 = 1 (mod p),费马这厮真是有趣,搞出几个定理都不给出证明,这个小定理还好,最要命的是那个大定理,耗费了几百年才被人给出证明。这里空白太少,写不下证明,只好直接拿来用了。

先给一个引理,当p是素数,若x^2 = 1 (mod p),则x = 1(mod p)或者 x = -1(mod p),这个引理的证明很简单,由x ^ 2 = 1 (mod p)得(x + 1)(x – 1) = 0 (mod p),而由于p是素数,所以x + 1 = 0 (mod p)或者x – 1 = 0 (mod p).

现在设n是一个素数且n > 2,则 n – 1一定是偶数,并且可以表示成2 ^s d的形式,其中s和d都为正整数,且d是奇数。那么对于任意2 <= a < n,
a^d = 1 (mod n)或者a^(2^r
d) = - 1(mod n),其中0 <= r <= s – 1。

证明如下:
由费马小定理可知,当n是素数时
a^(n – 1) = 1 (mod n), 我们将n – 1表示成2^s d,再由上面的引理得,如果我们对n – 1进行开方,则可以得到a^(2^(s – 1) d)=1 (mod n)或者a^(2^(s-1) *d) = -1 (mod n),如果是后者,则已经满足了,如果是前者,则继续这个开方过程,如果我们一直都是得到1,最终就是
a^d = 1(mod n).

而米勒-拉宾测试法就是用了这个定理的逆否命题。也就是,如果 a^d != 1 (mod n)且a^(2^r *d) != -1 (mod n)对于所有的0<=r<=s-1,则n不是素数。

联系作者

原题链接http://projecteuler.net/problem=60

Prime pair sets

The primes 3, 7, 109, and 673, are quite remarkable. By taking any two primes and concatenating them in any order the result will always be prime. For example, taking 7 and 109, both 7109 and 1097 are prime. The sum of these four primes, 792, represents the lowest sum for a set of four primes with this property.

Find the lowest sum for a set of five primes for which any two primes concatenate to produce another prime.

素数对集合

素数3,7,109和673非常有特色。取其中任意两个素数,以任意顺序链接起来都是素数。例如,取7和109,7109和1097都是素数。这四个素数的和792,代表着具有这种特征的四素数集中的最小和。

求具有取其中任意两个素数,连接起来都是素数的五素数集的最小和。

解答:

依然是暴力解决。唯一需要注意的是,判断素数的方法必须使用米勒-拉宾素性测试方法,否则速度无法接受。

联系作者

原题链接http://projecteuler.net/problem=59

XOR decryption

Each character on a computer is assigned a unique code and the preferred standard is ASCII (American Standard Code for Information Interchange). For example, uppercase A = 65, asterisk (*) = 42, and lowercase k = 107.

A modern encryption method is to take a text file, convert the bytes to ASCII, then XOR each byte with a given value, taken from a secret key. The advantage with the XOR function is that using the same encryption key on the cipher text, restores the plain text; for example, 65 XOR 42 = 107, then 107 XOR 42 = 65.

For unbreakable encryption, the key is the same length as the plain text message, and the key is made up of random bytes. The user would keep the encrypted message and the encryption key in different locations, and without both “halves”, it is impossible to decrypt the message.

Unfortunately, this method is impractical for most users, so the modified method is to use a password as a key. If the password is shorter than the message, which is likely, the key is repeated cyclically throughout the message. The balance for this method is using a sufficiently long password key for security, but short enough to be memorable.

Your task has been made easy, as the encryption key consists of three lower case characters. Using cipher1.txt (right click and ‘Save Link/Target As…’), a file containing the encrypted ASCII codes, and the knowledge that the plain text must contain common English words, decrypt the message and find the sum of the ASCII values in the original text.

异或加密
在计算机中的每个字符都被分配一个唯一的码值,最常用的标准是ASCII(美国标准信息交换码).例如,大写的A = 65,星号(*) = 42,小写的k = 107.

有一种现代的加密方法是将文本文件转化成对应的ASCII,然后将每一个字节与密码中的一个值异或。使用异或方法加密的一个优点是,在密文中使用同样的密钥可以得到明文。例如, 65 XOR 42 = 107 , 之后 107 XOR 42 = 65.

对于不可破解的加密,密钥的长度与明文一样长,并且密钥由随机的字节组成。一个用户将密文和密钥保存在不同的地方,如果没有同时拿到密文和密钥,将不可能破解信息。

不幸的是,这种方法对于许多用户都不实际,因此一个改良的方法是使用密码作为密钥。如果密码的长度小于信息,这也是最常见的情况,那么密钥就要循环贯穿信息。综合考虑,这种方法为了保证安全,选择一个足够长的密钥,为了便于记忆,也必须足够短。

你的任务已经简化了,因为密钥是由三个小写字母构成。使用cipher1.txt(右击,链接另存为),这是一个经过加密的ASCLL码,已经知道的是明文是由英文单词构成,请解密信息,并求原始信息中ASCLL码的总和.

解答:
暴力破解,用常见的英文单词the来判断。

联系作者

在Linux上经常需要定时运行某个脚本,这是crontab就派上用场了。对于如何使用crontab,一般google。事实上,我一直很好奇的是,网上那些人关于crontab的知识到底从哪里得来的,知道man crontab后我才知道,原来他们也是看的手册。
man crontab后,没有关于如何编写crontab任务的说明,看到see also crontab(5)后,执行man 5 crontab,发现这里有说明如何编写crontab任务。这样以后就不需要遇到一个问题,就上网找,直接看手册就好了。

一般说来,都是仿照晚上的例子写,如这里http://www.blogjava.net/xiaomage234/archive/2007/12/26/170490.html后来才发现,给出的例子中有一个坑。

1
* */1 * * * /usr/local/apache/bin/apachectl restart

每小时重启apache
这个例子中说每小时重启apache,试着写了之后,才发现每一分钟都会重启。仔细分析后才发现原因,因为第一列是分钟的位置,而使用*号,则代表0-59分钟,于是在一个小时里,0-59分钟都会重启apache,等到59分钟重启apache后,已经过了一小时,于是又回到0分钟,于是apache又重启了。

所以以后遇到位置的命令时,不要立马上网找,可以先看看手册的说明。或者找一个靠谱的网页看,后来才发现http://www.centos.bz/2011/03/auto-run-task-crontab/这里写的比较靠谱。在找这些命令使用过程中发现,网上这般人经常抄来抄去的,浪费别人的时间,太无聊了。

联系作者

很久之前,线上的程序跑着跑着,莫名其妙的就coredump了,找了很久都不知道原因。因为coredump的次数不是很多,有时好几天了也才dump了一次,所以也很难确定是在哪个地方出错了。通过版本回溯,慢慢缩小了出错的范围,可是我还是不知道在哪里出错了,直到今天晚上组长和我说了可能出错的地方,才知道原来有这么一个坑存在。

他说使用sort排序时,比较函数编写时,如果两个值相等返回true可能会存在问题,于是我看了自己写的,两个值相等时,正是返回true.

1
2
3
int cmp(const int &a, const int &b) {
    return a >= b;
}

可是我还是看不出这里有什么错误,google之后找到了解答,在一篇文章里说到,当排序的个数超过16个时,且这些书数全部相等时,如果比较函数在两个值相等返回true时就会出错。这是因为sort行数的实现中,当超过16个数时,使用快速排序,而且假定一定存在两个数不相等。具体可参看http://blog.sina.com.cn/s/blog_79d599dc01012m7l.html

联系作者

很早之前,在写第一个Servlet时,一直配置不成功,写一个Hello World!都成问题,于是转而奔向PHP,最近由于工作需要,要重新拾起Java那套东西。因为是用Maven做的管理,于是要安装Maven,到网上找参考资料,竟没安装好,于是到官网找解答,最终找到。

安装Maven之前,已经假设你安装好JDK.有一点需要注意的是,Maven3.2要求JDK1.6及以上,Maven3.0/3.1则要求JDK1.5及以上,否则会出现版本错误

1.下载Maven

到官网http://maven.apache.org/下载Maven,当前最新稳定版本为3.2.1

对于Windows系统,下载Maven 3.2.1 (Binary zip)

2.解压Maven,配置环境变量

将Maven解压,这里假设放在d:\apache-maven-3.2.1

之后进行环境变量配置,

在系统变量中增加M2_HOME,值为d:\apache-maven-3.2.1

在系统变量中增加M2,值为%M2_HOME%\bin

如果已经存在用户变量Path,则在值的前面增加%M2%; 注意一定要加上这个分号

如果不存在用户变量Path,则新建它,并赋值为%M2%

3.测试Maven

在命令行中执行mvn –version,在我的电脑上显示如下,

Apache Maven 3.2.1 (ea8b2b07643dbb1b84b6d16e1f08391b666bc1e9; 2014-02-15T01:37:5

2+08:00)

Maven home: d:\apache-maven-3.2.1

Java version: 1.6.0_15, vendor: Sun Microsystems Inc.

Java home: D:\Java\jdk1.6.0\jre

Default locale: zh_CN, platform encoding: GBK

OS name: “windows 7”, version: “6.1”, arch: “x86”, family: “windows”

如果不是这样,看看环境变量是否配置正确,以及JDK版本是否匹配

4.Eclipse的Maven插件安装

在插件安装上,网上的资料很多已经过时了,因为都是很早之前的链接。顺着官网找到了eclipse的Maven插件安装链接http://download.eclipse.org/technology/m2e/releases/

我用的是Eclipse Kepler.在Help->Install new software中,将以上安装链接加入,名字为Maven,之后即可下载插件

Maven插件安装

5.配置Maven插件

在Window->Preferences中,选择Maven

Maven-Installations配置

点击Installations,此时还是用插件自带的Maven版本3.0.4,而且Global settings是空的我们需要添加自己安装的版本,点击Add,打开D:\apache-maven-3.2.1即可,最终结果如下:

Maven-Installations配置结果

此时Maven插件将会使用安装的Maven3.2.1版本

之后点击User Settings,可以看到如下结果

这里也许最让你迷惑的是这三个概念,Global Settings, User Settings,以及Local Repository。因为我也未深入理解,所以也只能在这里说说我的理解。简单来说,

Global Settings就是一些全局设置,设置了中央jar仓库的位置等等.

User Settings设置了用户私有jar仓库的位置等等.

Local Repository是本地jar仓库的位置.

假设你现在在开发一个应用,它需要使用一个jar,则Maven会先到Local Repository中寻找,如果没找到这个jar,则它会到User Settings中设置的用户私有jar仓库中查找,如果还是没找到,则到Global Settings设置的中央jar仓库中查找,如果还是没找到,Maven将报错,指示jar未找到。

网上还有介绍另外一种方法,也就是先去下载Eclipse的Maven插件,之后再将插件导入到Eclipse, 只是因为直接用URL的方法已经解决了安装问题,所以没有尝试。

6.Hello World例子

之后用《Maven by Example》中的一个例子来介绍安装介绍。命令行进入Eclipse工作目录,我这里是d:\workspace,执行以下命令

mvn archetype:generate -DgroupId=org.sonatype.mavenbook -DartifactId=simple -Dpackage=org.sonatype.mavenbook -Dversion=1.0-SNAPSHOT

之后敲几次回车,一个最简单的HelloWorld项目就建立好了。

之后cd simple进入simple目录,如果你有兴趣,可以看看Maven生成的内容,

其目录结构如下

simple/

simple/pom.xml

simple/src/main

simple/src/main/java

simple/src/test

simple/src/test/java

其中src/main存放源文件,src/test存放测试文件,pom.xml为Maven提供编译信息,

执行mvn install,编译,测试,打包项目

执行java -cp target/simple-1.0-SNAPSHOT.jar org.sonatype.mavenbook.App

看到输出的 Hello World!

事实上,在命令行中,一个困惑是如何选择新建项目的类型,命令行里一共列出了九百多种,而很难知道哪个编号对应的是哪一种项目类型。

关于Maven的更多内容可以去sonatype官网下载《Maven by Example》,绝对值得一看。

参考文章:

http://www.blogjava.net/fancydeepin/archive/2012/07/13/eclipse_maven3_plugin.html

联系作者

之前为了找出Sphinx中index ‘test1’: search error: query too complex, not enough stack (thread_stack=1217498K or higher required)这个bug,大致看了一下Sphinx的源码,发现问题的原因是在计算线程使用的空间时出错,具体原因依然没有找到,还在努力当中。在这个过程中,看到以下这段程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void sphSleepMsec ( int iMsec )
{

if ( iMsec<0 )
return;

#if USE_WINDOWS
Sleep ( iMsec );

#else
struct timeval tvTimeout;
tvTimeout.tv_sec = iMsec / 1000; // full seconds
tvTimeout.tv_usec = ( iMsec % 1000 ) * 1000; // remainder is msec, so *1000 for usec

select ( 0, NULL, NULL, NULL, &tvTimeout ); // FIXME? could handle EINTR
#endif
}

其实就是一个毫秒定时器,《UNIX环境编程》第14章的习题就要求实现一个这样的函数。看这段程序,又是令人恶心的匈牙利命名,把它改为正常点的比较好。程序中的注释”//FIXME?could handle EINR”说的是select会被SIGINT信号中断,那么这个定时器也会因为这个原因而被中断信号中断,看看能否提供不被中断的方法。立刻就想到了忽略中断信号,试了一下,其实还是挺容易的。难道这种直接忽略中断信号还是存在问题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <sys/select.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void sleep_ms(int msec) {//睡眠msec毫秒
if (msec < 0)
return;
signal(SIGINT, SIG_IGN);
struct timeval tv;
tv.tv_sec = msec / 1000;//除以1000,得到秒数
tv.tv_usec = (msec % 1000) * 1000;//得到剩余的毫秒数,之后乘以1000得到微秒数
select(0, NULL, NULL, NULL, &tv);
}
int main() {
printf("start sleeping\n");
sleep_ms(4000);
printf("finish sleeping\n");
return 0;
}

联系作者

最终的代码如下,这里修正了对nc命令无法处理的问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <netinet/tcp.h>
#include <ctype.h>
#include <assert.h>
#define MAX_EVENTS 10
#define PORT 9999
//从buf中得到命令
void _get_command(char *buf, char *cmd) {
int i = 0;
int j = 0;
while (!isalpha(buf[i]))
i++;
while (buf[i] != '\0' && buf[i] != ' ' && buf[i] != '\r' && buf[i] != '\n') {
cmd[j++] = buf[i];
i++;
}
cmd[j] = '\0';
}
// 返回成功发送的字节数
int Send(int sock, void *buffer, int size)
{

int nsend = 0, total = 0;
int err;
if(NULL == buffer || 0 == size) {
return 0;
}
while(size > 0) {
nsend = write(sock, (char*)buffer + total, size);
if(nsend == -1) {
err = errno;
if(EINTR == err) {
printf("send data to socket[%d], error is %s[%d]\n",
sock, strerror(err), err);
} else {
printf("Fail to send data to socket[%d], error is %s[%d]",
sock, strerror(err), err);
return -1;
}
} else {
total += nsend;
size -= nsend;
}
}
return total;
}
//处理重置命令
void process_reset(int sock){
char mess[] = "reset successful\r\n";
Send(sock, mess, strlen(mess));
return;
}
//处理错误命令
void process_error(int sock) {
char error[] = "ERROR\r\n";
Send(sock, error, strlen(error));
return;
}
void process_stats(int sock){
char mess[] = "stats successful\r\n";
Send(sock, mess, strlen(mess));
return;
}

//处理退出命令
void process_quit(int sock) {
close(sock);
}

int process_command(int sock, char *buf) {
assert(buf != NULL);

/*char cmd[BUFSIZ];
_get_command(buf, cmd);
printf("command: %s\n", cmd);

if (strcmp("stats",cmd) == 0) {
process_stats(sock);
} else if (strcmp("reset", cmd) == 0) {
process_reset(sock);
} else if (strcmp("quit", cmd) == 0){
process_quit(sock);
} else {
process_error(sock);
}*/

Send(sock, buf, strlen(buf));

return 0;
}

//设置socket为非阻塞
void setnonblocking(int sockfd) {
int opts;
opts = fcntl(sockfd, F_GETFL);
if (opts < 0) {
perror("fcntl(F_GETFL)\n");
exit(1);
}
opts = (opts | O_NONBLOCK);
if (fcntl(sockfd, F_SETFL, opts) < 0) {
perror("fcntl(F_SETFL)\n");
exit(1);
}
}
int main() {
int listenfd, conn_sock, epfd;
char buf[BUFSIZ];
socklen_t clilen;
struct sockaddr_in cliaddr, servaddr;
struct epoll_event ev, events[MAX_EVENTS];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd < 0) {
printf("create socket error\n");
return -1;
}
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
setnonblocking(listenfd);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);

if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(struct sockaddr))<0){
printf("bind error\n");
close(listenfd);
return -1;
}

if(listen(listenfd, 5) < 0) {
printf("listen error\n");
close(listenfd);
return -1;
}
epfd = epoll_create(MAX_EVENTS); //生成epoll专用的文件描述符
if (epfd == -1) {
printf("epoll_create\n");
return -1;
}
ev.events = EPOLLIN | EPOLLET; //设置处理的事件类型,设置为边沿触发
ev.data.fd = listenfd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev) == -1) {
printf("epoll_ctl add listen_sock fail\n");
close(listenfd);
return -1;
}
while (1) {
int timeout = 1000;
int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
if (nfds == -1) {
printf("epoll_wait\n");
return -1;
}
for (int i = 0;i < nfds; ++i) {
int fd = events[i].data.fd;
if (fd == listenfd) { //监听事件
while ((conn_sock = accept(listenfd, NULL, NULL)) > 0) {//循环处理accept,这样可以处理多个连接在就绪队列中的情况
printf("accept %d\n", conn_sock);
setnonblocking(conn_sock);
ev.events = EPOLLIN | EPOLLET; //设置为边沿触发
ev.data.fd = conn_sock;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock, &ev) == -1) {
printf("epoll_ctl: add fail\n");
close(conn_sock);
return -1;
}
}
} else if (events[i].events & EPOLLIN) {//读事件,说明有数据从客户端发来
int n = 0;
int nread = 0;
while ((nread = read(fd, buf + n, BUFSIZ - n)) > 0) {
n += nread;
}
//printf("n %d nread %d\n", n, nread);
if (nread == -1 && errno != EAGAIN) {//读数据错误,关闭描述符
printf("read error\n");
close(fd); //关闭一个描述符,它会从epoll描述符集合中自动删除
continue;
}
if( n > 0) {
process_command(fd, buf);
memset(buf, 0, sizeof(buf));
}
if(nread == 0) { //客户端关闭连接,关闭相应的描述符
close(fd);
continue;
}
}
}
}
}

联系作者