在上文中,提供了一个十分简单的回射服务器的demo,但是,这个demo的作用十分有限,只能接收一个客户端的连接,然后和其建立联系,进行通讯。等这个客户端断开连接后,才能处理另外的连接。功能太弱了。因为我们之前学过多进程程序设计,所以,这里提供一个回射服务器的多进程版本,使之能够同时处理多个客户端的连接。
多进程主要是靠fork函数来完成的,他是一个特殊函数,一次调用,两次返回。如果返回大于0,表示是父进程,同时返回值表示了他的子进程的pid。返回值如果等于0,表示他是一个子进程。子进程是父进程的一个拷贝,保留了父进程的所有东西。但是,我们往往需要让子进程执行别的函数。这个函数,你可以在程序里写,然后在子进程调用;也可以是已经存在的文件,你可以用exec族的函数,对子进程的内容进行复写。这部分具体怎么处理,完全看你的业务需求。在本例中,就是对已经接受的连接,进行读写操作。
在返回的父进程中,往往需要继续处理程序的主框架的内容。而且,需要对子进程的资源进行回收,即等待子进程执行完成,用wait和waitpid函数回收子进程,避免其成为僵尸进程而占用资源。wait可以返回子进程的退出状态,waipid可以非阻塞的处理。是wait的加强版。
下面是这个版本的回射服务器的demo代码,贴一下。注意,因为代码没有重构,看起来比较乱。尤其是读写函数,目前还处于一个零散的状态,没有抽出来。此外,没有进行wait,否则容易卡住,这个接下来会提供解决的方法。
#include"head.h"
#define MAX_USER 1024
#define BUF_SIZE 1024
typedef struct user //yon {
int conn; //表示客户与服务器的连接
sockaddr_in client_addr; //用户的地址
char bufread[BUF_SIZE]; // 用户的读缓存
char bufwrite[BUF_SIZE]; //用户的写缓存
} user;
int main(int argc, char **argv)
{
if(argc<3)
{
printf("usage: %s ip port\n",basename(argv[0]));
exit(0);
}
printf("start echoback server...\n");
char * ip=(char * ) argv[1];
int port =atoi(argv[2]);
/*构造服务器端的地址,主要是填充sockaddr_in 结构体*/
struct sockaddr_in server_address;
bzero(&server_address,sizeof(server_address));
server_address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&server_address.sin_addr);
server_address.sin_port=htons(port);
int listenfd=socket(AF_INET,SOCK_STREAM,0);
assert(listenfd>=0);
int reuse=1;
setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
setnonblock(listenfd);
int ret=bind(listenfd,(struct sockaddr *) &server_address, sizeof(server_address));
assert(ret==0);
ret=listen(listenfd,5);
assert(ret==0);
/*服务器服务启动,等待客户端的链接的到来*/
int run_flag=1;
char buf[1024];
int conn=-1;
user users[MAX_USER];
int stop[MAX_USER];
for(int i=0;i
stop[i]=0;
int user_number=0;
while(run_flag)
{
int acfd=accept(listenfd,NULL,NULL);
if(acfd>=0)
{
printf("acfd>0,hhahahaha");
user_number++;
/*users[user_number] 表示一个conn,唯一标识这个用户链接*/
users[user_number].conn=acfd;
pid_t pid=fork();
if(pid<0)
{
printf("fork error\n");
exit(0);
}
else if (pid==0) /*这里进入子进程*/
{
close(listenfd); //关闭无用的监听套接字,由父进程继续监听
setnonblock(users[user_number].conn);
//while(!stop[ users[user_number].conn] )
while(1)
{
int readn=read(users[user_number].conn,users[user_number].bufread,BUF_SIZE);
// printf("readn=%d\n",readn);
//printf("errno=%d\n",errno);
if( (readn<0) && (errno!=EAGAIN))
{
printf("a\n");
printf("read fail, client %d\n",users[user_number].conn);
close(users[user_number].conn);
//stop[ users[user_number].conn ]=1;
//break;
exit(0);
}
else if (readn==0)
{
printf("b\n");
close(users[user_number].conn);
//stop[ users[user_number].conn ]=1;
//break;
exit(0);
} else if (readn>0)
{
printf("pid= %d\n",getpid());
users[user_number].bufread[readn]='\0';
memcpy(users[user_number].bufwrite,users[user_number].bufread,sizeof(users[user_number].bufread));
int writen=write(users[user_number].conn,users[user_number].bufwrite,sizeof(users[user_number].bufwrite));
memset(users[user_number].bufread,0,sizeof(users[user_number].bufread));
printf("write %d bytes to %d\n",writen,users[user_number].conn);
}
}
}
/*parent process*/
else
{
close(users[user_number].conn);
int stat_loc;
/*如果想要多个进程共同服务的话,就不能在这里阻塞的等待,可以通过sigchld信号进行捕获*/
// wait((int *)&stat_loc);
printf("parent, finished wait\n");
}
}
}
}
这段代码,运行起来后,可以用多个telnet连接服务器,发现服务器能为多个客户端服务啦。代码中存在的缺点如读写逻辑差、未进行子进程回收等内容,将在下一个版本中改掉。而且,多进程一般是用进程池,后面的版本也会实现。敬请期待。