在上文中,提供了一个十分简单的回射服务器的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=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连接服务器,发现服务器能为多个客户端服务啦。代码中存在的缺点如读写逻辑差、未进行子进程回收等内容,将在下一个版本中改掉。而且,多进程一般是用进程池,后面的版本也会实现。敬请期待。