服务器按处理方式可以分为迭代服务器和并发服务器两类。上一节介绍了网络socket服务器的编程,但是服务器每次只能处理一个客户的请求,它实现简单但效率很低,通常这种服务器被称为迭代服务器。 然而在实际应用中,不可能让一个服务器长时间地为一个客户服务,而需要其具有同时处理 多个客户请求的能力,这种同时可以处理多个客户请求的服务器称为并发服务器,其效率很 高却实现复杂。在实际应用中,并发服务器应用的最广泛。linux有3种实现并发服务器的方式:多进程并发服务器,多线程并发服务器,IO复用,先来看多进程并发服务器的实现。
什么是一个进程?在操作系统原理使用这样的术语来描述的:正在运行的程序及其占用的资源(CPU、内存、系统资源等)叫做进程。站在程序员的角度来看,我们使用vim编辑生成的C文件叫做源码,源码给程序员来看的但机器不识别,这时我们需要使用编译器gcc编译生成CPU可识别的二进制可执行程序并保存在存储介质上,这时编译生成的可执行程序只能叫做程序而不能叫进程。而一旦我们通过命令(./a.out)开始运行时,那正在运行的这个程序及其占用的资源就叫做进程了。进程这个概念是针对系统而不是针对用户的,对用户来说,他面对的概念是程序。很显然,一个程序可以执行多次,这也意味着多个进程可以执行同一个程序。
Linux内核在启动的最后阶段会创建init进程来执行程序/sbin/init,该进程是系统运行的第一个进程,进程号为 1,称为Linux 系统的初始化进程,该进程会创建其他子进程来启动不同写系统服务,而每个服务又可能创建不同的子进程来执行不同的程序。Linux下有两个基本的系统调用可以用于创建子进程:fork()和vfork()。
pid_t fork(void);
fork在英文中是"分叉"的意思。为什么取这个名字呢?因为一个进程在运行中,如果使用了fork,就产生了另一个进程,于是进程就”分叉”了,所以这个名字取得很形象。在我们编程的过程中,一个函数调用只有一次返回(return),但由于fork()系统调用会创建一个新的进程,这时它会有两次返回。一次返回是给父进程,其返回值是子进程的PID(Process ID),第二次返回是给子进程,其返回值为0。所以我们在调用fork()后,需要通过其返回值来判断当前的代码是在父进程还是子进程运行,如果返回值是0说明现在是子进程在运行,如果返回值>0说明是父进
程在运行,而如果返回值<0的话,说明fork()系统调用出错。fork 函数调用失败的原因主要有两个:
1.系统中已经有太多的进 程;
2.该实际用户 ID 的进程总数超过了系统限制。
每个子进程只有一个父进程,并且每个进程都可以通过getpid()获取自己的进程PID,也可以通过getppid()获取父进程的PID,这样在fork()时返回0给子进程是可取的。一个进程可以创建多个子进程,这样对于父进程而言,他并没有一个API函数可以获取其子进程的进程ID,所以父进程在通过fork()创建子进程的时候,必须通过返回值的形式告诉父进程其创建的子进程PID。这也是fork()系统调用两次返回值设计的原因。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MSG_NUM 1024
void print_usage(char *progname)
{
printf("%s usage: \n", progname);
printf("-p(--LISTEN_PORT): sepcify server port.\n");
printf("-h(--Help): print this help information.\n");
return ;
}
int main(int argc,char **argv)
{
int sockfd = -1;
int clifd =-1;
int rv =-1;
int LISTEN_PORT = 0;
int rw;
int on =1;
char buf[MSG_NUM];
struct sockaddr_in servaddr;
struct sockaddr_in cliaddr;
pid_t pid;
socklen_t cli_len=sizeof(struct sockaddr);
struct option longopts[] = {
{
"help", no_argument, NULL, 'h'},
{
"LISTEN_PORT", required_argument, NULL, 'P'},
{
0, 0, 0, 0}
};
while( (rw=getopt_long(argc, argv, "p:h", longopts, NULL)) != -1 )
{
switch(rw)
{
case 'p':
LISTEN_PORT=atoi(optarg);
break;
case 'h':
print_usage(argv[0]);
return 0;
}
}
if( !LISTEN_PORT )
{
print_usage(argv[0]);
return 0;
}
sockfd=socket(AF_INET, SOCK_STREAM,0);
if(sockfd<0)
{
printf("Create socket failure:%s\n",strerror(errno));
return 0;
}
printf("Create socket[%d] successfully!\n",sockfd);
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port = htons(LISTEN_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))<0)
{
printf("Bind socket failure:%s\n ",strerror(errno));
return -1;
}
printf("socket[%d] bind on port[%d] for all IP address ok",sockfd,LISTEN_PORT);
listen(sockfd,13);
printf("Start to listen on port [%d]\n", LISTEN_PORT);
while(1)
{
printf("Start accept new client incoming...\n");
printf("Value of cli_len:%d",cli_len);
clifd=accept(sockfd,(struct sockaddr*)&cliaddr,&cli_len);
if(clifd<0)
{
printf("Accept new client failure: %s\n", strerror(errno));
return -2;
}
printf("Accept new client[%s:%d] with fd [%d]\n", inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port), clifd);
pid = fork();
if (pid<0)
{
printf("fork error:%s",strerror(errno));
close(clifd);
continue;
}
else if (pid>0)
{
close(clifd);
continue;
}
else if (pid==0)
{
printf("Child process PID[%d] running...\n",getpid());
close(sockfd);
memset(buf,0,sizeof(buf));
rv=read(clifd,buf,sizeof(buf));
if(rv<0)
{
printf("Read data from client[%d] failure:%s\n",clifd,strerror(errno));
close(clifd);
return -3;
}
if (rv==0)
{
printf("The client[%d] has disconnected!",clifd);
return -4;
}
printf("Read %d bytes data from socket[%d]:%s\n",rv,clifd,buf);
if((write(clifd,buf,rv))<0)
{
printf("write to client[%d] failure:%s\n",clifd,strerror(errno));
close(clifd);
return -5;
}
}
printf("close client socket[%d] and child process exit\n", clifd);
close(clifd);
exit(0);
}
close(sockfd);
}