文章目录

  • 一、并发的服务端
    • 1、服务端
    • 2、客户端
  • 二、僵尸进程
    • 1、僵尸进程产生的原因
    • 2、僵尸进程的危害
    • 3、如何解决僵尸进程
  • 三、应用经验
  • 四、课后作业
  • 五、版权声明

前面的章节介绍socket通信的时候,socket的服务端在同一时间只能和一个客户端通信,并不是服务端有多忙,而是因为单进程的程序在同一时间只能做一件事情,不可能一边等待客户端的新连接一边与其它的客户端进行通信。

一、并发的服务端

如果把socket服务端改为多进程,在每次accept到一个客户端的连接后,生成一个子进程,让子进程负责和这个客户端通信,父进程继续accept客户端的连接,socket的服务端在监听新客户端的同时,还可以与多个客户端进行通信。这就是并发,如下图:

1、服务端

把book248.cpp修改一下,改为多进程。

示例(book250.cpp)

/** 程序名: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;   // 服务端用于监听的socketint m_clientfd;   // 客户端连上来的socketCTcpServer();bool InitServer(int port);  // 初始化服务端bool Accept();  // 等待客户端的连接// 向对端发送报文int  Send(const void *buf,const int buflen);// 接收对端的报文int  Recv(void *buf,const int buflen);void CloseClient();    // 关闭客户端的socketvoid 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()
{// 构造函数初始化socketm_listenfd=m_clientfd=0;
}CTcpServer::~CTcpServer()
{if (m_listenfd!=0) close(m_listenfd);  // 析构函数关闭socketif (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_INETservaddr.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; }
}

解释一下:

1)在CTcpServer中增加了两个成员函数。

  void CloseClient();    // 关闭客户端的socketvoid CloseListen();    // 关闭用于监听的socket

2)当有客户端连上来的时候,主进程执行fork,这时候会客户端的socket(m_clientfd)被复制了一份,对父进程来说,只负责监听客户端的连接,不需要与客户端通信,所以父进程关闭m_clientfd,注意,父进程关闭m_clientfd对子进程中的m_clientfd没有影响。

3)当有客户端连上来的时候,主进程执行fork,这时候服务端用于监听的socket(m_listenfd)也会被复制了一份,对子进程来说,只需要与客户端通信,不需要监听客户端的连接,所以子进程关闭监听的m_listenfd,同理,子进程关闭m_listenfd对父进程中的m_listenfd没有影响。

4)子进程执行完任务后,要调用retrun或exit(0)退出,如果没有调用return或exit(0),子进程将又会回到while循环首部。

2、客户端

把book247.cpp修改一下,循环的次数改为50,每次与服务端完成报文交互后sleep一秒,方便观察程序运行的效果。

示例(book249.cpp)

/** 程序名:book249.cpp,此程序对book247.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>// TCP客户端类
class CTcpClient
{public:int m_sockfd;CTcpClient();// 向服务器发起连接,serverip-服务端ip,port通信端口bool ConnectToServer(const char *serverip,const int port);// 向对端发送报文int  Send(const void *buf,const int buflen);// 接收对端的报文int  Recv(void *buf,const int buflen);~CTcpClient();
};int main()
{CTcpClient TcpClient;// 向服务器发起连接请求if (TcpClient.ConnectToServer("172.16.0.15",5051)==false){ printf("TcpClient.ConnectToServer(\"172.16.0.15\",5051) failed,exit...\n"); return -1; }char strbuffer[1024];for (int ii=0;ii<50;ii++){memset(strbuffer,0,sizeof(strbuffer));sprintf(strbuffer,"这是第%d个超级女生,编号%03d。",ii+1,ii+1);if (TcpClient.Send(strbuffer,strlen(strbuffer))<=0) break;printf("发送:%s\n",strbuffer);memset(strbuffer,0,sizeof(strbuffer));if (TcpClient.Recv(strbuffer,sizeof(strbuffer))<=0) break;printf("接收:%s\n",strbuffer);sleep(1);  // sleep一秒,方便观察程序的运行。}
}CTcpClient::CTcpClient()
{m_sockfd=0;  // 构造函数初始化m_sockfd
}CTcpClient::~CTcpClient()
{if (m_sockfd!=0) close(m_sockfd);  // 析构函数关闭m_sockfd
}// 向服务器发起连接,serverip-服务端ip,port通信端口
bool CTcpClient::ConnectToServer(const char *serverip,const int port)
{m_sockfd = socket(AF_INET,SOCK_STREAM,0); // 创建客户端的socketstruct hostent* h; // ip地址信息的数据结构if ( (h=gethostbyname(serverip))==0 ) { close(m_sockfd); m_sockfd=0; return false; }// 把服务器的地址和端口转换为数据结构struct sockaddr_in servaddr;memset(&servaddr,0,sizeof(servaddr));servaddr.sin_family = AF_INET;servaddr.sin_port = htons(port); memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);// 向服务器发起连接请求if (connect(m_sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))!=0){ close(m_sockfd); m_sockfd=0; return false; }return true;
}int CTcpClient::Send(const void *buf,const int buflen)
{return send(m_sockfd,buf,buflen,0);
}int CTcpClient::Recv(void *buf,const int buflen)
{return recv(m_sockfd,buf,buflen,0);
}

先启动服务端book250,然后启动多个book249,可以看到服务端可以同时与多个客户端进行通信,查看服务端的进行如下:


注意,服务端book250的主程序的while是一个死循环,没有退出机制,可以按Ctrl+c强制中止它,这不是正确的办法,后面我会介绍正确的方法。

二、僵尸进程

1、僵尸进程产生的原因

一个子进程在调用return或exit(0)结束自己的生命的时候,其实它并没有真正的被销毁,而是留下一个僵尸进程。

先启动服务端程序book250,然后多次启动客户端程序book249,马上查看book250的进程,如下图:

等全部的客户端book249程序运行完成后,再查看book250的进程,如下图。

被选中的就是僵尸进程,有<defunct>标志。

如果按Ctrl+c终止book250后,父进程退出,僵尸进程随之消失。

2、僵尸进程的危害

僵尸进程是子进程结束时,父进程又没有回收子进程占用的资源。

僵尸进程在消失之前会继续占用系统资源。

如果父进程先退出,子进程被系统接管,子进程退出后系统会回收其占用的相关资源,不会成为僵尸进程。父进和先退出的应用场景在以后的章节中介绍。

3、如何解决僵尸进程

解决僵尸进程的方法有两种。

子进程退出之前,会向父进程发送一个信号,父进程调用waid函数等待这个信号,只要等到了,就不会产生僵尸进程。这话说得容易,在并发的服务程序中这是不可能的,因为父进程要做其它的事,例如等待客户端的新连接,不可能去等待子进程的退出信号,这个方法我就不介绍了。

另一种方法就是父进程直接忽略子进程的退出信号,具体做法很简单,在主程序中启用以下代码:

signal(SIGCHLD,SIG_IGN);  // 忽略子进程退出的信号,避免产生僵尸进程

signal函数的用法暂时不介绍,以后会有详细说明。

先启动服务端程序book250,然后多次启动客户端程序book249,等book249运行结束后再查看book250的进程,不再有僵尸进程。

三、应用经验

在学习了多进程的基础知识之后,初学者可能会认为多进程是一个高大上的技术,认为多进程处理数据肯定比单进程快,其实不是。在实际开发中,采用多进程的主要目的是处理多个并发的任务,而不是为了提高程序的效率。

从效率方面来说,某些场景下多进程的效率比单进程低,原因很简单,因为在有限的硬件资源中,多进程程序的内存开销更大,还会产生资源的竞争。就像多个人端着一盆水,不如一个人端着一盆水走得快。

四、课后作业

本章节的重点是介绍多进程的应用场景,属于概念性的知识,代码其实很简单,只要各位理解了多进程应用的原理就行了。

但是,文章中提到的知识点,大家一定要用程序去测试它。

五、版权声明

C语言技术网原创文章,转载请说明文章的来源、作者和原文的链接。
来源:C语言技术网(www.freecplus.net)
作者:码农有道

如果文章有错别字,或者内容有错误,或其他的建议和意见,请您留言指正,非常感谢!!!

Linux多进程的应用相关推荐

  1. Linux多线程与Linux多进程混合项目的死锁问题

    目录 背景 线程和fork 内核原理分析 背景 本文并不是介绍Linux多进程多线程编程的科普文,如果希望系统学习Linux编程,可以看<Unix环境高级编程>第3版>. 本文是描述 ...

  2. Linux多进程编程之在线词典

    在线词典是基于Linux 多进程并发服务器编程,由服务器端和客户端构成,客户端可以运行在多个不同的主机上连接服务器,服务器对员工信息的操作结果通过数据库sqlite来保存.当用户登录后,根据用户名判断 ...

  3. Linux 高并发服务器实战 - 2 Linux多进程开发

    Linux 高并发服务器实战 - 2 Linux多进程开发 进程概述 概念1: 概念2: 微观而言,单CPU任意时刻只能运行一个程序 并发:两个队列交替使用一台咖啡机 并行:两个队列同时使用两台咖啡机 ...

  4. Linux 多进程(一)

    Linux 多进程 进程 进程理论相关内容直接看教材就好现代操作系统等 shell运行程序的过程: 用户键入命令->shell建立一个新进程来运行此程序->shell将程序从磁盘载入-&g ...

  5. Linux多进程多线程编程笔记

    文章目录 Linux多进程多线程编程 一.多进程编程 1.fork 函数 2.exec 系列函数 3.wait.waitpid 函数 4.pipe 管道 5.信号量 5.1.semget 5.2.se ...

  6. Linux多进程4种策略实现哲学家进餐问题

    1. 任务简介 一张圆桌上坐着5名哲学家,每两个哲学家之间的桌上摆一根筷子,桌子的中间是一碗米饭.哲学家们倾注毕生的精力用于思考和进餐,哲学家在思考时,并不影响他人.只有当哲学家饥饿时,才试图拿起左. ...

  7. HTTP服务器项目2:Linux多进程开发

    HTTP服务器项目2:Linux多进程开发 1.进程概述: 01 / 程序和进程 程序是包含一系列信息的文件,这些信息描述了如何在运行时创建一个进程: ◼ 二进制格式标识:每个程序文件都包含用于描述可 ...

  8. Linux 多进程开发

    Linux多进程开发 文章目录 进程概述 程序与进程 单道.多道程序设计 时间片 并行和并发 进程状态转换 进程的状态 进程相关命令 进程号和相关函数 进程创建 父子进程的虚拟地址空间 父子进程之间的 ...

  9. Linux -- 多进程编程之 - 守护进程

    内容概要 一.守护进程概述 二.守护进程创建 2.1.创建子进程,父进程退出 2.2.在子进程中创建新会话 2.2.1.进程组和会话期 2.2.2.setsid()函数说明 2.3.改变当前工作目录 ...

  10. Linux多进程实现生产者消费者问题

    1. 任务简介 生产者消费者问题(Producer-consumer problem),也称有限缓冲问题(Bounded-buffer problem),是一个著名的进程同步问题的经典案例.它描述的是 ...

最新文章

  1. 新会计科目的编号及内容
  2. oracle对比两列数据_oracle与mysql对同一张表 两列数据的某一字段进行值的交换
  3. 【已解决】Linux下安装MySQL数据库
  4. 对象的释放Dispose和Close对比
  5. 小波变换和motion信号处理(二)【转载】
  6. SS不能在Win7中打开,出现停止运行
  7. 《火球——UML大战需求分析》(第3章 分析业务模型-类图)——3.7 关于对象图
  8. 【转】uni-app在手持PDA上的激光扫码完美解决方案
  9. java计算机毕业设计网上书店管理系统源码+系统+数据库+lw文档+mybatis+运行部署
  10. vbs画动态爱心代码_求助,求一个vbs画心形的代码(程序小白的求助)
  11. actived生命周期_初探 Vue 生命周期和钩子函数
  12. html水平导航栏代码连接状态,水平导航栏1.html
  13. ROCKET 数据可视化可以如此简单
  14. matlab y 0,用MATLAB算y-2y-3y=0的解
  15. Android弹性滑动在自定义View中的高级应用
  16. 华为ensp ospf综合实验
  17. 【有趣的Python小程序】Pygame制作键盘彩色闪烁打字游戏KeyBoardFlash
  18. 案例:京东登录页面css创建
  19. Linux找回mysql的root密码
  20. C++中endl、“\n”和‘\n’的区别

热门文章

  1. 详解Python中的算术乘法、数组乘法与矩阵乘法
  2. Python通过WMI读取主板BIOS信息
  3. criteria 排序_产品需求挖掘与排序的2大利器:文本挖掘与KANO模型
  4. java epoch times_Java 8新特性探究(七)深入解析日期和时间
  5. 没有为 ucrtbase.dll 加载符号_深入理解Java虚拟机(类加载机制)
  6. php安全测试工具,免费的高级Web应用程序安全测试工具
  7. 不同项目的测试计划可以复用吗_不同品牌的水乳可以一起用吗 混合使用完全没问题...
  8. Linux socket can例程C++版本
  9. java让弹窗在最上层_layer弹出层显示在top顶层的方法
  10. 回填用土好还是砂石料好_养猪用颗粒料好还是自配料好?其实各有优劣,养猪人要会选择...