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