<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_达达的博客</title><subtitle type="text"/><id>http://feed.cnblogs.com/blog/u/49672/rss</id><updated>2010-11-26T05:58:20Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/49672/rss"/><entry><id>http://www.cnblogs.com/mingda/archive/2010/11/14/hight_performance_socket_server_02.html</id><title type="text">高性能Socket服务器编程－02</title><summary type="text">上一章，我向大家演示了一个最基本的socket服务器结构，它一次只能响应一个连接请求，而“能同时响应多个连接和请求”无疑是现实生活中对socket服务器的最基本要求。要如何让socket服务器可以同时响应多个连接和请求呢？多线和多进程程肯定是大部分人首先想到的，可能很多人不一定真正清楚多线程和多进程的socket服务器架构具体意味着什么，但是至少大家都或多或少听说过这两种技术。不过本章中，我们暂时还不会涉及到多线程和多进程的服务器架构，我它们归类为设计范畴，而我们暂时还没有脱离泥水匠身份，所以还要继续学习“泥沙之用途“，设计的事情需要等到我们泥水匠毕业，升级建筑设计师的时候再说。那么本章具体的内容是什么呢？真是没有悬念，在上一章中我已经提前透露了：IO重用。下面就正式进入主题吧。</summary><published>2010-11-14T06:08:00Z</published><updated>2010-11-14T06:08:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/11/14/hight_performance_socket_server_02.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/11/14/hight_performance_socket_server_02.html"/><content type="html">&lt;p&gt;原文地址：&lt;a href="http://unbe.cn/hight_performance_socket_server_02/"&gt;http://unbe.cn/hight_performance_socket_server_02/&lt;/a&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;上一章，我向大家演示了一个最基本的socket服务器结构，它一次只能响应一个连接请求，而“能同时响应多个连接和请求”无疑是现实生活中对socket服务器的最基本要求。要如何让socket服务器可以同时响应多个连接和请求呢？多线和多进程程肯定是大部分人首先想到的，可能很多人不一定真正清楚多线程和多进程的socket服务器架构具体意味着什么，但是至少大家都或多或少听说过这两种技术。不过本章中，我们暂时还不会涉及到多线程和多进程的服务器架构，我它们归类为设计范畴，而我们暂时还没有脱离泥水匠身份，所以还要继续学习“泥沙之用途“，设计的事情需要等到我们泥水匠毕业，升级建筑设计师的时候再说。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;那么本章具体的内容是什么呢？真是没有悬念，在上一章中我已经提前透露了：IO重用。下面就正式进入主题吧。&lt;/p&gt;&#xD;
&lt;!--more--&gt;&#xD;
&lt;p&gt;&lt;strong&gt;什么是IO重用&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;我们先来想像这样一个场景：一个只有一个柜台一个营业员的银行营业厅。这个银行所有业务都需要填表格，而且如果填了表格，熟练的营业员能在一瞬间帮你把事情办完。在繁忙的时候，大家排成长队等待轮到自己，当排到的时候从营业员手中拿到表格，然后填写一番，接着交给营业员处理。很显然，这个营业厅很低效，低效在哪里呢？它有优秀的营业员，但是缺少合理的运作模式。实际上后面的人排队等待的时间不是业务处理时间，而是前面的人的填表事件。万一遇到需要连续办多件事情的客户，他堵在那里填了一份又一份表格，后面的人只能一直等着了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;我们怎么改进这个营业厅呢？我们可以在大厅设置一个自行领取表格和填写表格的桌子，让大家先填好了表格再到柜台办理业务，这样高效的营业员就能非常快速的处理业务，就几乎不可能出现排队了（注意，我说的是"几乎"不是绝对）。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;而我们前一章演示的socket服务器程序，就像改进之前的银行营业厅，它的低效在于它没有充分利用IO，而不是它有复杂的业务逻辑。所以人们为了解决这类问题就在设计操作系统的时候加入了IO重用机制，让编程人员可以更有效的利用IO，就像提供了领取和填写表格的桌子一样。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;IO重用技术有很多种，有些是夸平台的，有些是平台独有的，这里就列举一些我知道的：&lt;/p&gt;&#xD;
&#xD;
&lt;table&gt;&#xD;
&lt;thead&gt;&#xD;
&lt;tr&gt;&lt;td&gt;名称&lt;/td&gt;&lt;td&gt;平台&lt;/td&gt;&lt;/tr&gt;&#xD;
&lt;/thead&gt;&#xD;
&lt;tbody&gt;&#xD;
&lt;tr&gt;&lt;td&gt;select&lt;/td&gt;&lt;td&gt;Linux, *BSD, Mac OS X, Solaris, Windows&lt;/td&gt;&lt;tr&gt;&#xD;
&lt;tr&gt;&lt;td&gt;poll&lt;/td&gt;&lt;td&gt;Linux, *BSD, Mac OS X&lt;/td&gt;&lt;tr&gt;&#xD;
&lt;tr&gt;&lt;td&gt;epoll&lt;/td&gt;&lt;td&gt;Linux&lt;/td&gt;&lt;tr&gt;&#xD;
&lt;tr&gt;&lt;td&gt;/dev/poll&lt;/td&gt;&lt;td&gt;Solaris&lt;/td&gt;&lt;tr&gt;&#xD;
&lt;tr&gt;&lt;td&gt;kqueue&lt;/td&gt;&lt;td&gt;FreeBSD&lt;/td&gt;&lt;tr&gt;&#xD;
&lt;tr&gt;&lt;td&gt;IOCP&lt;/td&gt;&lt;td&gt;Windows&lt;/td&gt;&lt;tr&gt;&#xD;
&lt;/tbody&gt;&#xD;
&lt;/table&gt;&#xD;
&#xD;
&lt;p&gt;每一种IO重用技术都是通过操作系统提供的一组特定的API函数调用来提供支持的，我们所要学的就是学会怎么用这些API并且了解每种技术背后的原理和来龙去脉。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;本章就从最通用应该也是最早出现的IO重用技术select开始。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;补充说明一点，IO重用并不局限于用在socket编程上，只要涉及到IO的编程都可以应用。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;&lt;strong&gt;select的用法&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;select技术主要由一个函数和几个宏来提供支持，下面是它们的说明：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;/*&#xD;
 * 功能：监视多个文件描述符，直到有一个或多个文件描述符准备好做某种IO操作时返回&#xD;
 * 返回：当调用成功时，返回已准备好的文件描述符个数；发生错误时返回-1，可以通过errno得到错误类型&#xD;
 * 参数：&#xD;
 *      nfds      － 后面三个文件描述符集合中最大的文件描述符加1（想想为什么？）&#xD;
 *      readfds   － 等待进行读操作的文件描述符，指针传递，在select返回时会改变这个参数的值，只保留已准备好的文件描述符&#xD;
 *      writefds  － 等待进行写操作的文件描述符，指针传递，在select返回时会改变这个参数的值，只保留已准备好的文件描述符&#xD;
 *      exceptfds － 监视异常的文件描述符号，很少用到&#xD;
 *      timeout   － 程序会在select调用的地方阻塞，你可以通过设置超时让程序可以在一定时间间隔后继续执行&#xD;
 */&#xD;
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);&#xD;
&#xD;
/*&#xD;
 * 功能：将指定文件描述符从指定的描述符集合中清除&#xD;
 * 参数：&#xD;
 *      fd  － 文件描述符&#xD;
 *      set － 文件描述符集合&#xD;
 */&#xD;
void FD_CLR(int fd, fd_set *set);&#xD;
&#xD;
/*&#xD;
 * 功能：检查一个文件描述符是否在集合中&#xD;
 * 返回：存在时返回非0整数，不存在则返回0&#xD;
 * 参数：&#xD;
 *      fd  － 文件描述符&#xD;
 *      set － 文件描述符集合&#xD;
 */&#xD;
int  FD_ISSET(int fd, fd_set *set);&#xD;
&#xD;
/*&#xD;
 * 功能：设置一个文件描述符到集合中&#xD;
 * 参数：&#xD;
 *      fd  － 文件描述符&#xD;
 *      set － 文件描述符集合&#xD;
 */&#xD;
void FD_SET(int fd, fd_set *set);&#xD;
&#xD;
/*&#xD;
 * 功能：将一个文件描述符集合清零&#xD;
 * 参数：&#xD;
 *      set － 文件描述符集合&#xD;
 */&#xD;
void FD_ZERO(fd_set *set);&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;在socket编程中，典型的select用法是：在创建监听的服务器socket文件描述符后，使用FD_ZERO初始化一个空的文件描述符集合，然后把监听的socket文件描述符通过FD_SET放入"读"集合中，然后进入服务器循环，当select返回时，用FD_ISSET检查readfds中是否有监听的socket文件描述符时，如果有说明有新的连接请求，这时候就调用accept接受连接，把accept返回的连接文件描述符放入自己维护的一个连接集合中，并把连接的文件描述符通过FD_SET放入"读"集合中，通过循环自己维护的连接集合中的文件描述符，调用FD_ISSET来判断是否某个连接有数据可读取，如果有就读取并处理。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;上面这么说可能很笼统也很模糊，下面通过具体的代码演示select的用法和连接的维护让大家对select有一个具象的了解：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;#include &amp;lt;stdio.h&amp;gt;&#xD;
#include &amp;lt;stdlib.h&amp;gt;&#xD;
#include &amp;lt;string.h&amp;gt;&#xD;
#include &amp;lt;sys/socket.h&amp;gt;&#xD;
#include &amp;lt;netinet/in.h&amp;gt;&#xD;
&#xD;
&#xD;
#define SD_PORT         10086&#xD;
#define SD_BACK_LOG     10&#xD;
#define SD_MAX_CLIENT   3&#xD;
&#xD;
&#xD;
int sd_listener_fd;&#xD;
&#xD;
&#xD;
void &#xD;
sd_init ()&#xD;
{&#xD;
    int reuse = 1;&#xD;
    &#xD;
    struct sockaddr_in addr;&#xD;
&#xD;
    if ((sd_listener_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1)&#xD;
    {&#xD;
        perror("Create listener socket failed");&#xD;
        exit(-1);&#xD;
    }&#xD;
&#xD;
    if (setsockopt(sd_listener_fd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;reuse, sizeof(reuse)) == -1)&#xD;
    {&#xD;
        perror("Setup listener socket failed");&#xD;
        exit(-1);&#xD;
    }&#xD;
&#xD;
    bzero(&amp;amp;(addr.sin_zero), 8);&#xD;
&#xD;
    addr.sin_family      = AF_INET;&#xD;
    addr.sin_port        = htons(SD_PORT);&#xD;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);&#xD;
&#xD;
    if (bind(sd_listener_fd, (struct sockaddr *)&amp;amp;addr, sizeof(addr)) == -1)&#xD;
    {&#xD;
        perror("Bind listener socket address failed");&#xD;
        exit(-1);&#xD;
    }&#xD;
&#xD;
    if (listen(sd_listener_fd, SD_BACK_LOG) == -1)&#xD;
    {&#xD;
        perror("Listen port failed");&#xD;
        exit(-1);&#xD;
    }&#xD;
}&#xD;
&#xD;
&#xD;
void   &#xD;
sd_loop ()&#xD;
{&#xD;
    char buf[1024];&#xD;
&#xD;
    int i = 0, j = 0;&#xD;
&#xD;
    int ret = 0;&#xD;
&#xD;
    int client_fd;&#xD;
&#xD;
    int client_addr_len;&#xD;
&#xD;
    int client_fds[SD_MAX_CLIENT];&#xD;
&#xD;
    int client_fd_max_i = -1;&#xD;
&#xD;
    int max_fd = sd_listener_fd;&#xD;
&#xD;
    struct sockaddr_in client_addr;&#xD;
&#xD;
    fd_set read_fds, ready_read_fds;&#xD;
&#xD;
    FD_ZERO(&amp;amp;read_fds);&#xD;
    FD_SET(sd_listener_fd, &amp;amp;read_fds);&#xD;
&#xD;
    for (i = 0; i &amp;lt; SD_MAX_CLIENT; i++)&#xD;
    {&#xD;
        client_fds[i] = -1;&#xD;
    }&#xD;
&#xD;
    printf("Waiting connect on port %d\n", SD_PORT);&#xD;
&#xD;
    for (;;)&#xD;
    {&#xD;
        ready_read_fds = read_fds;&#xD;
&#xD;
        ret = select(max_fd + 1, &amp;amp;ready_read_fds, NULL, NULL, NULL);&#xD;
&#xD;
        if (ret == -1)&#xD;
        {&#xD;
            perror("Select failed");&#xD;
            break;&#xD;
        }&#xD;
&#xD;
        if (ret == 0)&#xD;
            continue;&#xD;
&#xD;
        if (FD_ISSET(sd_listener_fd, &amp;amp;ready_read_fds))&#xD;
        {&#xD;
            client_fd = accept(sd_listener_fd, (struct sockaddr *)&amp;amp;client_addr, &amp;amp;client_addr_len);&#xD;
&#xD;
            FD_SET(client_fd, &amp;amp;read_fds);&#xD;
&#xD;
            for (i = 0; i &amp;lt; SD_MAX_CLIENT; i++)&#xD;
            {&#xD;
                if (client_fds[i] == -1)&#xD;
                {&#xD;
                    client_fds[i] = client_fd;&#xD;
                    break;&#xD;
                }&#xD;
            }&#xD;
&#xD;
            printf("Client connected\n");&#xD;
&#xD;
            if (i == SD_MAX_CLIENT - 1)&#xD;
            {&#xD;
                FD_CLR(sd_listener_fd, &amp;amp;read_fds);&#xD;
&#xD;
                printf("Connection is full\n");&#xD;
            }&#xD;
&#xD;
            if (client_fd &amp;gt; max_fd)&#xD;
                max_fd = client_fd;&#xD;
&#xD;
            if (i &amp;gt; client_fd_max_i)&#xD;
                client_fd_max_i = i;&#xD;
&#xD;
            if (-- ret == 0)&#xD;
                continue;&#xD;
        }&#xD;
&#xD;
        for (i = 0; i &amp;lt;= client_fd_max_i; i ++)&#xD;
        {&#xD;
            client_fd = client_fds[i];&#xD;
&#xD;
            if (client_fd == -1)&#xD;
                continue;&#xD;
&#xD;
            if (FD_ISSET(client_fd, &amp;amp;ready_read_fds))&#xD;
            {&#xD;
                if ((ret = read(client_fd, buf, 1024)) == 0)&#xD;
                {&#xD;
                    close(client_fd);&#xD;
    &#xD;
                    FD_CLR(client_fd, &amp;amp;read_fds);&#xD;
&#xD;
                    client_fds[i] = -1;&#xD;
&#xD;
                    printf("Client closed\n");&#xD;
&#xD;
                    if (!FD_ISSET(sd_listener_fd, &amp;amp;read_fds))&#xD;
                    {&#xD;
                        FD_SET(sd_listener_fd, &amp;amp;read_fds);&#xD;
&#xD;
                        printf("Listener come back\n");&#xD;
                    }&#xD;
&#xD;
                    max_fd = sd_listener_fd;&#xD;
&#xD;
                    for (j = 0; j &amp;lt;= client_fd_max_i; j ++)&#xD;
                    {&#xD;
                        if (client_fds[j] &amp;gt; max_fd)&#xD;
                            max_fd = client_fds[j];&#xD;
                    }&#xD;
&#xD;
                    if (i == client_fd_max_i)&#xD;
                    {&#xD;
                        while ((client_fd_max_i -= 1) &amp;lt; -1)&#xD;
                        {&#xD;
                            if (client_fds[client_fd_max_i] != -1)&#xD;
                                break;&#xD;
                        }&#xD;
                    }&#xD;
                }&#xD;
                else&#xD;
                {&#xD;
                    write(client_fd, buf, ret);&#xD;
                }&#xD;
&#xD;
                if (-- ret == 0)&#xD;
                    break;&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&#xD;
&#xD;
void&#xD;
sd_down ()&#xD;
{&#xD;
    close(sd_listener_fd);&#xD;
&#xD;
    printf("Server shutdown\n");&#xD;
}&#xD;
&#xD;
&#xD;
int&#xD;
main (int argc, char *argv[])&#xD;
{&#xD;
    sd_init();&#xD;
&#xD;
    sd_loop();&#xD;
&#xD;
    sd_down();&#xD;
&#xD;
    return 1;&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;上面的代码比前一章的例子复杂了很多，因为现在我们的echo服务器已经具备了同时响应多个客户端的能力。编译方式还是跟前一章一样，这里就不再重复说明。你现在可以同时用多个telnet连接到服务器上进行测试，为了方便测试，我通过SD_MAX_CLIENT指定了最大连接数是3，当达到最大连接数时，服务器就不再接受新的连接了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;比起之前的例子，代码增加都在sd_loop中，看起来很长，但如果分解开来其实逻辑很清晰。前面一大块是变量声明，接着进入无限循环，循环周期的开头阻塞在select调用，直到有文件描述准备好做操作select才返回，然后就是一个if包围的新连接接入处理，接着是for循环包围的连接请求处理。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;因为select返回时会改变参数的值，所以在每次select之前，我们都会把read_fds赋值给ready_read_fds，然后把ready_read_fds传递给select函数，这样read_fds本身就不会受到影响了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;&lt;strong&gt;示例中没有的东西&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;上面的示例只有一个模式，而select函数实际上有三种使用模式，具体内容大家可以通过man select查阅文档。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;示例中代码只体现了select的用法，并没有直接告诉我们select背后的机制和原理，不过我们通过使用方式来自己推测个大概。当然，我们也可以通过阅读Linux内核代码做到100%了解，但这就离我们主题有些远了，留给大家自己研究吧。我们把监听的文件描诉符集合丢给select，自然select内部会遍历监视这些文件描诉符，然后把具备特定状态的文件描述符保留在监视集合中其余的清除，然后把集合返回给调用者。所以select内部应该是一个遍历过程，而遍历过程需要有遍历结束的判断，所以才会需要我们传入maxfd作为参数。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;示例中不能体现出的还有一点就是select的局限性，select能较好的解决io重用的问题，至少大幅度的提高了我们程序中io的使用效率（从无到有），但是它并不是完美的，它也有一些需要改进的地方。select在有些操作系统上有单个进程监听的文件描述符个数限制，至少通过万能的网络，我可以确认在Linux和Windows内核中的确有这样的限制，Linux内核代码通过一个FD_SETSIZE的宏约束了文件描诉符集合的元素上限，这个值默认是1024，也就是说我们示例程序监视的客户端连接最多只能有1024个，网上也有人提供各种奇技淫巧来突破这个限制，不过我不推荐这么做，因为这样做不自然，并且还有其他的io重用技术可以让我们选用。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;我们的示例代码业务逻辑很简单，它只是简单返回收到的内容，但假设我们现在做的是一个MMORPG游戏的服务器，它在收到请求是可能需要执行复杂的游戏操作逻辑，那么socket服务器是不是很可能在业务逻辑处理的地方阻塞了呢？就像本章开头的银行里子中，我们假设了营业员充分高效，但现实生活中并不一定是这样。这就是有待我们解决的问题了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;示例中只用到select的readfds参数。原因很简单，为了让代码简单，如果用上writefds意味着就要区分开客户端的读和写处理，这会让代码更加复杂，对于我们演示select的基本使用来说这是不利的。但是对于高性能socket服务器来说区分读和写是必需的，因为你得用尽一切办法避免阻塞，上面的例子在实际应用中，很可能会在write的地方由于客户端没有准备好或者网络不畅导致程序在此位置阻塞，这就像是银行那个例子中有人没填好表格就跑到柜台前占着位置填表格，其实没有在做正事但后面的人都得等他一样，会影响到整体的执行效率。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;下面是经过进一步抽象和细化的echo服务器，它已经将读写事件区分开，并把服务器状态和连接抽象成不同的数据结构，由不同的函数负责不同操作：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;#include &amp;lt;stdio.h&amp;gt;&#xD;
#include &amp;lt;stdlib.h&amp;gt;&#xD;
#include &amp;lt;string.h&amp;gt;&#xD;
#include &amp;lt;sys/socket.h&amp;gt;&#xD;
#include &amp;lt;netinet/in.h&amp;gt;&#xD;
&#xD;
&#xD;
#define SD_PORT        10086&#xD;
#define SD_BACK_LOG    10&#xD;
#define SD_MAX_CONN    10&#xD;
#define sd_MAX_CONN    2000&#xD;
&#xD;
&#xD;
typedef struct&#xD;
{&#xD;
    int      buf_len;&#xD;
    char*    buf;&#xD;
}&#xD;
sd_conn_state;&#xD;
&#xD;
&#xD;
typedef struct&#xD;
{&#xD;
    int                   id;&#xD;
    int                   fd;&#xD;
    struct sockaddr_in    addr;&#xD;
&#xD;
    sd_conn_state*        state;&#xD;
}&#xD;
sd_conn;&#xD;
&#xD;
&#xD;
typedef struct&#xD;
{&#xD;
    int         max_fd;&#xD;
    int         listener_fd;&#xD;
&#xD;
    fd_set      read_fds;&#xD;
    fd_set      can_read_fds;&#xD;
&#xD;
    fd_set      write_fds;&#xD;
    fd_set      can_write_fds;&#xD;
&#xD;
    int         conn_max;&#xD;
    int         conn_free;&#xD;
    int         conn_count;&#xD;
&#xD;
    sd_conn*    conn_items;&#xD;
}&#xD;
sd_state;&#xD;
&#xD;
&#xD;
void &#xD;
sd_init (sd_state* state)&#xD;
{&#xD;
    int i;&#xD;
&#xD;
    sd_conn* conn;&#xD;
&#xD;
    int reuse = 1;&#xD;
    &#xD;
    struct sockaddr_in addr;&#xD;
&#xD;
    if ((state-&amp;gt;listener_fd = socket(PF_INET, SOCK_STREAM, 0)) == -1)&#xD;
    {&#xD;
        perror("Create listener socket failed");&#xD;
        exit(1);&#xD;
    }&#xD;
&#xD;
    if (setsockopt(state-&amp;gt;listener_fd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;reuse, sizeof(reuse)) == -1)&#xD;
    {&#xD;
        perror("Setup listener socket failed");&#xD;
        exit(1);&#xD;
    }&#xD;
&#xD;
    bzero(&amp;amp;(addr.sin_zero), 8);&#xD;
&#xD;
    addr.sin_family      = AF_INET;&#xD;
    addr.sin_port        = htons(SD_PORT);&#xD;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);&#xD;
&#xD;
    if (bind(state-&amp;gt;listener_fd, (struct sockaddr *)&amp;amp;addr, sizeof(addr)) == -1)&#xD;
    {&#xD;
        perror("Bind listener socket address failed");&#xD;
        exit(1);&#xD;
    }&#xD;
&#xD;
    if (listen(state-&amp;gt;listener_fd, SD_BACK_LOG) == -1)&#xD;
    {&#xD;
        perror("Listen port failed");&#xD;
        exit(1);&#xD;
    }&#xD;
&#xD;
    state-&amp;gt;max_fd = state-&amp;gt;listener_fd;&#xD;
&#xD;
    FD_ZERO(&amp;amp;state-&amp;gt;read_fds);&#xD;
    FD_ZERO(&amp;amp;state-&amp;gt;can_read_fds);&#xD;
&#xD;
    FD_ZERO(&amp;amp;state-&amp;gt;write_fds);&#xD;
    FD_ZERO(&amp;amp;state-&amp;gt;can_write_fds);&#xD;
&#xD;
    FD_SET(state-&amp;gt;listener_fd, &amp;amp;state-&amp;gt;read_fds);&#xD;
&#xD;
    state-&amp;gt;conn_max   = -1;&#xD;
    state-&amp;gt;conn_free  = SD_MAX_CONN;&#xD;
    state-&amp;gt;conn_count = SD_MAX_CONN;&#xD;
    state-&amp;gt;conn_items = calloc(SD_MAX_CONN, sizeof(sd_conn));&#xD;
&#xD;
    for (i = 0; i &amp;lt; SD_MAX_CONN; i++)&#xD;
    {&#xD;
        conn = &amp;amp;state-&amp;gt;conn_items[i];&#xD;
&#xD;
        conn-&amp;gt;id = -1;&#xD;
        conn-&amp;gt;fd = -1;&#xD;
        conn-&amp;gt;state = NULL;&#xD;
    }&#xD;
}&#xD;
&#xD;
void&#xD;
sd_conn_accept (sd_state* state)&#xD;
{&#xD;
    int i;&#xD;
&#xD;
    int addr_size;&#xD;
&#xD;
    sd_conn* conn;&#xD;
&#xD;
    for (i = 0; i &amp;lt; state-&amp;gt;conn_count; i ++)&#xD;
    {&#xD;
        conn = &amp;amp;state-&amp;gt;conn_items[i];&#xD;
&#xD;
        if (conn-&amp;gt;fd &amp;gt;= 0)&#xD;
            continue;&#xD;
&#xD;
        conn-&amp;gt;id = i;&#xD;
        conn-&amp;gt;state = NULL;&#xD;
&#xD;
        addr_size = sizeof(conn-&amp;gt;addr);&#xD;
&#xD;
        conn-&amp;gt;fd = accept(state-&amp;gt;listener_fd, (struct sockaddr *)&amp;amp;conn-&amp;gt;addr, &amp;amp;addr_size);&#xD;
&#xD;
        if (conn-&amp;gt;fd &amp;gt; state-&amp;gt;max_fd)&#xD;
            state-&amp;gt;max_fd = conn-&amp;gt;fd;&#xD;
&#xD;
        if (conn-&amp;gt;id &amp;gt; state-&amp;gt;conn_max)&#xD;
            state-&amp;gt;conn_max = conn-&amp;gt;id;&#xD;
&#xD;
        FD_SET(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;read_fds);&#xD;
&#xD;
        state-&amp;gt;conn_free --;&#xD;
&#xD;
        printf("Client connected\n");&#xD;
&#xD;
        break;&#xD;
    }&#xD;
}&#xD;
&#xD;
void&#xD;
sd_conn_close (sd_state* state, sd_conn* conn)&#xD;
{&#xD;
    int i;&#xD;
&#xD;
    sd_conn* temp_conn;&#xD;
&#xD;
    if (conn-&amp;gt;id == state-&amp;gt;conn_max)&#xD;
    {&#xD;
        for (i = conn-&amp;gt;id - 1; i &amp;gt;= 0; i --)&#xD;
        {&#xD;
            if (i &amp;lt; 0)&#xD;
            {&#xD;
                state-&amp;gt;conn_max = -1;&#xD;
&#xD;
                break;&#xD;
            }&#xD;
&#xD;
            temp_conn = &amp;amp;state-&amp;gt;conn_items[i];&#xD;
&#xD;
            if (temp_conn-&amp;gt;fd &amp;gt; 0)&#xD;
            {&#xD;
                state-&amp;gt;conn_max = temp_conn-&amp;gt;id;&#xD;
&#xD;
                break;&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
&#xD;
    if (conn-&amp;gt;fd == state-&amp;gt;max_fd)&#xD;
    {&#xD;
        if (state-&amp;gt;conn_max &amp;gt;= 0)&#xD;
        {&#xD;
            state-&amp;gt;max_fd = state-&amp;gt;listener_fd;&#xD;
&#xD;
            for (i = 0; i &amp;lt;= state-&amp;gt;conn_max; i ++)&#xD;
            {&#xD;
                temp_conn = &amp;amp;state-&amp;gt;conn_items[i];&#xD;
&#xD;
                if (temp_conn-&amp;gt;fd &amp;gt; state-&amp;gt;max_fd)&#xD;
                {&#xD;
                    state-&amp;gt;max_fd = temp_conn-&amp;gt;fd;&#xD;
                }&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
&#xD;
    FD_CLR(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;read_fds);&#xD;
    FD_CLR(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;write_fds);&#xD;
&#xD;
    close(conn-&amp;gt;fd);&#xD;
&#xD;
    conn-&amp;gt;id = -1;&#xD;
    conn-&amp;gt;fd = -1;&#xD;
    &#xD;
    if (conn-&amp;gt;state != NULL)&#xD;
    {&#xD;
        free(conn-&amp;gt;state);&#xD;
    }&#xD;
&#xD;
    state-&amp;gt;conn_free ++;&#xD;
    &#xD;
    printf("Client close\n");&#xD;
}&#xD;
&#xD;
void&#xD;
sd_conn_proc (sd_state* state, sd_conn* conn)&#xD;
{&#xD;
    int ret;&#xD;
&#xD;
    char* buf;&#xD;
&#xD;
    buf = calloc(1024, sizeof(char));&#xD;
&#xD;
    if ((ret = read(conn-&amp;gt;fd, buf, 1024)) == 0)&#xD;
    {&#xD;
        sd_conn_close(state, conn);&#xD;
&#xD;
        free(buf);&#xD;
    }&#xD;
    else&#xD;
    {&#xD;
        conn-&amp;gt;state = calloc(1, sizeof(sd_conn_state));&#xD;
&#xD;
        conn-&amp;gt;state-&amp;gt;buf = buf;&#xD;
        conn-&amp;gt;state-&amp;gt;buf_len = ret;&#xD;
&#xD;
        FD_SET(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;write_fds);&#xD;
    }&#xD;
}&#xD;
&#xD;
void&#xD;
sd_conn_repo (sd_state* state, sd_conn* conn)&#xD;
{&#xD;
    if (conn-&amp;gt;state == NULL)&#xD;
        return;&#xD;
&#xD;
    write(conn-&amp;gt;fd, conn-&amp;gt;state-&amp;gt;buf, conn-&amp;gt;state-&amp;gt;buf_len);&#xD;
&#xD;
    free(conn-&amp;gt;state-&amp;gt;buf);&#xD;
    free(conn-&amp;gt;state);&#xD;
&#xD;
    conn-&amp;gt;state = NULL;&#xD;
&#xD;
    FD_CLR(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;write_fds);&#xD;
}&#xD;
&#xD;
&#xD;
void   &#xD;
sd_loop (sd_state* state)&#xD;
{&#xD;
    int i = 0;&#xD;
&#xD;
    sd_conn* conn;&#xD;
&#xD;
    for (;;)&#xD;
    {&#xD;
        state-&amp;gt;can_read_fds = state-&amp;gt;read_fds;&#xD;
        state-&amp;gt;can_write_fds = state-&amp;gt;write_fds;&#xD;
&#xD;
        int num_ready = select(state-&amp;gt;max_fd + 1, &amp;amp;state-&amp;gt;can_read_fds, &amp;amp;state-&amp;gt;can_write_fds, NULL, NULL);&#xD;
&#xD;
        if (num_ready == 0)&#xD;
            continue;&#xD;
&#xD;
        if (num_ready == -1)&#xD;
        {&#xD;
            perror("Select failed\n");&#xD;
            exit(1);&#xD;
        }&#xD;
&#xD;
        if (FD_ISSET(state-&amp;gt;listener_fd, &amp;amp;state-&amp;gt;can_read_fds))&#xD;
        {&#xD;
            sd_conn_accept(state);&#xD;
&#xD;
            if (-- num_ready == 0)&#xD;
                continue;&#xD;
        }&#xD;
&#xD;
        for (i = 0; i &amp;lt;= state-&amp;gt;conn_max; i ++)&#xD;
        {&#xD;
            conn = &amp;amp;state-&amp;gt;conn_items[i];&#xD;
&#xD;
            if (conn-&amp;gt;fd == -1)&#xD;
                continue;&#xD;
&#xD;
            if (FD_ISSET(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;can_read_fds))&#xD;
            {&#xD;
                sd_conn_proc(state, conn);&#xD;
&#xD;
                if (-- num_ready == 0)&#xD;
                    break;&#xD;
            }&#xD;
&#xD;
            if (FD_ISSET(conn-&amp;gt;fd, &amp;amp;state-&amp;gt;can_write_fds))&#xD;
            {&#xD;
                sd_conn_repo(state, conn);&#xD;
&#xD;
                if (-- num_ready == 0)&#xD;
                    break;&#xD;
            }&#xD;
        }&#xD;
    }&#xD;
}&#xD;
&#xD;
&#xD;
void&#xD;
sd_down (sd_state* state)&#xD;
{&#xD;
    close(state-&amp;gt;listener_fd);&#xD;
&#xD;
    printf("Server shutdown\n");&#xD;
}&#xD;
&#xD;
&#xD;
int&#xD;
main (int argc, char *argv[])&#xD;
{&#xD;
    sd_state state;&#xD;
&#xD;
    sd_init(&amp;amp;state);&#xD;
&#xD;
    sd_loop(&amp;amp;state);&#xD;
&#xD;
    sd_down(&amp;amp;state);&#xD;
&#xD;
    return 1;&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;&lt;strong&gt;本章总结&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;本章通过演示select的使用向大家展示了io重用技术是怎样提高io使用效率的，让我们的socket服务器程序的可用向前迈了一大步。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;但是迈出这步后，还有无数的挑战的等着我们，例如上面说的select的限制、复杂业务逻辑阻塞，等等。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;后续我可能会继续花一到两章来描述poll和epoll，但也可能直接就介绍如何使用夸平台的libev和libevent库。特别是poll实际上它对select来说没有改进的地方，属于同一水平，实际上它们只是起源不一样，但是时代差不多所以水平也就差不多，而后续的epoll、dev/poll、iocp则是百家争鸣时期各个操作系统平台为了进一步提高io重用效率而设计的新机制，它们才本质上对select和poll等老模式进行了改进，所以我可能会跳过poll介绍epoll。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;libev的实验代码我其实已经做好了，poll和epoll有点懒得重复了，具体怎么样还不确定就留个悬念吧，呵呵。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/mingda/aggbug/1876961.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/11/14/hight_performance_socket_server_02.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/11/12/hight_performance_socket_server_01.html</id><title type="text">高性能Socket服务器编程－01</title><summary type="text">网络编程一直都是最吸引人、最有挑战的编程领域。从这篇文章开始，达达将同大家一起向这个领域出发，并接受各种难题的挑战，你准备好了吗？</summary><published>2010-11-12T02:47:00Z</published><updated>2010-11-12T02:47:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/11/12/hight_performance_socket_server_01.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/11/12/hight_performance_socket_server_01.html"/><content type="html">&lt;p&gt;原文地址：&lt;a href="http://unbe.cn/hight_performance_socket_server_01/"&gt;http://unbe.cn/hight_performance_socket_server_01/&lt;/a&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;网络编程一直都是最吸引人、最有挑战的编程领域。从这篇文章开始，达达将同大家一起向这个领域出发，并接受各种难题的挑战，你准备好了吗？&lt;/p&gt;&#xD;
&lt;!--more--&gt;&#xD;
&lt;p&gt;&lt;strong&gt;写在开始之前&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;在开始之前，达达有一些题外话想先跟大家说说。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;在阅读这一系列的文章时，我希望大家始终记住以下几点：&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;1. 软件开发没有银弹，人们总是试图找到问题的唯一解和最优解，但事实是每个问题都有N种解，并且在不同情况下最优解是不一样的，如果非要说软件开发有银弹，那么这颗银弹就是人的心，是否找能到最优解，在于你是否能把握住了所有事情的平衡点。所以，请不要说某某机制最好、某某算法最优、某某架构万能、不需要再了解其他了。也请不要自以为目空一切技术，商业和盈利至上，实现途径和方式无所谓。请抱着一切皆有可能的心态看待所有事物，才有更多机会看到平衡点。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;2. 语言、平台、API只是迷人眼的东西，它们好像什么都是，其实什么都不是。解决问题的关键在于设计者的心，设计者是否对要解决的问题和问题的上下文了然于心。不要把学习语言、学习平台、学习API当作目标，它们只是泥沙和工具，最终我们要建造的是房屋，所以请把目标放得更远。也不要把它们当成阻碍，因为它们生来就是要让人使用的东西，不可能形成阻碍，如果你觉得它们是阻碍，那实际上那个阻碍只在你心里。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;3. 记得：Do one thing, and do it well。一次只做一件事，并且做好它。这一系列文章的基本开发和测试环境是Linux，编译器是gcc。如果你之前对Linux不是很熟悉，我建议你安装一个VMware，并安装Ubuntu桌面系统，然后apt-get install build-essential，这样你的系统里就有完整的开发环境了，gnome自带的gedit很好用，我也是这么做的。不要一上来就Linux命令行界面加vi编辑器，没必要为难自己也不要搞得自己像黑客一样。请记住，我们当前要做的是高性能socket服务器，只做这一件事，并且做好它！不是研究Linux命令行或者vi编辑器，那些等有空再慢慢研究还来的及。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;4. 师傅请进门修行靠个人。文章和教程的内容其实都是转之由转的东西，如果要了解原汁原味的内容，首选应该去阅读操作系统的代码，其次是系统文档，再次才是网络教程。而经验是世界上最难传达的东西，文字只能让你形成记忆，不能让你获得经验，它只是像买彩票一样给你提供一个机会，让在你实践过程中可能会有那么一下的灵光一现，然后得出自己的结论，那才是你的真正经验。如果把生活比喻成RPG，造物主怎么可能让经验可以在玩家之间传递呢？那不是乱了套了，打RPG我们可以学各种技能，但是要得到经验就得打怪做任务，生活其实也是一样的道理。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;好了废话就到此结束。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;&lt;strong&gt;让我们开始吧！&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;别急，别急，勿在浮沙筑高台～～！&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;要开始建造我们的高性能socket服务器大厦之前，还是让我们先从泥水匠做起吧，先来了解以下泥沙和工具吧。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;记得前面说的吗？一次只做一件事，并且做好它。现在我们就抛开所有杂念和对高性能socket服务器的各种猜想，先做一个最基本的socket服务器端程序。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;等我们逐步熟悉了泥沙和工具，我们再杀回来逐个干掉高深莫测的服务器架构设计，这就是我们的行动计划。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;这里先贴出本章的示例代码，我再根据这个代码跟大家逐步讲解socket编程的关键知识点：&lt;/p&gt;&#xD;
&#xD;
&lt;b&gt;socketd.c&lt;/b&gt;&#xD;
&#xD;
&lt;pre &gt;#include &amp;lt;stdio.h&amp;gt;&#xD;
#include &amp;lt;stdlib.h&amp;gt;&#xD;
#include &amp;lt;string.h&amp;gt;&#xD;
#include &amp;lt;sys/socket.h&amp;gt;&#xD;
#include &amp;lt;netinet/in.h&amp;gt;&#xD;
&#xD;
&#xD;
#define SD_PORT       10086&#xD;
#define SD_BACK_LOG   10&#xD;
&#xD;
&#xD;
int sd_listener_fd;&#xD;
&#xD;
&#xD;
void &#xD;
sd_init ()&#xD;
{&#xD;
	int reuse = 1;&#xD;
	&#xD;
	struct sockaddr_in addr;&#xD;
&#xD;
	if ((sd_listener_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1)&#xD;
	{&#xD;
		perror("Create listener socket failed");&#xD;
		exit(-1);&#xD;
	}&#xD;
&#xD;
	if (setsockopt(sd_listener_fd, SOL_SOCKET, SO_REUSEADDR, &amp;amp;reuse, sizeof(reuse)) == -1)&#xD;
	{&#xD;
		perror("Setup listener socket failed");&#xD;
		exit(-1);&#xD;
	}&#xD;
&#xD;
	bzero(&amp;amp;(addr.sin_zero), 8);&#xD;
&#xD;
	addr.sin_family      = AF_INET;&#xD;
	addr.sin_port        = htons(SD_PORT);&#xD;
	addr.sin_addr.s_addr = htonl(INADDR_ANY);&#xD;
&#xD;
	if (bind(sd_listener_fd, (struct sockaddr *)&amp;amp;addr, sizeof(addr)) == -1)&#xD;
	{&#xD;
		perror("Bind listener socket address failed");&#xD;
		exit(-1);&#xD;
	}&#xD;
&#xD;
	if (listen(sd_listener_fd, SD_BACK_LOG) == -1)&#xD;
	{&#xD;
		perror("Listen port failed");&#xD;
		exit(-1);&#xD;
	}&#xD;
}&#xD;
&#xD;
&#xD;
void   &#xD;
sd_loop ()&#xD;
{&#xD;
	char buf[1024];&#xD;
&#xD;
	int ret = 0;&#xD;
&#xD;
	int client_fd;&#xD;
&#xD;
	int client_addr_len;&#xD;
&#xD;
	struct sockaddr_in client_addr;&#xD;
&#xD;
	printf("Waiting connect on port %d\n", SD_PORT);&#xD;
&#xD;
	client_addr_len = sizeof(client_addr);&#xD;
&#xD;
	client_fd = accept(sd_listener_fd, (struct sockaddr *)&amp;amp;client_addr, &amp;amp;client_addr_len);&#xD;
&#xD;
	printf("Client connected\n");&#xD;
&#xD;
	for (;;)&#xD;
	{&#xD;
		if ((ret = read(client_fd, buf, 1024)) == 0)&#xD;
		{&#xD;
			close(client_fd);&#xD;
&#xD;
			printf("Client closed\n");&#xD;
			&#xD;
			break;&#xD;
		}&#xD;
		else&#xD;
		{&#xD;
			write(client_fd, buf, ret);&#xD;
		}&#xD;
	}&#xD;
}&#xD;
&#xD;
&#xD;
void&#xD;
sd_down ()&#xD;
{&#xD;
	close(sd_listener_fd);&#xD;
&#xD;
	printf("Server shutdown\n");&#xD;
}&#xD;
&#xD;
&#xD;
int&#xD;
main (int argc, char *argv[])&#xD;
{&#xD;
	sd_init();&#xD;
&#xD;
	sd_loop();&#xD;
&#xD;
	sd_down();&#xD;
&#xD;
	return 1;&#xD;
}&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;上面的代码是一个简单的echo服务器，它一次只处理一个连接，在客户端退出时服务器端也跟着关闭&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;你可以复制上面的代码保存为socketd.c，然后打开终端，切换到文件所在目录，输入：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;cc socketd.c -o socketd&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;不出意外的话，我们的最原始版socket服务器就编译好了。然后输入：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;./socketd&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;服务器就启动了。另外再开一个终端，输入：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;telnet localhost 10086&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;这时候telnet应该能连上socket服务器，你可以在telnet里面输入一些文字，然后回车，服务器应该会将你发送的内容原样返回。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;当你玩腻了，就在telnet界面按住Ctrl键，然后输入“]“，回车。这时候telent会切换到命令界面，输入q，回车，退出telent。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;telnet退出后，相当于客户端断开了连接，按代码逻辑上面的示例程序应该会跟着退出。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;顺便说一下，像上面示例这样接受并原样返回客户端请求内容的socket服务器叫做echo服务器，名字谁取的我不知道，反正大家都这么叫。 :)&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;下面我们来分析一下这段代码，我们从大结构分析入手，再深入到每个函数的介绍。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;阅读这段代码要从main函数开始，main函数逐步调用了三个sd_开头的函数，sd_init() 初始化服务器 -&amp;gt; sd_loop() 服务器循环处理请求 -&amp;gt; sd_down() 服务器关闭。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;sd_init 函数中的代码是典型的服务器端socket初始化过程，socket() 创建套接字 -&amp;gt; bind() 绑定地址 -&amp;gt; listener() 开始监听端口。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;sd_loop 函数中的代码则是一个简单的接收客户端请求并回发数据的示例，accept() 接受新连接 -&amp;gt; read() 接受请求数据 -&amp;gt; write()发送数据。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;sd_down 函数中的代码演示了如何关闭套接字，close() 就这么简单。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;下面我以函数注释的方式一一注释上面代码涉及到的系统函数，这样可以不需要附加太多废话的描述并条理清晰。&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;/*&#xD;
 * 功能：创建socket&#xD;
 * 返回：成功时，返回socket文件描述符；失败时，返回-1，可以通过errno获取错误类型&#xD;
 * 参数：&#xD;
 *      domain   － 地址种类，较常用的有AF_INET和AF_INET6，分别对应IPv4协议和IPv6协议&#xD;
 *      type     － 套接字类型，较常用的有SOCK_STREAM和SOCK_DGRAM，分别对应TCP/IP协议和UDP协议&#xD;
 *      protocol － 协议，一些特殊的套接字类型下可能会用到，但是做TCP或者UDP编程时不会用到此参数，所以我们通常传递0&#xD;
 */&#xD;
int socket(int domain, int type, int protocol);&#xD;
&#xD;
/*&#xD;
 * 功能：将socket绑定到指定的地址&#xD;
 * 返回：成功时，返回0；失败时，返回-1，可以通过errno获取错误类型&#xD;
 * 参数：&#xD;
 *      sockfd  － 套接字文件描述符，就是socket函数成功时返回的那个&#xD;
 *      addr    － 所要绑定到的地址，其中包含地址种类、协议族IP地址和端口号，IP地址和端口号在赋值时分别需要用htonl和htons函数进行大小端转换&#xD;
 *      addrlen － 地址长度，因为我们通常用的是sockaddr_in类型地址，所以这个参数就是sizeof(struct sockaddr_in)&#xD;
 */&#xD;
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);&#xD;
&#xD;
/*&#xD;
 * 功能：监听套接字上的连接&#xD;
 * 返回：成功时，返回0；失败时，返回-1，可以通过errno获取错误类型&#xD;
 * 参数：&#xD;
 *      sockfd  － 套接字文件描述符，就是socket函数成功时返回的那个&#xD;
 *      backlog － 等待连接完成的队列大小，当服务器繁忙时可能没办法一次响应所有连接请求，&#xD;
 *                 这时候连接请求会被放入队列等待处理，队列满的时候，客户端才真正无法连接&#xD;
 */&#xD;
int listen(int sockfd, int backlog);&#xD;
&#xD;
/*&#xD;
 * 功能：接受一个新的连接&#xD;
 * 返回：成功时，返回新连接的socket文件描述符；失败时，返回-1，可以通过errno获取错误类型&#xD;
 * 参数：&#xD;
 *      sockfd  － 监听的套接字文件描述符，就是listen函数用的那个&#xD;
 *      addr    － 新连接的地址信息（指针返回）&#xD;
 *      addrlen － 新连接的地址长度（指针返回）&#xD;
 */&#xD;
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;上面的函数介绍并不是最详细也不是最权威的，我建议大家不妨在命令行下面用man命令查阅各个函数的详细文档，用法是man [函数名]。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;在bind之前我们创建socket地址的时候，用到了htonl和htons函数：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;/*&#xD;
 * 功能：Host to Network (long)，将主机上的长整型数据进行大小端转换，以适应网络规范&#xD;
 */&#xD;
uint32_t htonl(uint32_t hostlong);&#xD;
&#xD;
/*&#xD;
 * 功能：Host to Network (short)，将主机上的短整型数据进行大小端转换，以适应网络规范&#xD;
 */&#xD;
uint16_t htons(uint16_t hostshort);&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;分别与这两个函数对应但没有出现在代码中的还有：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;/*&#xD;
 * 功能：Network to Host (long)，将网络规范格式的长整型数据进行大小端转换，以适应主机&#xD;
 */&#xD;
uint32_t ntohl(uint32_t netlong);&#xD;
&#xD;
/*&#xD;
 * 功能：Network to Host (short)，将网络规范格式的短整型数据进行大小端转换，以适应主机&#xD;
 */&#xD;
uint16_t ntohs(uint16_t netshort);&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;为什么数据需要进行大小端的转换呢？大小端转换又是什么呢？这就要涉及到计算机组织原理的知识了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;简单说来，我们的数据在计算机中是以二进制字节数据表示的，二进制字节数据保存在内存中时就涉及到一个实现问题，比如十进制数1，对应的字节是应该表示成1000 0000还是0000 0001呢？到底是高位在前还是低位在前对计算机来说是一个实现的问题，而我们平时阅读和书写的习惯是高位在前，所以成为大端模式，即0000 0001格式，而反之则称为小端格式。在不同厂商的CPU上，数据的存储格式是不一样的，比如IBM和SUN用的是大端格式，Intel用的则是小端格式。而当不同数据格式的主机，在网络间进行数据传输时，这个实现问题就演变成了兼容问题，而RFC规范中规定了网络通讯时的字节格式是大端格式，所以系统就提供了相应的转换函数提高程序兼容性。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;对于大小端格式的详细信息，大家如果有兴趣了解可以在网上搜索，下面是一个关于大小端的有趣故事：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;端模式（Endian）的这个词出自Jonathan Swift书写的《格列佛游记》。&#xD;
&#xD;
这本书根据将鸡蛋敲开的方法不同将所有的人分为两类，从圆头开始将鸡蛋敲开的人被归为Big Endian，从尖头开始将鸡蛋敲开的人被归为Littile Endian。&#xD;
&#xD;
小人国的内战就源于吃鸡蛋时是究竟从大头（Big-Endian）敲开还是从小头（Little-Endian）敲开。在计算机业Big Endian和Little Endian也几乎引起一场战争。&#xD;
&#xD;
在计算机业界，Endian表示数据在存储器中的存放顺序。&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;示例代码中，在创建监听的套接字文件描述符后，还执行了这样一句代码：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;setsockopt(sd_listener_fd, SOL_SOCKET, SO_REUSEADDR, &amp;reuse, sizeof(reuse));&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;setsockopt函数是用来设置socket的参数的，这些参数决定socket的一些表现，例如后面的章节中我们使用这个函数设置socket为无阻塞模式。而上面这行代码则是用来运行socket重用端口的，所以它必须执行在bind之前。这么设置为了防止程序在意外退出后，系统没有释放端口而导致程序无法再使用原来端口&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;本章的示例代码只需要走马观花看一遍，熟悉一下socket编程的大概流程，以后自己亲手实验的时候不记得怎么做了再回来查阅就可以。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;没必要死记硬背这些API，只需要知到这些函数存在，它们大概干什么用的。盖楼嘛，你背各种泥沙学名和化学成分有什么用？只要懂得辨别，需要时能从手册找到查阅到具体信息就可以了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;&lt;strong&gt;本章总结&lt;/strong&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;本章演示了一个简单的echo服务器，它只支持一次处理一个连接，并且在连接退出时，服务器端也跟着关闭。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;通过这个简单的例子，我们学习了基本的socket初始化过程和连接的响应方式。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;当然这些知识对于我们的远大目标“高性能socket服务器"来说是远远不够的，但是这些是基础的基础，至少我们已经迈开了第一步。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;下一章我将向大家介绍如何利用IO重用，在一个进程中同时处理多个连接的请求，敬请期待。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/mingda/aggbug/1875465.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/11/12/hight_performance_socket_server_01.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/11/12/php_file_system_based_cache_class.html</id><title type="text">基于/dev/shm的PHP缓存类</title><summary type="text">在《Erlang和PHP间的Socket通讯》中我提到做了基于/dev/shm的缓存实现的性能测试，这里分享一下测试中我封装的一个基于文件系统的缓存类，在Linux上只需要把根目录指向/dev/shm，就可以变成一个基于内存的缓存了，在Windows上可以用普通文件系统做测试。</summary><published>2010-11-12T02:44:00Z</published><updated>2010-11-12T02:44:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/11/12/php_file_system_based_cache_class.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/11/12/php_file_system_based_cache_class.html"/><content type="html">&lt;p&gt;原文地址：&lt;a href="http://unbe.cn/php_file_system_based_cache_class/"&gt;http://unbe.cn/php_file_system_based_cache_class/&lt;/a&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;在&lt;a href="http://unbe.cn/socket_connection_between_erlang_and_php/"&gt;《Erlang和PHP间的Socket通讯》&lt;/a&gt;中我提到做了基于/dev/shm的缓存实现的性能测试，这里分享一下测试中我封装的一个基于文件系统的缓存类，在Linux上只需要把根目录指向/dev/shm，就可以变成一个基于内存的缓存了，在Windows上可以用普通文件系统做测试。&lt;/p&gt;&#xD;
&lt;!--more--&gt;&#xD;
&lt;p&gt;需要先提醒大家一点，这个缓存类只是一个原型。只是提出基于/dev/shm的缓存实现的可能性，并不是一个完整的可以在生产环境使用的缓存类。它还有很多有待完善和测试的地方，例如：数据的失效时间功能；并发情况下同一个key的数据操作；批量移除和添加并发情况下发生；等等。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;使用示例：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;$data_1 = array(&#xD;
  'u_id' =&gt; 1,&#xD;
  'name' =&gt; 'DaDa'&#xD;
);&#xD;
&#xD;
$data_2 = array(&#xD;
  'u_id' =&gt; 2,&#xD;
  'name' =&gt; 'WaWa'&#xD;
);&#xD;
&#xD;
$cache = new file_cache("/dev/shm");&#xD;
&#xD;
$cache-&gt;set("user/1/data", $data_1);  //保存数据&#xD;
$cache-&gt;set("user/2/data", $data_2);  //保存数据&#xD;
&#xD;
$result = $cache-&gt;get("user/1/data"); //获取数据&#xD;
&#xD;
$cache-&gt;remove("user/1/data"); //删除数据&#xD;
&#xD;
$cache-&gt;remove_by_search("user", $data_1);  //删除user节点下所有数据&#xD;
&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;由于/dev/shm是把内存模拟成文件系统，所以很容易就实现了层级式的缓存管理。这对合理利用内存空间是很有帮助的。比如一些针对用户的缓存，可以通过层级式的存储，在用户退出系统时全部移除。再比如一些同表的不同业务逻辑视图数据的缓存，在表更新后，也可以批量的移除。&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;&amp;lt;?php&#xD;
class file_cache&#xD;
{&#xD;
    private $root_dir;&#xD;
    &#xD;
    public function __construct ($root_dir)&#xD;
    {&#xD;
        $this-&amp;gt;root_dir = $root_dir;&#xD;
        &#xD;
        if (FALSE == file_exists($this-&amp;gt;root_dir))&#xD;
        {&#xD;
            mkdir($this-&amp;gt;root_dir, 0700, true);&#xD;
        }&#xD;
    }&#xD;
    &#xD;
    public function set ($key, $value)&#xD;
    {&#xD;
        $key = $this-&amp;gt;escape_key($key);&#xD;
        &#xD;
        $file_name = $this-&amp;gt;root_dir . '/' . $key;&#xD;
        &#xD;
        $dir = dirname($file_name);&#xD;
        &#xD;
        if (FALSE == file_exists($dir))&#xD;
        {&#xD;
            mkdir($dir, 0700, true);&#xD;
        }&#xD;
        &#xD;
        file_put_contents($file_name, serialize($value), LOCK_EX);&#xD;
    }&#xD;
    &#xD;
    public function get ($key)&#xD;
    {&#xD;
        $key = $this-&amp;gt;escape_key($key);&#xD;
        &#xD;
        $file_name = $this-&amp;gt;root_dir . '/' . $key;&#xD;
        &#xD;
        if (file_exists($file_name))&#xD;
        {&#xD;
            return unserialize(file_get_contents($file_name));&#xD;
        }&#xD;
        &#xD;
        return null;&#xD;
    }&#xD;
    &#xD;
    public function remove ($key)&#xD;
    {&#xD;
        $key = $this-&amp;gt;escape_key($key);&#xD;
        &#xD;
        $file = $this-&amp;gt;root_dir . '/' . $key;&#xD;
        &#xD;
        if (file_exists($file))&#xD;
        {&#xD;
            unlink($file);&#xD;
        }&#xD;
    }&#xD;
    &#xD;
    public function remove_by_search ($key)&#xD;
    {&#xD;
        $key = $this-&amp;gt;escape_key($key);&#xD;
        &#xD;
        $dir = $this-&amp;gt;root_dir . '/' . $key;&#xD;
        &#xD;
        if (strrpos($key, '/') &amp;lt; 0)&#xD;
            $key .= '/';&#xD;
        &#xD;
        if (file_exists($dir))&#xD;
        {&#xD;
            $this-&amp;gt;removeDir($dir);&#xD;
        }&#xD;
    }&#xD;
    &#xD;
    private function escape_key ($key)&#xD;
    {&#xD;
        return str_replace('..', '', $key);&#xD;
    }&#xD;
    &#xD;
    function removeDir($dirName)&#xD;
    {&#xD;
        $result = false;&#xD;
        &#xD;
        $handle = opendir($dirName);&#xD;
        &#xD;
        while(($file = readdir($handle)) !== false)&#xD;
        {&#xD;
            if($file != '.' &amp;amp;&amp;amp; $file != '..')&#xD;
            {&#xD;
                $dir = $dirName . DIRECTORY_SEPARATOR . $file;&#xD;
            &#xD;
                is_dir($dir) ? $this-&amp;gt;removeDir($dir) : unlink($dir);&#xD;
            }&#xD;
        }&#xD;
        &#xD;
        closedir($handle);&#xD;
        &#xD;
        rmdir($dirName) ? true : false;&#xD;
        &#xD;
        return $result;&#xD;
    }&#xD;
}&#xD;
?&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&lt;img src="http://www.cnblogs.com/mingda/aggbug/1875464.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/11/12/php_file_system_based_cache_class.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/11/12/erlang_php_socket_test_01.html</id><title type="text">Erlang和PHP间的Socket通讯－01</title><summary type="text">前段时间，在群里和发哥聊起memcached和APC，渐渐的聊到了/dev/shm，发哥说他用/dev/shm做缓存很好用。这次讨论触发了我对memcached、APC和dev/shm数据读写性能的测试。测试中我想到了Erlang内置的ets和传说中的并发性能，如果用Erlang + ets做一个类似memcached这样的key-value的缓存服务器，性能会比memcached好吗？于是我动手做了试验，用Erlang编写了一个支持并发连接的Socket服务器，写了一个PHP的客户端。测试的结果我先按下不表，放到文章结尾再附带说明，以免冲淡了本篇文章的主题。这次试验最值得分享的经验是Erlang和PHP间的Socket通讯方式。</summary><published>2010-11-12T02:40:00Z</published><updated>2010-11-12T02:40:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/11/12/erlang_php_socket_test_01.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/11/12/erlang_php_socket_test_01.html"/><content type="html">&lt;p&gt;原文链接：&lt;a href="http://unbe.cn/erlang_php_socket_test_01/"&gt;http://unbe.cn/erlang_php_socket_test_01/&lt;/a&gt;&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;前段时间，在群里和发哥聊起memcached和APC，渐渐的聊到了/dev/shm，发哥说他用/dev/shm做缓存很好用。这次讨论触发了我对memcached、APC和dev/shm数据读写性能的测试。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;测试中我想到了Erlang内置的ets和传说中的并发性能，如果用Erlang + ets做一个类似memcached这样的key-value的缓存服务器，性能会比memcached好吗？于是我动手做了试验，用Erlang编写了一个支持并发连接的Socket服务器，写了一个PHP的客户端。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;测试的结果我先按下不表，放到文章结尾再附带说明，以免冲淡了本篇文章的主题。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;这次试验最值得分享的经验是Erlang和PHP间的Socket通讯方式。&lt;/p&gt;&#xD;
&#xD;
&lt;!--more--&gt;&#xD;
&#xD;
&lt;p&gt;下面的Erlang代码和PHP代码都没有实际涉及到缓存功能，愿因文章最后会有说明，这里只关注通讯机制。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;Erlang端的服务器代码，有一个很好听的名字叫mycached，启动服务的方式是：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;mycached:start(10086, 10).&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;mycached:start的第一个参数是端口号，第二个参数是工作进程数。服务器启动后，会有n个工作进程同时等待accpet，我不知道这里会不会有“惊群”问题，我假定Erlang内部机制会妥善处理多个进程同时等待一个套接字的情况，之所以这么假定，是因为这段服务器代码的基础来自于Erlang官方文档，上面的示例代码就是这样一个模式。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;当其中一个进程接收到来自客户端的连接后，就会陷入loop函数，处理客户端请求，直到客户端断开连接。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;事实上，也可以用spawn来启动一个新进程执行loop函数，响应客户端请求，而工作进程继续回到accept的状态。只变了一行代码，服务器的工作模式立马就发生改变，Erlang真的很神奇。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;我还编写了两个用于测试mycached自身的函数。一个叫test，用于执行单次请求并输出返回结果，主要起功能测试的作用。一个叫banch，用于执行批量请求，并输出执行时间，主要起压力测试的作用。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;test函数的调用示例：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;mycached:test("localhost", 10086, 1, "Hello").&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;test函数的第一个参数是服务器地址，第二个参数是服务器端口号，第三个参数是请求类型，第四个参数是请求参数。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;banch函数的调用示例：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;mycached:banch("localhost", 10086, 1, "Hello", 10, 1000).&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;banch函数的前四个参数都和test函数一致，增加的两个参数，一个是连接次数，一个是请求次数。上面的示例代码将会执行10次连接，每次连接会分别发起1000次的请求，总共1w次请求。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;以下是Erlang端的完整代码：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;-module(mycached).&#xD;
-export([start/2, server/1, loop/1, test/4, for/3, banch/6, banch_call/6]).&#xD;
&#xD;
-define(CMD_GET, 1).&#xD;
-define(CMD_SET, 2).&#xD;
-define(CMD_DEL, 3).&#xD;
&#xD;
&#xD;
start(LPort, Num) -&amp;gt;&#xD;
    case gen_tcp:listen(LPort, [binary, {active, false}, {packet, 2}]) of&#xD;
    &#xD;
        {ok, LSock} -&amp;gt;&#xD;
            start_servers(LSock, Num),&#xD;
            &#xD;
            {ok, Port} = inet:port(LSock),&#xD;
            &#xD;
            Port;&#xD;
            &#xD;
        {error, Reason} -&amp;gt;&#xD;
            {error, Reason}&#xD;
    end.&#xD;
&#xD;
&#xD;
start_servers(_, 0) -&amp;gt;&#xD;
    ok;&#xD;
    &#xD;
start_servers(LSock, Num) -&amp;gt;&#xD;
    spawn(?MODULE, server, [LSock]),&#xD;
    &#xD;
    start_servers(LSock, Num - 1).&#xD;
&#xD;
&#xD;
server(LSock) -&amp;gt;&#xD;
    case gen_tcp:accept(LSock) of&#xD;
    &#xD;
        {ok, CSock} -&amp;gt;&#xD;
            loop(CSock),&#xD;
            server(LSock);&#xD;
            &#xD;
        Other -&amp;gt;&#xD;
            io:format("accept returned ~w - goodbye!~n", [Other]),&#xD;
            ok&#xD;
    end.&#xD;
&#xD;
&#xD;
loop(CSock) -&amp;gt;&#xD;
    inet:setopts(CSock, [{active, once}]),&#xD;
    &#xD;
    receive&#xD;
    &#xD;
        {tcp, CSock, Request} -&amp;gt;&#xD;
            Response = process(Request),&#xD;
            &#xD;
            Response_Bin = list_to_binary(Response),&#xD;
            &#xD;
            gen_tcp:send(CSock, Response_Bin),&#xD;
            &#xD;
            loop(CSock);&#xD;
            &#xD;
        {tcp_closed, CSock} -&amp;gt;&#xD;
            io:format("socket ~w closed [~w]~n", [CSock, self()]),&#xD;
            ok&#xD;
    end.&#xD;
&#xD;
&#xD;
process(Request) -&amp;gt;&#xD;
    try&#xD;
        {&amp;lt;&amp;lt;Type&amp;gt;&amp;gt;, Params} = split_binary(Request, 1),&#xD;
        &#xD;
        case Type of&#xD;
            ?CMD_GET -&amp;gt;&#xD;
                "Command: GET";&#xD;
            ?CMD_SET -&amp;gt;&#xD;
                "Command: SET";&#xD;
            ?CMD_DEL -&amp;gt;&#xD;
                "Command: DEL";&#xD;
            _ -&amp;gt;&#xD;
                "Unknow Command"&#xD;
        end&#xD;
    catch&#xD;
        _:E -&amp;gt; io:format("process failed: ~w [~w]~n", [E, self()]),&#xD;
        "Server Error"&#xD;
    end.&#xD;
&#xD;
&#xD;
test(Host, Port, Command, Params) -&amp;gt;&#xD;
    test_call(Host, Port, Command, Params, 1).&#xD;
&#xD;
&#xD;
banch(Host, Port, Command, Params, Times, RTimes) -&amp;gt;&#xD;
    {M, _} = timer:tc(?MODULE, banch_call, [Host, Port, Command, Params, Times, RTimes]),&#xD;
    &#xD;
    io:format("Time: ~p micro seconds~n", [M]),&#xD;
    &#xD;
    ok.&#xD;
&#xD;
&#xD;
banch_call(Host, Port, Command, Params, Times, RTimes) -&amp;gt;&#xD;
    for (0, Times,&#xD;
        fun() -&amp;gt;&#xD;
            test_call(Host, Port, Command, Params, RTimes)&#xD;
        end&#xD;
    ),&#xD;
    ok.&#xD;
&#xD;
&#xD;
test_call(Host, Port, Command, Params, Times) -&amp;gt;&#xD;
    {ok, Sock} = gen_tcp:connect(Host, Port, [binary, {active, false}, {packet, 2}]),&#xD;
    &#xD;
    Request = [Command, Params],&#xD;
    &#xD;
    Request_Bin = list_to_binary(Request),&#xD;
    &#xD;
    case Times of&#xD;
        1 -&amp;gt;&#xD;
            {ok, Bin} = test_send(Sock, Request_Bin),&#xD;
    		&#xD;
    		ok = gen_tcp:close(Sock),&#xD;
			&#xD;
    		Bin;&#xD;
&#xD;
        _ -&amp;gt;&#xD;
            for (0, Times,&#xD;
                fun() -&amp;gt;&#xD;
                    {ok, _} = test_send(Sock, Request_Bin)&#xD;
                end&#xD;
            ),&#xD;
    		&#xD;
    		ok = gen_tcp:close(Sock),&#xD;
			&#xD;
			ok&#xD;
    end.&#xD;
&#xD;
&#xD;
test_send(Sock, Request_Bin) -&amp;gt;&#xD;
    ok = gen_tcp:send(Sock, Request_Bin),&#xD;
    &#xD;
    gen_tcp:recv(Sock, 0).&#xD;
&#xD;
&#xD;
for (To, To, _) -&amp;gt;&#xD;
    ok;&#xD;
&#xD;
for (From, To, Callback) -&amp;gt;&#xD;
    Callback(),&#xD;
    for (From + 1, To, Callback).&#xD;
&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;PHP端最大的难点在于请求的封包和解包，事实上这部分都集中在php内置的pack和unpack函数的使用上。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;mycached的通讯是基于Erlang的{packet, 2}模式的，Erlang会自动将数据包的前两个字节当作请求的长度，在Erlang端就不需要自己进行复杂的封包和解包工作了，只需要把精力都放在业务数据的解析上。而PHP端就没这么幸运了，你必须自己将在请求的头部加上两个字节的包大小信息，而接受到服务器响应时，必须先从头部读取两个字节的包大小信息，再解包。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;完整的PHP客户端代码：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;&amp;lt;?php&#xD;
&#xD;
class mycached&#xD;
{&#xD;
    private $host;&#xD;
    private $port;&#xD;
    private $sock;&#xD;
    &#xD;
    function __construct ($host, $port)&#xD;
    {&#xD;
        $this-&amp;gt;host = $host;&#xD;
        $this-&amp;gt;port = $port;&#xD;
        &#xD;
        $this-&amp;gt;sock = @socket_create(AF_INET, SOCK_STREAM, getprotobyname('tcp'));&#xD;
        &#xD;
        if ($this-&amp;gt;sock)&#xD;
        {&#xD;
            socket_connect($this-&amp;gt;sock, $this-&amp;gt;host, $this-&amp;gt;port);&#xD;
        }&#xD;
    }&#xD;
    &#xD;
    public function set ($key, $value)&#xD;
    {&#xD;
    }&#xD;
    &#xD;
    public function get ($key)&#xD;
    {&#xD;
        $msg = $this-&amp;gt;pack_data(1, $key);&#xD;
        &#xD;
        $sent = @socket_write($this-&amp;gt;sock, $msg, strlen($msg));&#xD;
        &#xD;
        if ($sent === FALSE)&#xD;
        {&#xD;
            return null;&#xD;
        }&#xD;
        &#xD;
        $buff = $this-&amp;gt;socket_read_len($this-&amp;gt;sock, 2, PHP_BINARY_READ);&#xD;
        &#xD;
        $head = unpack("H*", $buff);&#xD;
        &#xD;
        $len = hexdec($head[1]);&#xD;
        &#xD;
        $res = $this-&amp;gt;socket_read_len($this-&amp;gt;sock, $len, PHP_BINARY_READ);&#xD;
        &#xD;
        return $res;&#xD;
    }&#xD;
    &#xD;
    public function remove ($key)&#xD;
    {&#xD;
    }&#xD;
    &#xD;
    public function remove_by_search ($key)&#xD;
    {  &#xD;
    }&#xD;
    &#xD;
    private function pack_data ($type, $data)&#xD;
    {&#xD;
        $cmd = pack("C*", $type);&#xD;
        &#xD;
        $cmd_len = strlen($cmd);&#xD;
        &#xD;
        $body = pack("A*", $data);&#xD;
    &#xD;
        $body_len = strlen($body);&#xD;
        &#xD;
        $len = $cmd_len + $body_len;&#xD;
        &#xD;
        $head = pack("H*", $this-&amp;gt;to_hex_str($len));&#xD;
        &#xD;
        return $head.$cmd.$body;&#xD;
    }&#xD;
    &#xD;
    private function to_hex_str ($num)&#xD;
    {&#xD;
        $str = dechex($num);&#xD;
        &#xD;
        $str = str_repeat('0', 4 - strlen($str)).$str;&#xD;
        &#xD;
        return $str;   &#xD;
    } &#xD;
    &#xD;
    private function socket_read_len ($socket, $len, $type)&#xD;
    {&#xD;
        $offset = 0;&#xD;
        $socketData = '';&#xD;
        &#xD;
        while ($offset &amp;lt; $len)&#xD;
        {&#xD;
            if (($data = @socket_read ($socket, $len - $offset, $type)) === false)&#xD;
            {&#xD;
                return false;&#xD;
            }&#xD;
            &#xD;
            $dataLen = strlen ($data);&#xD;
            &#xD;
            $offset += $dataLen;&#xD;
            &#xD;
            $socketData .= $data;&#xD;
            &#xD;
            if ($dataLen == 0) { break; }&#xD;
        }&#xD;
        &#xD;
        return $socketData;&#xD;
    }&#xD;
}&#xD;
&#xD;
?&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;以下是PHP端的测试代码：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;&amp;lt;?php&#xD;
&#xD;
$cache = new mycached('localhost', 10086);&#xD;
&#xD;
$stime = microtime(true);&#xD;
&#xD;
for ($i = 0; $i &amp;lt; 10000; $i ++)&#xD;
{&#xD;
    $res = $cache-&amp;gt;get("hello");&#xD;
}&#xD;
&#xD;
$etime = microtime(true);&#xD;
&#xD;
echo "Time: " . ($etime - $stime) . "\n";&#xD;
&#xD;
?&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;好了，通讯相关代码都介绍完毕了，这里可以说说测试结果了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;测试结果是APC读写最快比memcached快差不多5倍左右，但是APC不能在进程间共享数据，不是我要找的东西。memcached和PHP读写/dev/shm差不多，memcached略胜一点点，但/dev/shm有个很大的好处就是可以很容易实现缓存的层级管理。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;在我刚能让Erlang和PHP建立通讯后，我决定先测试一下通讯性能，如果还没有加入缓存操作逻辑的单纯通讯，性能都无法让人接受的话，那也就没必要再完整实现整个缓存服务器了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;试验结果真的很出人意料，通讯性能比之前想象的相差得太远，1万次请求响应时间在2s ~ 1s波动，而memcached的1万次写才0.3s，读还更快一点。这就是为什么上面分享的代码实际上没有涉及任何缓存操作的原因。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;也许是我之前对Erlang性能有过高的估计，也许是我的代码优化得不够好。但不管怎么样，Erlang的开发效率是值得肯定的，实验过程中整个Socket服务器一直流畅的演化着。从无到有，从有到支持并发，从支持并发到支持测试，从支持测试到支持压力测试。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;我想大家也许会有疑问，是不是PHP端效率太低，让我得出了错误的结论？这一点，大家可以放心，我在PHP和Erlang两端都提供了测试代码，大家可以自己都测试一遍。事实上，PHP端的性能损失反而是出乎我意料的小，呵呵。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/mingda/aggbug/1875440.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/11/12/erlang_php_socket_test_01.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/06/13/probability_event_in_code.html</id><title type="text">概率事件的实现方式</title><summary type="text">前几天修复游戏装备强化功能的BUG时，发现原先负责这个功能的同事用了很糟糕的方式来实现不同等级装备的强化成功率，于是我动手重写掉了他的代码。</summary><published>2010-06-12T17:42:00Z</published><updated>2010-06-12T17:42:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/06/13/probability_event_in_code.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/06/13/probability_event_in_code.html"/><content type="html">&lt;p&gt;前几天修复游戏装备强化功能的BUG时，发现原先负责这个功能的同事用了很糟糕的方式来实现不同等级装备的强化成功率，于是我动手重写掉了他的代码。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;他的实现方式大概分一下步骤：&lt;br/&gt;&#xD;
1. 获取特定等级装备的强化成功率和失败率（一个百分数，比如成功率20%，等于数值20）&lt;br/&gt;&#xD;
2. 创建一个集合A，用于3、4、5步骤的操作。&#xD;
3. 从0开始循环至（成功率 * 10），每次循环往A中插入一个字符串'success'（假设成功率20%，这个集合便有200个'success'字符串）&lt;br/&gt;&#xD;
4. 从0开始循环至（失败率 * 10），每次循环往A中插入一个字符串'failed'（假设失败率80%，这个集合便有800个'failed'字符串）&lt;br/&gt;&#xD;
5. 取0 ~ 999之间的随机数i，取A[i]，判断是'success'还是'failed'，然后执行相应操作&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;非常繁琐又非常低效率的算法，即浪费CPU时间又浪费内存空间，却没得到任何好处，还让代码冗长而不易阅读。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;实际上，重构上面的逻辑，我只用了一行代码：&lt;br/&gt;&#xD;
取0 ~ 9999之间的随机数i，i &lt;= 成功率 * 100 则强化成功，否则强化失败。&#xD;
&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;为了增加随机数的随机性，我将随机数区间增大了10倍。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;以下是试验代码，通过100组，每组100次生成随机数，再取平均值，可以证明我上面描述的算法虽然只需要一行代码，但却是很可靠的。&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;&amp;lt;?php&#xD;
$rate = 20;&#xD;
&#xD;
$t1 = 0;&#xD;
&#xD;
for ($j = 0; $j &amp;lt; 100; $j ++)&#xD;
{&#xD;
    $t = 0;&#xD;
    &#xD;
    for ($i = 0; $i &amp;lt; 100; $i++)&#xD;
    {&#xD;
        if(rand(0, 9999) &amp;lt;= $rate * 100)&#xD;
        {&#xD;
            $t += 1;&#xD;
        }&#xD;
    }&#xD;
    &#xD;
    $t1 += $t;&#xD;
}&#xD;
&#xD;
echo $t1 / 100;&#xD;
&#xD;
?&amp;gt;&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;本来觉得这是很基本的东西，只能说是原来那位同事水平不够或者责任心不够，才会写出糟糕的代码，所以也就不想写这篇文章。&lt;br/&gt;&#xD;
但是考虑到没人分享，就还是有可能有一些经验比较欠缺的程序员朋友在工作中会采用同样恶心或者更恶心的实现办法，于是我还是写了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;其实这件事情还有很多值得深思的地方，不单单是程序算法这么简单。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;我经常说，一个事情的发生，不会是单一作用力产生的效果，而是多个力的合力产生的效果。&lt;br/&gt;&#xD;
在这个事情上不能只看到开发者能力的不足，也要意识到项目管理者在监督环节的疏忽，以及开发流程上是否应该引入设计讨论会的问题。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;此处略去数百字，发到微博去了，本文到此结束。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/mingda/aggbug/1757473.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/06/13/probability_event_in_code.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/06/07/performance_test_of_php_object_serialization.html</id><title type="text">PHP中json_encode、json_decode与serialize、unserialize的性能测试</title><summary type="text">今天偶然在想，如果用PHP写一个类似BDB的基于文件的Key-Value小型数据库用于存储非结构化的记录型数据，不知道效率会如何？于是便联想到PHP中的对象怎么样序列化存储性价比最高呢？接着想到了之前同事推荐的JSON编码和解码函数。据他所说，json_encode和json_decode比内置的serialize和deserialize函数要高效。于是我决定动手实验，证实一下同事所说的情况是否属实。</summary><published>2010-06-07T05:46:00Z</published><updated>2010-06-07T05:46:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/06/07/performance_test_of_php_object_serialization.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/06/07/performance_test_of_php_object_serialization.html"/><content type="html">&lt;p&gt;今天偶然在想，如果用PHP写一个类似BDB的基于文件的Key-Value小型数据库用于存储非结构化的记录型数据，不知道效率会如何？&lt;br/&gt;&#xD;
于是便联想到PHP中的对象怎么样序列化存储性价比最高呢？接着想到了之前同事推荐的JSON编码和解码函数。&lt;br/&gt;&#xD;
据他所说，json_encode和json_decode比内置的serialize和unserialize函数要高效。&lt;br/&gt;&#xD;
于是我决定动手实验，证实一下同事所说的情况是否属实。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;实验分别在PHP 5.2.13和PHP 5.3.2环境下进行。&lt;br/&gt;&#xD;
用同一个变量，分别用以上方式进行编码或解码10000次，并得出每个函数执行10000次所需的时间。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;以下是PHP 5.2.13环境其中一次测试结果：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;json :		190&#xD;
serialize :	257&#xD;
&#xD;
json_encode :	0.08364200592041&#xD;
json_decode :	0.18004894256592&#xD;
&#xD;
serialize :	0.063642024993896&#xD;
unserialize :	0.086990833282471&#xD;
&#xD;
DONE.&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;以下是PHP 5.3.2环境其中一次测试结果：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;json :		190&#xD;
serialize :	257&#xD;
&#xD;
json_encode :	0.062805891036987&#xD;
json_decode :	0.14239192008972&#xD;
&#xD;
serialize :	0.048481941223145&#xD;
unserialize :	0.05927300453186&#xD;
&#xD;
DONE.&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;这次实验得到的结论是：&lt;br/&gt;&#xD;
json_encode和json_decode的效率并没有比serialize和unserialize的效率高，在反序列化的时候性能相差两倍左右，PHP 5.3执行效率比PHP 5.2略有提升。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;以下是我用来做测试的代码：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;&amp;lt;?php&#xD;
$target = array (&#xD;
  'name' =&amp;gt; '全能头盔',&#xD;
  'quality' =&amp;gt; 'Blue',&#xD;
  'ti_id' =&amp;gt; 21302,&#xD;
  'is_bind' =&amp;gt; 1,&#xD;
  'demand_conditions' =&amp;gt; &#xD;
  array (&#xD;
    'HeroLevel' =&amp;gt; 1,&#xD;
  ),&#xD;
  'quality_attr_sign' =&amp;gt; &#xD;
  array (&#xD;
    'HeroStrength' =&amp;gt; 8,&#xD;
    'HeroAgility' =&amp;gt; 8,&#xD;
    'HeroIntelligence' =&amp;gt; 8,&#xD;
  ),&#xD;
);&#xD;
&#xD;
$json = json_encode($target);&#xD;
$seri = serialize($target);&#xD;
&#xD;
echo "json :\t\t" . strlen($json) . "\r\n";&#xD;
echo "serialize :\t" . strlen($seri) . "\r\n\r\n";&#xD;
&#xD;
$stime = microtime(true);&#xD;
&#xD;
for ($i = 0; $i &amp;lt; 10000; $i ++)&#xD;
{&#xD;
    json_encode($target);&#xD;
}&#xD;
&#xD;
$etime = microtime(true);&#xD;
&#xD;
echo "json_encode :\t" . ($etime - $stime) . "\r\n";&#xD;
&#xD;
//----------------------------------&#xD;
&#xD;
$stime = microtime(true);&#xD;
&#xD;
for ($i = 0; $i &amp;lt; 10000; $i ++)&#xD;
{&#xD;
    json_decode($json);&#xD;
}&#xD;
&#xD;
$etime = microtime(true);&#xD;
&#xD;
echo "json_decode :\t" . ($etime - $stime) . "\r\n\r\n";&#xD;
&#xD;
//----------------------------------&#xD;
&#xD;
$stime = microtime(true);&#xD;
&#xD;
for ($i = 0; $i &amp;lt; 10000; $i ++)&#xD;
{&#xD;
    serialize($target);&#xD;
}&#xD;
&#xD;
$etime = microtime(true);&#xD;
&#xD;
echo "serialize :\t" . ($etime - $stime) . "\r\n";&#xD;
&#xD;
//----------------------------------&#xD;
&#xD;
$stime = microtime(true);&#xD;
&#xD;
for ($i = 0; $i &amp;lt; 10000; $i ++)&#xD;
{&#xD;
    unserialize($seri);&#xD;
}&#xD;
&#xD;
$etime = microtime(true);&#xD;
&#xD;
echo "unserialize :\t" . ($etime - $stime) . "\r\n\r\n";&#xD;
&#xD;
echo 'DONE.';&#xD;
&#xD;
?&amp;gt;&#xD;
&lt;/pre&gt;&lt;img src="http://www.cnblogs.com/mingda/aggbug/1753152.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/06/07/performance_test_of_php_object_serialization.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/04/12/compile_libmemcached_with_cygwin.html</id><title type="text">Cygwin下编译libmemcached</title><summary type="text">今天试着在Cygwin下编译libmemcached，遇到一些问题，经过几番折腾最终解决，并成功连接Memcached，记下来，方便别人参考。</summary><published>2010-04-12T15:51:00Z</published><updated>2010-04-12T15:51:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/04/12/compile_libmemcached_with_cygwin.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/04/12/compile_libmemcached_with_cygwin.html"/><content type="html">&lt;p&gt;今天试着在Cygwin下编译libmemcached，遇到一些问题，经过几番折腾最终解决，并成功连接Memcached，记下来，方便别人参考。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;编译libmemcached之前，需要先安装memcached，而编译安装memcached依赖于libevent。&lt;br/&gt;&#xD;
&#xD;
&lt;p&gt;所以，首先要下载libevent代码并编译安装，这步不会遇到什么问题，所以没什么好说明的。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;接着，下载memcached代码并编译安装，配置步骤也不会有问题，只要上一步的libevent有顺利完成。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;到make的时候，会遇到两个问题：&lt;br/&gt;&#xD;
1. util.c编译无法通过。&lt;br/&gt;&#xD;
2. 解决掉util.c的问题后，testapp.c编译通不过，提示sigaction缺少字段sa_handler。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;解决第一个问题，需要打开util.c，在文件顶部加一个宏：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;#define xisspace(c) isspace((unsigned char)(c))&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;然后，替换util.c中的isspace为xisspace。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;解决第二个问题，需要打开testapp.c，找到125行的：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;struct sigaction action = { .sa_handler = SIG_IGN, .sa_flags = 0};&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;将代码改为：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;struct sigaction action;&#xD;
action.sa_handler = SIG_IGN;&#xD;
action.sa_flags = 0;&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;处理完上面两个问题，memcached应该可以正确配置并make成功了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;接着配置libmemcached，也不会有问题，在make时又会出现编译错误：client/ms_setting.h 文件59行的timeval类型找不到。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;打开client/ms_setting.h，在文件头部加入time.h的包含（timeval就声明在里面）：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;#include "sys/time.h"&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;这时候应该就可以顺利编译并安装libmemcached了。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;以上步骤完成后，使用以下命令启动memcached：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;memcached -d -m 2048 -l 127.0.0.1 -p 11211&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;然后在libmemecached的代码文件夹新建一个test.c，复制以下代码到test.c。&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;#include "stdio.h"&#xD;
#include "stdlib.h"&#xD;
#include "string.h"&#xD;
#include "libmemcached/memcached.h"&#xD;
&#xD;
int main(int argc, char *argv[]) &#xD;
{&#xD;
    memcached_st *memc;&#xD;
    memcached_return rc;&#xD;
    memcached_server_st *servers;&#xD;
&#xD;
    char value[8191];&#xD;
&#xD;
    //connect multi server&#xD;
    memc = memcached_create(NULL);&#xD;
    servers = memcached_server_list_append(NULL, "localhost", 11211, &amp;amp;rc);&#xD;
    //servers = memcached_server_list_append(servers, "localhost", 11212, &amp;amp;rc);&#xD;
    rc = memcached_server_push(memc, servers);&#xD;
    memcached_server_free(servers);&#xD;
&#xD;
    //Save multi data&#xD;
    size_t i;&#xD;
&#xD;
    char *keys[]= {"key1", "key2", "key3"};&#xD;
    size_t key_length[]= {4, 4, 4};&#xD;
&#xD;
    char *values[] = {&#xD;
        "This is c first value", &#xD;
        "This is c second value", &#xD;
        "This is c third value"&#xD;
    };&#xD;
    size_t val_length[]= {21, 22, 21};&#xD;
&#xD;
    for (i = 0; i &amp;lt; 3; i++)&#xD;
    {&#xD;
        rc = memcached_set(&#xD;
                memc, &#xD;
                keys[i], &#xD;
                key_length[i], &#xD;
                values[i], &#xD;
                val_length[i], &#xD;
                (time_t)180, &#xD;
                (uint32_t)0&#xD;
        );&#xD;
&#xD;
        if (rc == MEMCACHED_SUCCESS) &#xD;
        {&#xD;
            printf("Save key:%s data:\"%s\" success.\n",keys[i], values[i]);&#xD;
        }&#xD;
    }&#xD;
    &#xD;
    char return_key[MEMCACHED_MAX_KEY];&#xD;
    size_t return_key_length;&#xD;
&#xD;
    char *return_value;&#xD;
    size_t return_value_length;&#xD;
&#xD;
    uint32_t flags;&#xD;
&#xD;
    rc = memcached_mget(memc, keys, key_length, 3);&#xD;
&#xD;
    while ((return_value = memcached_fetch(&#xD;
        memc, &#xD;
        return_key,&#xD;
        &amp;amp;return_key_length, &#xD;
        &amp;amp;return_value_length, &#xD;
        &amp;amp;flags, &#xD;
        &amp;amp;rc))) &#xD;
    {&#xD;
        if (rc == MEMCACHED_SUCCESS) &#xD;
        {&#xD;
            printf("Fetch key:%s data:\"%s\"\n", return_key, return_value);&#xD;
        }&#xD;
    }&#xD;
&#xD;
    //Delete multi data&#xD;
    for (i = 0; i &amp;lt; 3; i++) &#xD;
    {&#xD;
        rc = memcached_set(&#xD;
                memc, &#xD;
                keys[i], &#xD;
                key_length[i], &#xD;
                values[i], &#xD;
                val_length[i], &#xD;
                (time_t)180, &#xD;
                (uint32_t)0&#xD;
        );&#xD;
&#xD;
        rc = memcached_delete(&#xD;
                memc, &#xD;
                keys[i],&#xD;
                key_length[i], &#xD;
                (time_t)0&#xD;
        );&#xD;
&#xD;
        if (rc == MEMCACHED_SUCCESS) &#xD;
        {&#xD;
            printf("Delete %s success\n", keys[i], values[i]);&#xD;
        }&#xD;
    }&#xD;
&#xD;
    //free&#xD;
    memcached_free(memc);&#xD;
&#xD;
    return 0;&#xD;
}&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;使用以下命令编译并测试：&lt;/P&gt;&#xD;
&#xD;
&lt;pre &gt;gcc -o test test.c -L /usr/local/lib -lmemcached&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;如果一切正常的话，应该得到以下输入：&lt;/p&gt;&#xD;
&#xD;
&lt;pre &gt;Save key:key1 data:"This is c first value" success.&#xD;
Save key:key2 data:"This is c second value" success.&#xD;
Save key:key3 data:"This is c third value" success.&#xD;
Fetch key:key1 data:"This is c first value"&#xD;
Fetch key:key2 data:"This is c second value"&#xD;
Fetch key:key3 data:"This is c third value"&#xD;
Delete key1 success&#xD;
Delete key2 success&#xD;
Delete key3 success&#xD;
&lt;/pre&gt;&#xD;
&#xD;
&lt;p&gt;经过我测试，这时候的test.exe，是已经静态编译了libmemcached了，如果要脱离cygwin的shell执行，例如在另外一台没有安装cygwin的机器执行，除了上面编译的test.exe以外，还需要拷贝cygwin的bin目录下的cyggcc_s-1.dll和cygwin1.dll，到目标机器上的test.exe所在的目录。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;声明：&lt;br/&gt;&#xD;
1. 以上测试所用的C语言代码来自于“大宗师”的博客文章《&lt;a href="http://blog.chinaacc.com/liuzhantao/blog/20100226-2612042929081.html" target="_blank"&gt;c读写memcache&lt;/a&gt;》&lt;br/&gt;&#xD;
2. 本文所涉及的软件版本分别为：&lt;br/&gt;&#xD;
cygwin-2.693、libevent-1.4.13-stable、memcached-1.4.5、libmemcached-0.39&lt;/br&gt;&#xD;
本文内容极有可能会随以上软件版本更新而变得不再适用。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/mingda/aggbug/1710658.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/mingda/archive/2010/04/12/compile_libmemcached_with_cygwin.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/02/11/actionscript_drawing_complex_button.html</id><title type="text">ActionScript绘制复杂样式按钮</title><summary type="text">新版MicroUI的可行性试验。</summary><published>2010-02-11T15:16:00Z</published><updated>2010-02-11T15:16:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/02/11/actionscript_drawing_complex_button.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/02/11/actionscript_drawing_complex_button.html"/></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/02/09/1666145.html</id><title type="text">无题</title><summary type="text">这段时间换了工作，很感激身边的人都很关心和信任我，你们让我学到很多。</summary><published>2010-02-08T16:09:00Z</published><updated>2010-02-08T16:09:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/02/09/1666145.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/02/09/1666145.html"/></entry><entry><id>http://www.cnblogs.com/mingda/archive/2010/02/01/javascript_get_element_position.html</id><title type="text">获取元素位置坐标的JavaScript函数</title><summary type="text">获取元素位置坐标的JS函数，记下来，方便找 :)</summary><published>2010-02-01T01:48:00Z</published><updated>2010-02-01T01:48:00Z</updated><author><name>达达</name><uri>http://www.cnblogs.com/mingda/</uri></author><link rel="alternate" href="http://www.cnblogs.com/mingda/archive/2010/02/01/javascript_get_element_position.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/mingda/archive/2010/02/01/javascript_get_element_position.html"/></entry></feed>
