Linux网络编程服务器模型之并发服务器


=Start=

缘由:

学习需要

正文:

参考解答:
/*
Linux网络编程服务器模型选择之并发服务器(上)
http://www.cnblogs.com/lizhenghn/p/3617666.html
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <stdio.h>

#define BUFFLEN 1024
#define SERVER_PORT 12347
#define BACKLOG 5

static void handle_request(int s_c)
{
    time_t now;        
    char buff[BUFFLEN];
    int n = 0;
    memset(buff, 0, BUFFLEN);
    n = recv(s_c, buff, BUFFLEN, 0); /*接收发送方数据*/
    if(n > 0 && !strncmp(buff, "TIME", 4)) /*判断是否合法接收数据*/
    {
        memset(buff, 0, BUFFLEN);
        now = time(NULL);
        sprintf(buff, "%24s\r\n",ctime(&now));
        send(s_c, buff, strlen(buff),0); /*发送数据*/
    }        
    
    close(s_c); /*关闭客户端*/
}

static void handle_connect(int s_s)
{    
    int s_c;    /*客户端套接字文件描述符*/
    struct sockaddr_in from;    /*客户端地址*/
    int len = sizeof(from);
    
    /*主处理过程*/
    while(1)
    {
        /*接收客户端连接*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        if(s_c > 0)/*客户端成功连接*/
        {
            /*创建进程进行数据处理*/
            if(fork() > 0)/*父进程*/
            {
                // printf("Remote IP:Port is '%s:%d'\n", inet_ntoa(from.sin_addr), (int)ntohs(from.sin_port)); // Segmentation fault (why?)
                close(s_c);/*关闭父进程的客户端连接套接字*/
            }
            else
            {
                handle_request(s_c);/*处理连接请求*/
                // printf("Remote IP:Port is '%s:%d'\n", inet_ntoa(from.sin_addr), (int)ntohs(from.sin_port)); // Nothing happens (why?)
                return ;    
            }
        }
    }        
}


int main(int argc, char *argv[])
{
    int s_s; /*服务器套接字文件描述符*/
    struct sockaddr_in local; /*本地地址*/    
    
    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);
    
    /*初始化地址*/
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;/*AF_INET协议族*/
    local.sin_addr.s_addr = htonl(INADDR_ANY);/*任意本地地址*/
    local.sin_port = htons(SERVER_PORT);/*服务器端口*/
    
    int err;
    /*将套接字文件描述符绑定到本地地址和端口*/
    err = bind(s_s, (struct sockaddr*)&local, sizeof(local));
    err = listen(s_s, BACKLOG);/*侦听*/
    
    /*处理客户端连接*/
    handle_connect(s_s);
    
    close(s_s);
    
    return 0;        
}

&

/*
Linux网络编程服务器模型选择之IO复用循环并发服务器
http://www.cnblogs.com/lizhenghn/p/3619091.html

http://man7.org/linux/man-pages/man3/pthread_create.3.html
http://man7.org/linux/man-pages/man2/socket.2.html
http://man7.org/linux/man-pages/man2/bind.2.html
http://man7.org/linux/man-pages/man2/listen.2.html
http://man7.org/linux/man-pages/man2/select.2.html
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <pthread.h>
#include <sys/select.h>

#define BUFFLEN 1024
#define SERVER_PORT 12349
#define BACKLOG 5 /*挂起队列的最大长度*/
#define CLIENTNUM 1024 /*最大支持客户端数量*/

// 全局变量
int connect_host[CLIENTNUM]; /*可连接客户端的文件描述符数组*/
int connect_number = 0;

static void *handle_request(void *argv)
{
    time_t now; /*时间*/
    char buff[BUFFLEN]; /*收发数据缓冲区*/

    fd_set scanfd; /*侦听描述符集合*/

    struct timeval timeout; /*超时*/
    timeout.tv_sec = 1; /*阻塞1秒后超时返回*/
    timeout.tv_usec = 0;

    int i = 0;
    int maxfd = -1; /*最大侦听文件描述符*/
    int err = -1;
    int n = 0;
    for(;;)
    {
        /*最大文件描述符值初始化为-1*/
        maxfd = -1;
        FD_ZERO(&scanfd); /*清零文件描述符集合*/
        for(i=0; i<CLIENTNUM; i++) /*将文件描述符放入集合*/
        {
            if(connect_host[i] != -1)/*合法的文件描述符*/
            {
                FD_SET(connect_host[i], &scanfd); /*放入集合*/
                if(maxfd < connect_host[i]) /*更新最大文件描述符值*/
                {
                    maxfd = connect_host[i];
                }
            }
        }
        
        err = select(maxfd + 1, &scanfd, NULL, NULL, &timeout); /*select等待*/
        switch(err)
        {
            case 0: /*超时*/
                break;
            case -1: /*错误发生*/
                break;
            default: /*有可读套接字文件描述符*/
                if(connect_number <= 0)
                    break;
                for(i = 0; i<CLIENTNUM; i++)
                {
                    /*查找激活的文件描述符*/
                    if(connect_host[i] != -1 && FD_ISSET(connect_host[i], &scanfd))
                    {
                        memset(buff, 0, BUFFLEN); /*清零*/
                        n = recv(connect_host[i], buff, BUFFLEN, 0); /*接收发送方数据*/
                        if(n > 0 && !strncmp(buff, "TIME", 4))/*判断是否合法接收数据*/
                        {
                            memset(buff, 0, BUFFLEN); /*清零*/
                            now = time(NULL); /*当前时间*/
                            sprintf(buff, "%24s\r\n", ctime(&now)); /*将时间拷贝入缓冲区*/
                            send(connect_host[i], buff, strlen(buff), 0); /*发送数据*/
                        }
                        /*关闭客户端*/
                        close(connect_host[i]);
                        
                        connect_host[i] = -1; /*更新文件描述符在数组中的值*/
                        connect_number--; /*客户端计数器减1*/
                    }
                }
                break;
        }
    }

    return NULL;
}

static void *handle_connect(void *argv)
{
    int sock_fd = *((int*)argv); /*获得服务器侦听套接字文件描述符*/
    int s_c = -1; /*连接客户端文件描述符*/
    struct sockaddr_in from;
    int len = sizeof(from);
    
    /*接收客户端连接*/
    for(;;)
    {
        int i = 0;
        s_c = accept(sock_fd, (struct sockaddr *)&from, &len); /*接收客户端的请求*/
        // printf("a client connect, from:%s\n", inet_ntoa(from.sin_addr)); // Segmentation fault (why?)
        /*查找合适位置,将客户端的文件描述符放入*/
        for(i=0; i<CLIENTNUM; i++)
        {
            if(connect_host[i] == -1)/*找到*/
            {
                connect_host[i]= s_c; /*放入*/

                connect_number++; /*客户端计数器加1*/
                
                break; /*继续轮询等待客户端连接*/
            }
        }
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    memset(connect_host, -1, CLIENTNUM);

    int sock_fd; /*服务器套接字文件描述符*/
    sock_fd = socket(AF_INET, SOCK_STREAM, 0); /*建立TCP套接字*/

    struct sockaddr_in local; /*本地地址*/
    /*初始化地址连接信息*/
    memset(&local, 0, sizeof(local)); /*清零*/
    local.sin_family = AF_INET; /*AF_INET协议族*/
    local.sin_addr.s_addr = htonl(INADDR_ANY); /*任意本地地址*/
    local.sin_port = htons(SERVER_PORT); /*服务器端口*/

    int err;
    err = bind(sock_fd, (struct sockaddr *)&local, sizeof(local)); /*将套接字文件描述符绑定到本地地址和端口*/
    // if(err == -1) perror("bind() error");

    err = listen(sock_fd, BACKLOG); /*将 sock_fd 设置为监听模式,参数 BACKLOG 定义挂起队列的最大长度*/
    // if(err == -1) perror("listen() error");

    pthread_t thread_do[2]; /*线程ID*/

    /*创建线程处理客户端连接*/
    pthread_create(&thread_do[0], /*线程ID*/
                    NULL, /*属性*/
                    handle_connect, /*线程回调函数*/
                    (void*)&sock_fd); /*传递给线程回调函数的唯一线程参数*/

    /*创建线程处理客户端请求*/
    pthread_create(&thread_do[1], /*线程ID*/
                    NULL, /*属性*/
                    handle_request, /*线程回调函数*/
                    NULL); /*传递给线程回调函数的唯一线程参数*/
    int i = 0;
    for(i=0; i<2; i++)
        pthread_join(thread_do[i], NULL); /*等待线程结束*/

    close(sock_fd);

    return 0;
}
参考链接:

Linux网络编程服务器模型选择之并发服务器(上)
http://www.cnblogs.com/lizhenghn/p/3617666.html

Linux网络编程服务器模型选择之IO复用循环并发服务器
http://www.cnblogs.com/lizhenghn/p/3619091.html

http://man7.org/linux/man-pages/man3/pthread_create.3.html
http://man7.org/linux/man-pages/man2/socket.2.html
http://man7.org/linux/man-pages/man2/bind.2.html
http://man7.org/linux/man-pages/man2/listen.2.html
http://man7.org/linux/man-pages/man2/select.2.html

=END=


《 “Linux网络编程服务器模型之并发服务器” 》 有 6 条评论

  1. 通过完整示例来理解如何使用 epoll
    http://blog.jobbole.com/93566/
    https://banu.com/blog/2/how-to-use-epoll-a-complete-example-in-c/
    `
    Linux 的 epoll(7) 机制,它是 Linux 最好的「就绪通知机制」。

    select(2) 一次可以监测 FD_SETSIZE 数量大小的描述符,FD_SETSIZE 通常是一个在 libc 编译时指定的数字(1024)。需要线性扫描所有通过描述符,时间复杂度为 O(n) 。
    poll(2) 一次可以监测的描述符数量并没有限制,但撇开其它因素,我们每次都不得不检查就绪通知,线性扫描所有通过描述符,这样时间复杂度为 O(n) 而且很慢。
    epoll 没有这些固定限制,也不执行任何线性扫描,能在O(1)时间内完成操作。因此它可以更高效地执行和处理大量事件。
    `

  2. 漫话:如何给女朋友解释什么是BIO、NIO和AIO?
    https://mp.weixin.qq.com/s/HQd-PsnJI8TtEtAXMsjuPw
    `
    BIO (Blocking I/O):同步阻塞I/O模式。
    NIO (New I/O):同步非阻塞模式。
    AIO (Asynchronous I/O):异步非阻塞I/O模型。

    以烧水为例进行简单说明:
    同步阻塞模式:这种模式下,我们的工作模式是先来到厨房,开始烧水,并坐在水壶面前一直等着水烧开。

    同步非阻塞模式:这种模式下,我们的工作模式是先来到厨房,开始烧水,但是我们不一直坐在水壶前面等,而是回到客厅看电视,然后每隔几分钟到厨房看一下水有没有烧开。

    异步非阻塞I/O模型:这种模式下,我们的工作模式是先来到厨房,开始烧水,我们不一一直坐在水壶前面等,也不隔一段时间去看一下,而是在客厅看电视,水壶上面有个开关,水烧开之后他会通知我。

    阻塞VS非阻塞:人是否坐在水壶前面一直等。

    同步VS异步:水壶是不是在水烧开之后主动通知人。
    `

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注