龙空技术网

linux fork多进程并发服务器模型之C/C++代码实战

linux技术栈 626

前言:

现时我们对“c多进程并发通信”大约比较着重,你们都想要知道一些“c多进程并发通信”的相关知识。那么小编在网摘上收集了一些有关“c多进程并发通信””的相关文章,希望同学们能喜欢,看官们快快来学习一下吧!

今天我们一起来聊聊多进程实现与多个客户端进行通信。

如果是在while中循环accept, 然后循环处理事情, 此时, 这种服务是迭代服务, 只能逐一处理客户端的请求, 后一个请求必须等前一个请求处理完毕, 无法并发处理, 真是急死人。 要实现并发, 我们可以考虑多线程, 也可以考虑多进程, 本文来说说后者。 在我们的多进程服务器模型中, 我们用父进程来处理连接(监听socket), 用fork子进程的方法来处理通信(通信socket), 各司其职, 美哉。

一旦涉及到fork, 就必须注意僵尸进程的处理, 所以, 我们要用waitpid进行收尸, 这一点, 我们已经说过了。 另外, 要注意, 父子进程共享socket句柄的文件表(如果不理解的话, 建议看看APUE), 所以close socket的时候, 只是使引用计数减1, 并不是真正地直接关闭socket(减为0才是真正的关闭)。

废话少说, 直接上菜。

服务端程序为:

#include <unistd.h>

#include <sys/types.h>

#include <sys/socket.h>

#include <netdb.h>

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <ctype.h>

#include <errno.h>

#include <malloc.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <sys/ioctl.h>

#include <stdarg.h>

#include <fcntl.h>

#include <sys/types.h>

#include <sys/wait.h>

#include <netinet/in.h>

#include <arpa/inet.h>

#include <signal.h>

void sigChildFun(int sigNO)

{

pid_t pid;

int stat;

while((pid = waitpid(-1, &stat, WNOHANG)) > 0) // 循环收尸(僵尸进程), 此时waitpid不会阻塞

{

NULL;

}

return;

}

int main()

{

sockaddr_in servAddr;

memset(&servAddr,0,sizeof(servAddr));

servAddr.sin_family = AF_INET;

servAddr.sin_addr.s_addr = INADDR_ANY;

servAddr.sin_port = htons(8765);

int iListenSocket = socket(AF_INET, SOCK_STREAM, 0);

bind(iListenSocket, (sockaddr *)&servAddr, sizeof(servAddr));

listen(iListenSocket,5);

signal(SIGCHLD, sigChildFun);

while(1)

{

sockaddr_in clientAddr;

socklen_t iSize = sizeof(clientAddr);

memset(&clientAddr, 0, sizeof(clientAddr));

int iConnSocket = accept(iListenSocket,(sockaddr*)&clientAddr, &iSize);

if(iConnSocket < 0)

{

if(errno == EINTR || errno == ECONNABORTED)

{

continue;

}

else

{

printf("accept error, server\n");

return -1;

}

}

int tmpPid = fork();

if(tmpPid == 0)

{

close(iListenSocket); // 子进程让监听socket的计数减1, 并非直接关闭监听socket

char szBuf[1024] = {0};

snprintf(szBuf, sizeof(szBuf), "server pid[%u], client ip[%s]", getpid(), inet_ntoa(clientAddr.sin_addr));

write(iConnSocket, szBuf, strlen(szBuf) + 1);

while(1)

{

if(read(iConnSocket, szBuf, 1) <= 0)

{

close(iConnSocket); // 子进程让通信的socket计数减1

return -2; // 子进程退出

}

}

close(iConnSocket); // 子进程让通信的socket计数减1

return 0; // 子进程退出

}

close(iConnSocket); // 父进程让通信的socket计数减1

}

getchar();

close(iListenSocket); // 父进程让监听socket计数减1, 此时会关掉监听socket(因为之前子进程已经有此操作)

return 0;

}

启动它。

客户端程序为:

我们开启一个客户端, 此时如下:

客户端信息为:


服务端信息为:


可以看到, 服务端16402子进程是与客户端通信的进程, 父进程16096是监听的父进程(主进程)。

另外再开启一个客户端(不要关闭旧的客户端), 此时如下:

新客户端信息为:


服务端信息为:


可以看到, 父进程16096新开了一个子进程16497来与新的客户端进行通信。

我们关闭第一个客户端, 然后看到服务端为:


我们再关闭第二个客户端, 然后看到服务端为:


显然, 客户端退出后, 发FIN包, 服务端子进程的recv函数就为0, 退出子进程的while循环了, 因此, 对应的子进程就over了, 而且不会留下僵尸进程(有waitpid)。 而且, 我们可以看到, 负责连接管理(accept)的父进程(主进程)依然安然无恙, 优哉游哉地等待下一个客户端连接。

在这里, 我们可以看到, 这个服务器是并发的, 而不是迭代的。 什么意思呢? 你看, 即使子进程处理业务需要很久很久, 那么上述服务依然能并发地响应n个几乎同时到达的客户端, 此时,父进程开启n个子进程, 并发地工作, 并发地与客户端进行通信, 而且还互不干扰, 大大提升了服务满意度。

注:需要C/C++ Linux服务器开发学习资料私信“资料”(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

标签: #c多进程并发通信