多进程学习随笔
一、概述:
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