前面介绍了关于连接linux服务端方式,可是服务端的资源是有限的,所以我们通常须要又一次思考,设计一套server模型来处理相应的client的请求。

第一种:并发server。通过主进程统一处理client的连接。当client连接过后。暂时fork()进程,由子进程处理client请求,将连接请求和业务进行了分离。

server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#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);        /*发送数据*/
    }       
    /*关闭client*/
    close(s_c); 
}
static int handle_connect(int s_s)
{

    int s_c;                                /*client套接字文件描写叙述符*/
    struct sockaddr_in from;                /*client地址*/
    socklen_t len = sizeof(from);

    /*主处理过程*/
    while(1)
    {
        /*接收client连接*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        if(s_c > 0)                         /*client成功连接*/
        {
            /*创建进程进行数据处理*/
            if(fork() > 0){                 /*父进程*/
                close(s_c);                 /*关闭父进程的client连接套接字*/
            }else{
                handle_request(s_c);        /*处理连接请求*/
                return(0);  
            }
        }
    }       
}
int main(int argc, char *argv[])
{
    int s_s;                                /*server套接字文件描写叙述符*/
    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);    /*serverport*/

    /*将套接字文件描写叙述符绑定到本地地址和port*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);               /*侦听*/

    /*处理client连接*/
    handle_connect(s_s);

    close(s_s);

    return 0;
}

代码比較具体,easy理解。

以下介绍client代码,后面的client代码都是一样的。


client.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
    int s;                                      /*server套接字文件描写叙述符*/
    struct sockaddr_in server;                  /*本地地址*/
    char buff[BUFFLEN];                         /*收发数据缓冲区*/
    int n = 0;                                  /*接收字符串长度*/

    /*建立TCP套接字*/
    s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址*/
    memset(&server, 0, sizeof(server));     /*清零*/
    server.sin_family = AF_INET;                /*AF_INET协议族*/
    server.sin_addr.s_addr = htonl(INADDR_ANY);/*随意本地地址*/
    server.sin_port = htons(SERVER_PORT);       /*serverport*/   

    /*连接server*/
    connect(s, (struct sockaddr*)&server,sizeof(server));
    memset(buff, 0, BUFFLEN);                   /*清零*/
    strcpy(buff, "TIME");                       /*复制发送字符串*/
    /*发送数据*/
    send(s, buff, strlen(buff), 0);
    memset(buff, 0, BUFFLEN);                   /*清零*/
    /*接收数据*/    
    n = recv(s, buff, BUFFLEN, 0);
    /*打印消息*/
    if(n >0){
        printf("TIME:%s",buff); 
    }
    close(s);

    return 0;
}

另外一种模型:通过线程来处理,线程比进程占用资源少,效率高,数据共享。通过pthread_create()建立一个连接请求处理。线程处理函数为handle_request().
server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
static void handle_request(void *argv)
{
    int s_c = *((int*)argv);
    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);        /*发送数据*/
    }       
    /*关闭client*/
    close(s_c); 
}
static void handle_connect(int s_s)
{

    int s_c;                                    /*client套接字文件描写叙述符*/
    struct sockaddr_in from;                    /*client地址*/
    socklen_t len = sizeof(from);
    pthread_t  thread_do;

    /*主处理过程*/
    while(1)
    {
        /*接收client连接*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        if(s_c > 0)                         /*client成功连接*/
        {
            /*创建线程处理连接*/
            pthread_create(&thread_do,
                    NULL,
                    (void*)handle_request,
                    &s_c);              
        }
    }       
}
int main(int argc, char *argv[])
{
    int s_s;                                /*server套接字文件描写叙述符*/
    struct sockaddr_in local;               /*本地地址*/    

    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址和port*/
    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);        /*serverport*/

    /*将套接字文件描写叙述符绑定到本地地址和port*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);                   /*侦听*/

    /*处理client连接*/
    handle_connect(s_s);

    close(s_s);

    return 0;       
}

第三种:服务端各线程独自accept()。使用相互排斥锁,使用pthread_create()建立多个线程组成的线程池,主线程等待程序结束。各个线程独自接收clientaccept。以及后面数据处理。
server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 2
/*相互排斥量*/
pthread_mutex_t ALOCK = PTHREAD_MUTEX_INITIALIZER;
static void *handle_request(void *argv)
{   
    int s_s = *((int*)argv);
    int s_c;                                /*client套接字文件描写叙述符*/
    struct sockaddr_in from;                /*client地址*/
    socklen_t len = sizeof(from);
    for(;;)
    {
        time_t now;                         /*时间*/
        char buff[BUFFLEN];                 /*收发数据缓冲区*/
        int n = 0;

        pthread_mutex_lock(&ALOCK);         /*进入相互排斥区*/
        s_c = accept(s_s, (struct sockaddr*)&from, &len);
        /*接收client的请求*/
        pthread_mutex_unlock(&ALOCK);       /*离开相互排斥区*/

        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);        /*发送数据*/
        }       
        /*关闭client*/
        close(s_c); 
    }

    return NULL;
}
static void handle_connect(int s)
{   
    int s_s = s;
    pthread_t  thread_do[CLIENTNUM];        /*线程ID*/
    int i = 0;
    for(i=0;i<CLIENTNUM;i++)                /*建立线程池*/
    {
        /*创建线程*/
        pthread_create(&thread_do[i],       /*线程ID*/
                    NULL,                   /*属性*/
                    handle_request,         /*线程回调函数*/
                    (void*)&s_s);           /*线程參数*/
    }
    /*等待线程结束*/
    for(i=0;i<CLIENTNUM;i++)
        pthread_join(thread_do[i], NULL);
}
int main(int argc, char *argv[])
{
    int s_s;                                /*server套接字文件描写叙述符*/
    struct sockaddr_in local;               /*本地地址*/    

    /*建立TCP套接字*/
    s_s = socket(AF_INET, SOCK_STREAM, 0);

    /*初始化地址和port*/
    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);        /*serverport*/

    /*将套接字文件描写叙述符绑定到本地地址和port*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);                   /*侦听*/

    /*处理client连接*/
    handle_connect(s_s);

    close(s_s);                                 /*关闭套接字*/

    return 0;
}

第四种:IO复用server。并发serverclient越多,对server造成的压力越大,所以还有第四种模型。IO复用函数用select来做。
server.c

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/select.h>
#define BUFFLEN 1024
#define SERVER_PORT 8888
#define BACKLOG 5
#define CLIENTNUM 1024                      /*最大支持client数量*/

/*可连接client的文件描写叙述符数组*/
int connect_host[CLIENTNUM];
int connect_number =  0;
static void *handle_request(void *argv)
{   
    time_t now;                                 /*时间*/
    char buff[BUFFLEN];                         /*收发数据缓冲区*/
    int n = 0;

    int maxfd = -1;                             /*最大侦听文件描写叙述符*/
    fd_set scanfd;                              /*侦听描写叙述符集合*/
    struct   timeval   timeout;                     /*超时*/
    timeout.tv_sec     =   1;                   /* 堵塞1s后超时返回 */     
    timeout.tv_usec   =   0;     

    int i = 0;
    int err  = -1;
    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];
                }
            }
        }
        /*select等待*/
        err = select(maxfd + 1, &scanfd, NULL, NULL, &timeout) ;        
        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)
                    if(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);
                            /*发送数据*/
                        }
                        /*更新文件描写叙述符在数组中的值*/
                        connect_host[i] = -1;
                        connect_number --;  /*client计数器减1*/    
                        /*关闭client*/
                        close(connect_host[i]);
                    } 
                }
                break;  
        }         
    } 

    return NULL;
}
static void *handle_connect(void *argv)
{   
    int s_s = *((int*)argv) ;           /*获得server侦听套接字文件描写叙述符*/
    struct sockaddr_in from;
    socklen_t len = sizeof(from);
    /*接收client连接*/
    for(;;)
    {
        int i = 0;
        int s_c = accept(s_s, (struct sockaddr*)&from, &len);
        /*接收client的请求*/
        printf("a client connect, from:%s\n",inet_ntoa(from.sin_addr));
        /*查找合适位置,将client的文件描写叙述符放入*/             
        for(i=0;i<CLIENTNUM;i++)
        {
            if(connect_host[i] == -1)           /*找到*/
            {
                /*放入*/
                connect_host[i]= s_c;

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


int main(int argc, char *argv[])
{
    int s_s;                                /*server套接字文件描写叙述符*/
    struct sockaddr_in local;               /*本地地址*/    
    int i = 0;
    memset(connect_host, -1, CLIENTNUM);

    /*建立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);        /*serverport*/

    /*将套接字文件描写叙述符绑定到本地地址和port*/
    bind(s_s, (struct sockaddr*)&local, sizeof(local));
    listen(s_s, BACKLOG);                   /*侦听*/

    pthread_t  thread_do[2];/*线程ID*/
    /*创建线程处理client连接*/
    pthread_create(&thread_do[0],               /*线程ID*/
                    NULL,                       /*属性*/
                    handle_connect,             /*线程回调函数*/
                    (void*)&s_s);               /*线程參数*/
    /*创建线程处理client请求*/                 
    pthread_create(&thread_do[1],               /*线程ID*/
                    NULL,                       /*属性*/
                    handle_request,             /*线程回调函数*/
                    NULL);                      /*线程參数*/
    /*等待线程结束*/
    for(i=0;i<2;i++)
        pthread_join(thread_do[i], NULL);

    close(s_s);

    return 0;
}

选择合适的server模型十分重要。对于编程有非常大的影响。