一、多进程服务器
在实际项目中,服务器同时可以处理多个客户端的请求,我们称为并发服务器。比如12306服务器,在遇到春节购票高峰的时候,虽然一天需要处理上亿的客户流量,但是我们购票丝毫不觉得卡顿,当然有时候会是网络的问题,这就是并发服务器的强大功能。并发方式还有多线程、多路复用(select、epoll、poll),本文主要记录多进程并发。
二、多进程编程系统调用
1、fork()函数
(1)原型
头文件#include <unistd>
pid_t fork(void);
返回值:成功返回0或>0的数,失败返回-1;
(2)功能
fork()调用之后,当前进程称为父进程,父进程的文本段、数据段、堆栈都会被复制一份,在内存区域重新开辟空间,被复制的进程称为子进程,fork()成功,则返回两个值,第一次返回给父进程,是子进程的PID。第二次返回0给子进程。fork()失败返回<0,失败的原因一般是系统中有太多进程或着超出了系统限制。调用getpid可以获取当前进程ID,getppid()获取父进程ID。
2、vfork()函数
(1)原型
头文件:
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);
返回值:
成功:子进程中返回 0,父进程中返回子进程 ID。
失败:返回 -1。
(2)功能:vfork和fork都是创建新进程,它俩有两个区别,执行顺序和对数据段的影响。
执行顺序:fork()调用后的父子进程执行顺序由操作系统调度器决定;而vfork()后的父进程要等待子进程exit(0)退出后才执行。
数据段影响:子进程没有退出之前是在父进程的进程空间运行,这也是父子进进程均会影响数据段的原因。
3、exec*()系列函数
功能:是去执行另外的程序。exec函数不会返回,因为去执行别的程序,如果返回,说明出错。
4、wait()和waitpid()
父进程可以调用wait()或waitpid()查看子进程推出状态。
僵死进程:如果一个已经终止、但其父进程尚未对其调用wait进行善后处理(获取终止子进程的有关信息如CPU时间片、释放它锁占用的资源如文件描述符等的进程被称僵死进程(zombie)。所以最好使用wait()或waitpid()处理僵死进程。
四、实现代码
这里只给出服务器的main实现代码,其他c文件和客户端代码请点击查看
main.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<fcntl.h>
#include<errno.h>
#include<stdlib.h>
#include<getopt.h>
#include<string.h>
#include<arpa/inet.h>
#include<ctype.h>
#include"server.h"
int deal_data(char *,char *,char *,char *);
/*
void print_usage(char *program)
{
printf("%s usages: %s\n",strerror(errno));
printf("-p(port):serve listen port\n");
printf("-h(help):help information\n");
exit(0);
}
*/
int main(int argc,char **argv)
{
int opt;
int serv_port = 0;
struct sockaddr_in serv_addr;
struct sockaddr_in clien_addr;
socklen_t clienaddr_len;
int sockfd;
int clienfd;
int fd;
int rv;
char buf[1024];
int on = 1;
char finish[25]="serve receive finish!";
pid_t pid;
sqlite3 *db;
char *errmsg;
int statu;
char ID[50];
char temp[10];
char time[20];
/*
struct option long_options[]=
{
{"port",1,NULL,'p'},
{"h",0,NULL,'h'},
{0,0,0,0}
};
while((opt = getopt_long(argc,argv,"p:h",long_options,NULL)) > 0)
{
switch(opt)
{
case 'p':
serv_port = atoi(optarg);
break;
case 'h':
print_usage(argv[0]);
break;
default:
break;
}
}
if(!serv_port)
{
print_usage(argv[0]);
}
*/
DNSopt(argc,argv,&serv_port);
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0)
{
printf("serve socket failed: %s\n",strerror(errno));
mylog(__FUNCTION__,__LINE__,ERROR,"serve socket failed:%s\n",strerror(errno));
}
memset(&serv_addr,0,sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(serv_port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
if(bind(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0)
{
printf("bind failed: %s\n",strerror(errno));
mylog(__FUNCTION__,__LINE__,ERROR,"bind failed:%s\n",strerror(errno));
close(sockfd);
return -1;
}
listen(sockfd,10);
while(1)
{
printf("serve wait to connect new client...\n");
if((clienfd = accept(sockfd,(struct sockaddr *)&clien_addr,&clienaddr_len)) <0 )
{
printf("serve accept failed: %s\n",strerror(errno));
mylog(__FUNCTION__,__LINE__,ERROR,"server accept faild:%s\n",strerror(errno));
return -1;
}
printf("Accept new client[%s:%d] scuccessfully.\n",inet_ntoa(clien_addr.sin_addr),ntohs(clien_addr.sin_port));
pid = fork();
if(pid < 0)
{
printf("fork() create child process failed: %s\n",strerror(errno));
mylog(__FUNCTION__,__LINE__,ERROR,"fork() create process failed:%s\n",strerror(errno));
close(clienfd);
continue;
}
if(pid > 0)
{
close(clienfd);
continue;
}
if(pid == 0)
{
close(sockfd);
printf("child process is running...\n");
datbas_open(&db);
while(1)
{
memset(buf,0,sizeof(buf));
if((rv=read(clienfd,buf,sizeof(buf))) < 0)
{
printf("read from client failed: %s\n",strerror(errno));
close(clienfd);
close(fd);
datbas_close(db);
exit(0);
}
else if(rv == 0)
{
printf("Disconnect with client..\n");
close(clienfd);
close(fd);
datbas_close(db);
exit(0);
}
if(deal_data(buf,ID,temp,time) < 0)
printf("deal_data failed:%s\n",strerror(errno));
if(datbas_insert(db,ID,temp,time) < 0)
printf("datbas_insert failed:%s\n",strerror(errno));
if(write(clienfd,finish,sizeof(finish))<0)
printf("write to client failed:%s\n",strerror(errno));
}
close(clienfd);
datbas_close(db);
}
}
return 0;
}
五、测试结果
运行服务器
第一个客户端:[127.0.0.1:54154]
第二个客户端:[127.0.0.1:54156]