多进程学习随笔
Linux下一个进程在内存里有三部分的数据: “代码段”、”堆栈段”和”数据段”。 ”代码段”,顾名思义,就是存放了程序代码。 “堆栈段”存放的就是程序的返回地址、程序的参数以及程序的局部变量。 “数据段”则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用new函数分配的空间)。
系统如果同时运行多个相同的程序,它们的“代码段”是相同的,“堆栈段”和“数据段”是不同的(相同的程序,处理的数据不同)。
使用fork函数创建子进程:共用代码段 复制程序的堆栈段与数据段 父子程序独立运行 互不冲突 父进程返回子进程 进程号 子进程返回零 getpid获取当前程序进程号
1.socket通信
/* * 程序名:book250.cpp,此程序用于演示多进程的socket通信服务端。 * 作者:C语言技术网(www.freecplus.net) 日期:20190525 */ #include <stdio.h> #include <string.h> #include <unistd.h> #include <netdb.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> class CTcpServer { public: int m_listenfd; // 服务端用于监听的socket int m_clientfd; // 客户端连上来的socket CTcpServer(); bool InitServer(int port); // 初始化服务端 bool Accept(); // 等待客户端的连接 // 向对端发送报文 int Send(const void *buf,const int buflen); // 接收对端的报文 int Recv(void *buf,const int buflen); void CloseClient(); // 关闭客户端的socket void CloseListen(); // 关闭用于监听的socket ~CTcpServer(); }; CTcpServer TcpServer; int main() { // signal(SIGCHLD,SIG_IGN); // 忽略子进程退出的信号,避免产生僵尸进程 if (TcpServer.InitServer(5051)==false) { printf("服务端初始化失败,程序退出。\n"); return -1; } while (1) { if (TcpServer.Accept() == false) continue; if (fork()>0) { TcpServer.CloseClient(); continue; } // 父进程回到while,继续Accept。 // 子进程负责与客户端进行通信,直到客户端断开连接。 TcpServer.CloseListen(); printf("客户端已连接。\n"); // 与客户端通信,接收客户端发过来的报文后,回复ok。 char strbuffer[1024]; while (1) { memset(strbuffer,0,sizeof(strbuffer)); if (TcpServer.Recv(strbuffer,sizeof(strbuffer))<=0) break; printf("接收:%s\n",strbuffer); strcpy(strbuffer,"ok"); if (TcpServer.Send(strbuffer,strlen(strbuffer))<=0) break; printf("发送:%s\n",strbuffer); } printf("客户端已断开连接。\n"); return 0; // 或者exit(0),子进程退出。 } } CTcpServer::CTcpServer() { // 构造函数初始化socket m_listenfd=m_clientfd=0; } CTcpServer::~CTcpServer() { if (m_listenfd!=0) close(m_listenfd); // 析构函数关闭socket if (m_clientfd!=0) close(m_clientfd); // 析构函数关闭socket } // 初始化服务端的socket,port为通信端口 bool CTcpServer::InitServer(int port) { if (m_listenfd!=0) { close(m_listenfd); m_listenfd=0; } m_listenfd = socket(AF_INET,SOCK_STREAM,0); // 创建服务端的socket // 把服务端用于通信的地址和端口绑定到socket上 struct sockaddr_in servaddr; // 服务端地址信息的数据结构 memset(&servaddr,0,sizeof(servaddr)); servaddr.sin_family = AF_INET; // 协议族,在socket编程中只能是AF_INET servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 本主机的任意ip地址 servaddr.sin_port = htons(port); // 绑定通信端口 if (bind(m_listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) != 0 ) { close(m_listenfd); m_listenfd=0; return false; } // 把socket设置为监听模式 if (listen(m_listenfd,5) != 0 ) { close(m_listenfd); m_listenfd=0; return false; } return true; } bool CTcpServer::Accept() { if ( (m_clientfd=accept(m_listenfd,0,0)) <= 0) return false; return true; } int CTcpServer::Send(const void *buf,const int buflen) { return send(m_clientfd,buf,buflen,0); } int CTcpServer::Recv(void *buf,const int buflen) { return recv(m_clientfd,buf,buflen,0); } void CTcpServer::CloseClient() // 关闭客户端的socket { if (m_clientfd!=0) { close(m_clientfd); m_clientfd=0; } } void CTcpServer::CloseListen() // 关闭用于监听的socket { if (m_listenfd!=0) { close(m_listenfd); m_listenfd=0; } }主进程调用fork函数时 创建子进程 父进程主要负责监听 子进程主要负责通信 两者互不干扰 关闭父进程的connected socket 子进程的 master socket 不影响彼此 2.文件读写
三、僵尸进程 产生:子进程return 或 exit 父进程未回收 子进程结束未被销毁留下僵尸进程 影响:带有标志。继续占用系统资源 消除: 1.子进程退出之前,会向父进程发送一个信号,父进程调用waid函数等待这个信号,只要等到了,就不会产生僵尸进程。 2.signal(SIGCHLD,SIG_IGN); // 忽略子进程退出的信号,避免产生僵尸进程
三、进程交互 1)数据传输:一个进程需要将它的数据发送给另一个进程。 2)共享数据:多个进程想要操作共享数据,一个进程对共享数据的修改,别的进程应该立刻看到。 3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如通知进程退出)。 4)进程控制:一个进程希望控制另一个进程的运行。
进程通信的方式大概分为六种。
1)管道:包括无名管道(pipe)及命名管道(named pipe),无名管道可用于具有父进程和子进程之间的通信。命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。
2)消息队列(message):进程可以向队列中添加消息,其它的进程则可以读取队列中的消息。
3)信号(signal):信号用于通知其它进程有某种事件发生。 4)共享内存(shared memory):多个进程可以访问同一块内存空间。 5)信号量(semaphore):也叫信号灯,用于进程之间对共享资源进行加锁。 6)套接字(socket):可用于不同计算机之间的进程间通信。 参考资料:C语言技术网https://freecplus.net/8bd691add361411d84745282afa7e4fe.html https://freecplus.net/d95f4eaf18eb46d19b82383519126dec.html