进程模型服务器端修炼主要包括以下境界
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; }