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