所在的位置: C++ >> C++介绍 >> CC服务器并发

CC服务器并发

1.单线程/进程

在TCP通信过程中,服务器端启动之后可以同时和多个客户端建立连接,并进行网络通信,但是在介绍TCP通信流程的时候,提供的服务器代码却不能完成这样的需求,先简单的看一下之前的服务器代码的处理思路,再来分析代码中的弊端:

//server.c#includestdio.h#includestdlib.h#includeunistd.h#includestring.h#includearpa/inet.hintmain(){//1.创建监听的套接字intlfd=socket(AF_INET,SOCK_STREAM,0);//2.将socket()返回值和本地的IP端口绑定到一起structsockaddr_inaddr;addr.sin_family=AF_INET;addr.sin_port=htons();//大端端口//INADDR_ANY代表本机的所有IP,假设有三个网卡就有三个IP地址//这个宏可以代表任意一个IP地址addr.sin_addr.s_addr=INADDR_ANY;//这个宏的值为0==0.0.0.0intret=bind(lfd,(structsockaddr*)addr,sizeof(addr));//3.设置监听ret=listen(lfd,);//4.阻塞等待并接受客户端连接structsockaddr_incliaddr;intclilen=sizeof(cliaddr);intcfd=accept(lfd,(structsockaddr*)cliaddr,clilen);//5.和客户端通信while(1){//接收数据charbuf[];memset(buf,0,sizeof(buf));intlen=read(cfd,buf,sizeof(buf));if(len0){printf("客户端say:%s\n",buf);write(cfd,buf,len);}elseif(len==0){printf("客户端断开了连接...\n");break;}else{perror("read");break;}}close(cfd);close(lfd);return0;}

在上面的代码中用到了三个会引起程序阻塞的函数,分别是:

accept():如果服务器端没有新客户端连接,阻塞当前进程/线程,如果检测到新连接解除阻塞,建立连接read():如果通信的套接字对应的读缓冲区没有数据,阻塞当前进程/线程,检测到数据解除阻塞,接收数据write():如果通信的套接字写缓冲区被写满了,阻塞当前进程/线程(这种情况比较少见)

如果需要和发起新的连接请求的客户端建立连接,那么就必须在服务器端通过一个循环调用accept()函数,另外已经和服务器建立连接的客户端需要和服务器通信,发送数据时的阻塞可以忽略,当接收不到数据时程序也会被阻塞,这时候就会非常矛盾,被accept()阻塞就无法通信,被read()阻塞就无法和客户端建立新连接。因此得出一个结论,基于上述处理方式,在单线程/单进程场景下,服务器是无法处理多连接的,解决方案也有很多,常用的有四种:

使用多线程实现使用多进程实现使用IO多路转接(复用)实现使用IO多路转接+多线程实现2.多进程并发

如果要编写多进程版的并发服务器程序,首先要考虑,创建出的多个进程都是什么角色,这样就可以在程序中对号入座了。在Tcp服务器端一共有两个角色,分别是:监听和通信,监听是一个持续的动作,如果有新连接就建立连接,如果没有新连接就阻塞。关于通信是需要和多个客户端同时进行的,因此需要多个进程,这样才能达到互不影响的效果。进程也有两大类:父进程和子进程,通过分析我们可以这样分配进程:

父进程:负责监听,处理客户端的连接请求,也就是在父进程中循环调用accept()函数创建子进程:建立一个新的连接,就创建一个新的子进程,让这个子进程和对应的客户端通信回收子进程资源:子进程退出回收其内核PCB资源,防止出现僵尸进程子进程:负责通信,基于父进程建立新连接之后得到的文件描述符,和对应的客户端完成数据的接收和发送。发送数据:send()/write()接收数据:recv()/read()

在多进程版的服务器端程序中,多个进程是有血缘关系,对应有血缘关系的进程来说,还需要想明白他们有哪些资源是可以被继承的,哪些资源是独占的,以及一些其他细节:

子进程是父进程的拷贝,在子进程的内核区PCB中,文件描述符也是可以被拷贝的,因此在父进程可以使用的文件描述符在子进程中也有一份,并且可以使用它们做和父进程一样的事情。父子进程有用各自的独立的虚拟地址空间,因此所有的资源都是独占的为了节省系统资源,对于只有在父进程才能用到的资源,可以在子进程中将其释放掉,父进程亦如此。由于需要在父进程中做accept()操作,并且要释放子进程资源,如果想要更高效一下可以使用信号的方式处理

多进程版并发TCP服务器示例代码如下:

#includestdio.h#includestdlib.h#includeunistd.h#includestring.h#includearpa/inet.h#includesignal.h#includesys/wait.h#includeerrno.h//信号处理函数voidcallback(intnum){while(1){pid_tpid=waitpid(-1,NULL,WNOHANG);if(pid=0){printf("子进程正在运行,或者子进程被回收完毕了\n");break;}printf("childdie,pid=%d\n",pid);}}intchildWork(intcfd);intmain(){//1.创建监听的套接字intlfd=socket(AF_INET,SOCK_STREAM,0);if(lfd==-1){perror("socket");exit(0);}//2.将socket()返回值和本地的IP端口绑定到一起structsockaddr_inaddr;addr.sin_family=AF_INET;addr.sin_port=htons();//大端端口//INADDR_ANY代表本机的所有IP,假设有三个网卡就有三个IP地址//这个宏可以代表任意一个IP地址//这个宏一般用于本地的绑定操作addr.sin_addr.s_addr=INADDR_ANY;//这个宏的值为0==0.0.0.0//inet_pton(AF_INET,"...",addr.sin_addr.s_addr);intret=bind(lfd,(structsockaddr*)addr,sizeof(addr));if(ret==-1){perror("bind");exit(0);}//3.设置监听ret=listen(lfd,);if(ret==-1){perror("listen");exit(0);}//注册信号的捕捉structsigactionact;act.sa_flags=0;act.sa_handler=callback;sigemptyset(act.sa_mask);sigaction(SIGCHLD,act,NULL);//接受多个客户端连接,对需要循环调用acceptwhile(1){//4.阻塞等待并接受客户端连接structsockaddr_incliaddr;intclilen=sizeof(cliaddr);intcfd=accept(lfd,(structsockaddr*)cliaddr,clilen);if(cfd==-1){if(errno==EINTR){//accept调用被信号中断了,解除阻塞,返回了-1//重新调用一次acceptcontinue;}perror("accept");exit(0);}//打印客户端的地址信息charip[24]={0};printf("客户端的IP


转载请注明:http://www.aierlanlan.com/rzfs/143.html