进程模型服务器端

进程模型服务器端修炼主要包括以下境界

1.每个进程对应一个连接

2.预先创建一定量的进程,当连接到来时,拿一个进程来对付他,个人称它为静态进程池

3.预先创建一定量的进程,当连接到来时,拿一个进程来对付他,如果没有进程,尝试创建新进程,当进程过多时,关闭一些进程,此乃动态调整的进程池模型。

4.与其他模型联合使用,比如说和线程模型,和IO复用模型合用

此文提出第一第二境界的解决方案。

 

本文包括第一和第二点,后面两点涉及知识点稍多。暂时没能很好的应用。

进程——连接:对于每个连接,fork一个进程来处理连接,处理结束即退出子进程

优点:简单,非常简单

缺点:效率不行,并发访问不行,大量连接不行。

对于此类模型,代码相对容易,服务器端如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>

#define MAX_BUF    1024


int setup(char *ip, int port){
    /* variable */
    int sock_fd, connect_fd;
    struct sockaddr_in server, client;
    int ret;
    /* socket */
    sock_fd = socket(PF_INET, SOCK_STREAM, 0);
    if(sock_fd < 0){
        perror("socket failed");
        exit(1);
    }

    server.sin_family = PF_INET;
    //server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(port);
    if(inet_pton(PF_INET, ip, &server.sin_addr) < 0){
        perror("inet_pton");
        exit(1);
    }
    /* bind */
    ret = bind(sock_fd, (struct sockaddr*)&server, sizeof(server));
    if(ret < 0){
        perror("bind failed");
        exit(1);
    }
    /* listen */
    if(listen(sock_fd, 5)<0){
        perror("listen failed\n");
        exit(1);
    }
    return sock_fd;
}

//处理模型
void process_mode(int sock_fd, int connect_fd){
    char buff[MAX_BUF];
    int ret = -1;
    pid_t pid;
    pid = fork();
    if(pid<0){
        perror("fork error");
        exit(errno);
    }
    //子进程
    else if(pid == 0){

        close(sock_fd);
        if((ret = recv(connect_fd, buff, sizeof(buff), 0)) < 0){
            perror("recv");
            exit(1);
        }        
        else if(ret == 0)
            printf("read end\n");
        else{
            fprintf(stderr,"receive message %s retval:%d\n",
             buff, ret);
        }
        close(connect_fd);
        exit(0);
    }
    total_count++;    
    close(connect_fd);
}


int main(int argc, char **argv){

    int connect_fd;
    if(argc != 3){
        fprintf(stderr,"usage <ip><port>");
        exit(-1);
    }
    
    //setup
    int sock_fd = setup(argv[1], atoi(argv[2]) );
    printf("network setup successfully.\nip:%s port:%s\n",
         argv[1], argv[2]);
    /* accept */
    while(1){
        connect_fd = accept(sock_fd, (struct sockaddr*)NULL,NULL);    
        process_mode(sock_fd, connect_fd);    
    }    
    
    return 0;    
}

 

值得注意的一点是:fork之后要关闭原本绑定的套接字,父进程要关闭连接套接字,这是fork带来的影响,关闭描述符并不会带来资源销毁,只要描述符引用不为0即可

为了克服上面模型中效率低下的缺点,可以预先fork一定量的进程,当连接到来时就不用重新fork了,处理完客户请求之后,不是退出,而是继续等待请求。由此产生静态进程池。

静态线程池的实现相对简单。难点是设计的时候要防止惊群现象。所谓惊群就类似一群鸽子在吃东西你跑去了全部鸽子都跑了。为杜绝惊群现象,需要加锁。

此类模型优点是避免fork带来的效率上的降低。

缺点是效率还是不够高,当进程池中进程不足时,不能动态调整池中进程个数。当连接很少时,池中进程数过多,这也是一种浪费。

具体实现如下:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#define MAX_BUF    1024


/* 静态池 */
typedef struct static_pool{
    int nchild;
    pid_t *pids;
}spool;

/* 服务器结构 */
typedef struct Server{

}Server, *pServer;

spool pool;

/* 启动 */
int setup(char *ip, int port){
    /* variable */
    int sock_fd, connect_fd;
    struct sockaddr_in server, client;
    int ret;
    /* socket */
    sock_fd = socket(PF_INET, SOCK_STREAM, 0);
    if(sock_fd < 0){
        perror("socket failed");
        exit(1);
    }

    server.sin_family = PF_INET;
    //server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons(port);
    if(inet_pton(PF_INET, ip, &server.sin_addr) < 0){
        perror("inet_pton");
        exit(1);
    }
    /* bind */
    ret = bind(sock_fd, (struct sockaddr*)&server, sizeof(server));
    if(ret < 0){
        perror("bind failed");
        exit(1);
    }
    /* listen */
    if(listen(sock_fd, 5)<0){
        perror("listen failed\n");
        exit(1);
    }
    return sock_fd;
}

//SIGNAL INT int3中断
void sig_call_back(int signo){
    int i;

    for(i = 0; i<pool.nchild; i++)
        kill(pool.pids[i], SIGTERM);
    while(wait(NULL)>0);

    if(errno != ECHILD){
        perror("wait");
        exit(errno);
    }
    exit(0);
}

//封装锁操作, reference:Unix Network Programming

struct flock lock_it, unlock_it;
int lock_fd = -1;
/* 初始化信息 */
void my_lock_init(char *pathname){
    
    char lock_file[1024];

    strncpy(lock_file, pathname, sizeof(lock_file));
    lock_fd = mkstemp(lock_file);
    if(lock_fd < 0){
        perror("mkstemp");
        exit(errno);
    }
    unlink(lock_file);
    lock_it.l_type = F_WRLCK;
    lock_it.l_whence = SEEK_SET;
    lock_it.l_start = 0;
    lock_it.l_len = 0;
    
    unlock_it.l_type = F_UNLCK;
    unlock_it.l_whence = SEEK_SET;
    unlock_it.l_start = 0;
    unlock_it.l_len = 0;
    
}
/* 锁等待 */
void my_lock_wait(){
    int rc;
    
    while((rc = fcntl(lock_fd, F_SETLKW, &lock_it))<0){
        if(errno == EINTR)
            continue;
        else{
            perror("fcntl");
            exit(errno);
        }
    }
}

/* 释放锁 */
void my_lock_release(){
    if(fcntl(lock_fd, F_SETLKW, &unlock_it) < 0){
        perror("fcntl");
        exit(errno);
    }
}

/* 处理请求, 此处为空 */
void process(int connect_fd){

}

/* 等待请求 */
void child_loop(int sock_fd){
    
    int connect_fd;
    socklen_t client_len;
    struct sockaddr_in client;
    memset(&client, 0, sizeof(client));
    while(1){
        client_len = sizeof(struct sockaddr_in);
        my_lock_wait();
        connect_fd = accept(sock_fd, (struct sockaddr*)&client, &client_len);
        printf("process %d deal with connnector\n", getpid());
        process(connect_fd);
        close(connect_fd);
        my_lock_release();
    }
}

/* 产生子进程,子进程接受请求并处理请求 */
int make_child(int sock_fd){
    pid_t pid;
    if((pid = fork()) > 0)
        return pid;
    child_loop(sock_fd);
}

/* 预先fork */
void preprocess(int sock_fd, int n){
    
    int i = 0;
    pool.nchild = n;
    pool.pids = (pid_t*)malloc(sizeof(pid_t) * n);    
    if(pool.pids == NULL){
        perror("malloc");
        exit(-1);
    }
    
    //生娃
    my_lock_init("/tmp/lock.XXXXXX");
    for(i = 0; i<n; i++)
        pool.pids[i] = make_child(sock_fd);
}


int main(int argc, char **argv){

    if(argc != 4){
        fprintf(stderr,"usage <ip><port><process num>");
        exit(-1);
    }
    //setup
    int sock_fd = setup(argv[1], atoi(argv[2]) );
    printf("network setup successfully.\nip:%s port:%s\n",
         argv[1], argv[2]);
    
    preprocess(sock_fd, atoi(argv[3]));    
    signal(SIGINT, sig_call_back);
    for(;;)
        pause();
    return 0;    
}

 

代码不难,注意点却是不少。一个是加锁,一个是父进程结束时候要把所有子进程都杀掉,避免产生孤儿进程。

用于测试的客户端例子,对于上面两个模型都适用。

#include <stdio.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netdb.h>
#include <netinet/in.h>
#include <sys/socket.h>

#define LEN(str) (sizeof(char)*strlen(str))

void connect_server(char *ip, int port){
    
    /* variable */
    int sock_fd;
    struct sockaddr_in server;
    char buff[1024];
    int ret;

    /* socket */
    sock_fd = socket(PF_INET, SOCK_STREAM, 0);
    if(sock_fd < 0)    {
        perror("socket failed");
        exit(1);
    }

    server.sin_family = PF_INET;
    server.sin_port = htons(port);
    if(inet_pton(PF_INET, ip, &server.sin_addr) < 0){
        perror("inet_pton");
        exit(1);
    }
    /* connect */
    if((ret = connect(sock_fd, (struct sockaddr*)&server, 
                sizeof(server)) )< 0){
        perror("connect failed");
        exit(1);
    }
    /* send buff */
    sprintf(buff, "Hello World");
    if(( ret = send(sock_fd, buff, LEN(buff), 0)) < 0){
        perror("send");
        exit(1);
    }
    printf("send msg\n");

    /* close */
    close(sock_fd);
}

int main(int argc, char **argv){
    int i;
    if(argc < 4){
        perror("usage<ip><port><connect count>");
        exit(-1);
    }
    for(i = 0; i< atoi(argv[3]); i++)
        connect_server(argv[1], atoi(argv[2]) );
    return 0;
}

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。