利用之前学习到的内容,我们的服务器可以按照顺序处理多个客户端的服务请求。在客户端和服务时间增长的情况下,服务器就不足以满足需求了。
(1)普通服务器:当有100个客户端连接请求到来时,假设每个请求的受理时间为1s,那么第50个请求需要等待50s,第100个请求需要等待100s
(2)并发服务器:所有客户端的连接请求受理时间都不超过1s,单平均服务时间2-3s。
很明显并发服务器处理高并发量的情况效率更高。
多进程服务器:通过创建多个进程提供服务。
多路复用服务器:通过捆绑并统一管理 I/O 对象提供服务。
多线程服务器:通过生成与客户端等量的线程提供服务。
进程(Process)是计算机中的一个术语,指的是正在运行中的程序实例。在操作系统中,每个进程都有自己独立的内存空间和资源,它们之间相互隔离,并且可以独立执行。
每个进程可以包含一个或多个线程,线程是进程内的执行单元,负责执行进程的指令。不同的进程之间可以并发执行,相互之间独立运行,彼此不会干扰。
进程有以下几个特点:
进程是操作系统中重要的概念,它为程序的执行提供了一个独立和可控的环境。通过进程,操作系统可以同时运行多个应用程序,实现资源的合理分配和管理。
生活中有许多例子可以说明进程的概念。下面是几个常见的例子:
煮饭过程:将烹饪一顿饭比作一个进程。在煮饭的过程中,你需要准备食材、洗切处理、点火、加热、炒煮等一系列步骤。每个步骤都是相对独立的,但又相互关联,最终完成一道美味的饭菜。
打印文件:当你要打印一个文件时,你会选择打印命令并发送给打印机。打印机会创建一个打印进程,它负责从计算机接收数据、解析文件格式、生成打印页面,并将页面发送到打印机进行输出。同时,你可以进行其他操作,如编辑文档或浏览网页,这些操作与打印进程并行执行。
路上的交通:将路上的车辆比作进程。在拥挤的道路上,每辆车都是一个独立的进程,它们之间相互独立运行,但也受到交通规则和信号灯的控制。每辆车根据自己的路径和目的地进行行驶,通过调度和协调,交通系统实现了车辆的并发运行和道路资源的合理利用。
整个工业生产过程:在一个工厂中,生产线上的各个环节可以看作是不同的进程。例如,原材料的采购、加工制造、装配、质量检测等环节都是相对独立的进程,它们按照一定的顺序和流程进行,并最终完成产品的制造。
这些例子说明了生活中进程的存在和应用。无论是在计算机系统中还是在日常生活中,进程都扮演着协调和管理任务的重要角色,实现了多个任务之间的并发执行和资源的合理利用。
进程ID(Process ID),也称为PID,是操作系统中用来唯一标识一个正在运行的进程的数字标识符。每个进程在创建时都会被分配一个独特的PID。
进程ID的作用有以下几个方面:
进程标识:通过PID,操作系统可以准确地标识和区分不同的进程。不同的进程具有不同的PID,使得操作系统可以对它们进行管理、调度和资源分配。
进程控制:操作系统可以使用PID来控制进程的创建、终止和暂停等操作。通过指定PID,可以准确地选择目标进程并执行相应的操作。
进程通信:在进程间进行通信时,PID常被用作目标进程的标识符。发送进程可以通过目标进程的PID将消息或数据传递给指定的进程。
资源管理:各个系统资源,如内存、文件、网络连接等,都与特定的进程相关联。通过PID,操作系统可以将资源与相应的进程关联起来,并进行有效的资源管理和保护。
需要注意的是,PID是动态分配的,当一个进程终止后,其PID可能会被重新分配给新创建的进程。因此,PID只在进程的生命周期内是唯一的。
在操作系统中,可以使用fork()函数来创建一个新的进程。fork()是一个系统调用,其功能是复制当前进程(称为父进程),创建一个新的进程(称为子进程)。子进程是父进程的副本,它继承了父进程的代码、数据和资源。
具体使用方法如下:
- #include
- #include
- int val=10;
- int main(){
- pid_t pid=fork();
- int index=25;
- val++,index+=5;
- if(pid==-1){//fork调用失败
- std::cout<<" fork调用失败"<
- return 1;
- }
- else if(pid==0)
- index+=10;
- else
- val+=2;
-
- if(pid==0)
- std::cout<<"子进程:"<<"val:"<
" index:"< - else
- std::cout<<"父进程:"<<"val:"<
" index:"< - ;
-
- return 0;
- }
-

可以看出,父子进程的变量都是单独区分开的,修改并不会相互影响。
通过fork()函数创建的子进程继承了父进程的大部分状态,包括变量值、打开的文件、进程优先级等。子进程可以独立执行其他任务,这样就实现了并发执行多个进程的能力。
需要注意的是,fork()函数的调用可能会导致操作系统创建新的进程和分配额外的资源。因此,在使用fork()函数时应该注意合理使用系统资源,避免过多创建进程导致系统负载过重。
(二)进程与僵尸进程
进程是操作系统中正在运行的程序的实例,它具有独立的执行环境和资源。当一个进程完成了它的任务,并且终止了,但其父进程尚未通过wait()或waitpid()等系统调用来获取该子进程的状态信息时,这个已经终止但尚未被回收的进程就成为僵尸进程。
僵尸进程是一种特殊的进程状态,其主要特点包括:
- 僵尸进程处于终止状态:即进程已经执行完毕,但它的进程描述符仍然存在于系统中。
- 父进程尚未对其进行处理:父进程还没有使用wait()或waitpid()等系统调用来获取子进程的退出状态信息。
- 僵尸进程不再执行任何代码:僵尸进程不再占用CPU时间片,也不再占用其他系统资源。
产生僵尸进程的常见情况是,父进程在创建子进程后,没有及时处理子进程的终止状态。这可能是因为父进程疏忽、崩溃或者被其他任务所占用而没有处理子进程。
虽然僵尸进程本身并不会导致系统性能问题,但过多的僵尸进程可能会浪费系统资源。因此,需要及时清理僵尸进程。父进程可以通过以下方式处理僵尸进程:
- 使用wait()或waitpid()等系统调用:父进程可以主动调用wait()或waitpid()等系统调用来获取子进程的退出状态信息,从而使子进程成为"终止"状态,释放其占用的系统资源。
- 使用信号处理机制:父进程可以通过注册SIGCHLD信号处理函数,当收到这个信号时,处理僵尸进程的终止状态。
另外,操作系统也会提供一些机制来自动回收僵尸进程,例如Linux中的"init"进程(PID为1)会负责收养孤儿进程和回收僵尸进程。
在编写程序时,父进程应该及时处理子进程的退出状态,以避免过多的僵尸进程积累。
1.子进程的终止方式:
(1)正常退出:子进程可以在执行完任务后通过调用exit()函数来正常退出。exit()函数会终止当前进程,并将退出状态传递给父进程。
- #include
-
- int main() {
- // 子进程执行任务
- // ...
-
- // 正常退出
- exit(EXIT_SUCCESS);
- }
(2)异常退出:子进程也可以通过调用abort()函数或触发一个信号来异常退出。abort()函数会立刻终止进程,触发SIGABRT信号,而信号处理程序则会默认终止进程。
- #include
-
- int main() {
- // 子进程执行任务
- // ...
-
- // 异常退出
- abort();
- // 或者
- raise(SIGABRT);
- }
(3)返回值退出:子进程可以通过在main函数中返回一个整数值来退出。这个整数值会被传递给父进程作为退出状态码。
- int main() {
- // 子进程执行任务
- // ...
-
- // 返回值退出
- return 0;
- }
(4)exec()系列函数:子进程可以使用exec()系列函数来加载一个新的程序镜像,从而替换当前进程的内容。一旦调用exec()成功,子进程就会停止原有的代码执行,而是开始执行新程序的代码。这种方式不是直接终止进程,而是将子进程转变为新的程序。
- #include
-
- int main() {
- // 子进程执行任务
- // ...
-
- // 加载新程序
- execl("/bin/ls", "ls", "-l", NULL);
- }
无论子进程是通过哪种方式终止,父进程都可以通过使用 wait() 或 waitpid() 等系统调用来获取子进程的退出状态信息,并进行相应的处理。这样可以确保父进程及时清理僵尸进程,并释放相应的资源。
2.销毁僵尸进程
(1)wait
- #include
- #include
- #include
- using namespace std;
- int main(){
- int status;
- pid_t pid=fork();
-
- if(pid==-1){
- std::cout<<"父进程创建失败"<
- return 1;
- }
- else if(pid==0)
- return 3;//返回终止
- else{
- cout<<"child PID: "<
- pid=fork();
- if(pid==0)
- exit(7);//exit函数终止
- else{
- cout<<"child PID: "<
- wait(&status);//将之前终止的子进程相关信息保存到status变量,同时子进程完全销毁
- if(WIFEXITED(status))//判断是否正常终止,如果正常退出,下面子进程返回值
- cout<<"child send one:"<<WEXITSTATUS(status)<
- wait(&status);//第二个终止的子进程
- if(WIFEXITED(status))
- cout<<"child send two:"<<WEXITSTATUS(status)<
- sleep(30);
- }
- }
- return 0;
- }

return 或是 exit 都是把进程终止,但是子进程的系统资源还没有回收,父进程通过 wait 函数释放子进程所占据的资源。
注意:调用 wait 函数时,如果没有已终止的子进程,那么程序将阻塞(Blocking)直到子进程终止,因此需谨慎使用。
(2)waitpid
调用 waitpid 函数时,程序不会阻塞。
- #include
- #include
- #include
- #include
- using namespace std;
- int main(){
- pid_t pid=fork();//创建子进程
- int status;
-
- if(pid==0){//子进程
- sleep(15);
- return 24;
- }
- else{//父进程
- while(!waitpid(-1,&status,WNOHANG)){//当没有子进程终止时,保持循环
- sleep(3);//休眠三秒
- cout<<"sleep 3sec"<
- }//子进程终止后,资源被waitpid回收
-
-
- if(WIFEXITED(status))//判断子进程是否正常退出
- cout<<"child send :"<<WEXITSTATUS(status)<
- }
- return 0;
- }
(三)信号处理
我们已经直到了进程创建和销毁方法,那么,子进程何时终止呢?父进程要一直等待吗?
1.利用信号处理技术消灭僵尸进程
(1)signal
- #include
- #include
- #include
- #include
- #include
-
- // SIGCHLD信号处理函数
- void handleSIGCHLD(int signum) {
- pid_t pid;
- int status;
-
- // 循环等待所有子进程退出,并处理其退出状态
- while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- if (WIFEXITED(status)) {
- std::cout << "子进程 " << pid << " 正常退出,退出状态:" << WEXITSTATUS(status) << std::endl;
- }
- else if (WIFSIGNALED(status)) {
- std::cout << "子进程 " << pid << " 异常退出,终止信号:" << WTERMSIG(status) << std::endl;
- }
- }
- }
-
- int main() {
- // 注册SIGCHLD信号处理函数
- signal(SIGCHLD, handleSIGCHLD);
-
- // 创建子进程
- pid_t childPid = fork();
-
- if (childPid == 0) {
- // 子进程代码
- sleep(2);
- return 0;
- }
- else if (childPid > 0) {
- // 父进程代码
- sleep(5);
- return 0;
- }
- else {
- // fork()失败
- std::cerr << "无法创建子进程" << std::endl;
- return 1;
- }
-
- return 0;
- }
(2)sigaction
- #include
- #include
- #include
- #include
-
- void sigchld_handler(int signum) {
- pid_t pid;
- int status;
-
- // 循环等待所有子进程退出
- while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
- if (WIFEXITED(status)) {
- std::cout << "Child process " << pid << " exited with status " << WEXITSTATUS(status) << std::endl;
- } else if (WIFSIGNALED(status)) {
- std::cout << "Child process " << pid << " terminated by signal " << WTERMSIG(status) << std::endl;
- }
- }
- }
-
- int main() {
- struct sigaction sa;
-
- // 设置 SIGCHLD 信号处理函数
- sa.sa_handler = sigchld_handler;
- sigemptyset(&sa.sa_mask);
- sa.sa_flags = SA_RESTART;
-
- if (sigaction(SIGCHLD, &sa, nullptr) == -1) {
- perror("Error setting signal handler");
- return 1;
- }
-
- // 创建子进程
- pid_t pid = fork();
-
- if (pid == -1) {
- perror("Error creating child process");
- return 1;
- } else if (pid == 0) {
- // 子进程执行某些任务
- // ...
-
- exit(0); // 子进程正常退出
- }
-
- // 父进程继续执行其他任务
- // ...
-
- // 父进程等待一段时间后结束
- sleep(10);
-
- return 0;
- }
(3)二者区别
-
signal 函数:
signal 函数是 C 标准库提供的函数,具有广泛的兼容性。- 优点:简单易用,适合进行基本的信号处理,不需要额外的结构体参数。
- 缺点:在某些情况下,可能会出现可重入性问题,并且对于某些信号,可能无法修改其行为或使用高级功能。
-
sigaction 函数:
sigaction 函数是 POSIX 标准提供的函数,提供了更多的信号处理选项和控制能力。- 优点:灵活性强,可以指定更复杂的信号处理行为,可以获取和保存之前的信号处理信息。
- 缺点:相对于
signal 函数,使用 sigaction 函数可能需要编写更多的代码。
(四)基于多任务的并发服务器
echo_mpserv.cpp
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define BUF_SIZE 30
-
- void error_handling(const char* message);
- void read_childproc(int sig);
-
- int main(int argc, char* argv[]) {
- int serv_sock, clnt_sock;
- struct sockaddr_in serv_adr, clnt_adr;
- pid_t pid;
-
- socklen_t adr_sz;
- int str_len, state;
- char buf[BUF_SIZE];
-
- if (argc != 2) {
- std::cout << "Usage : " << argv[0] << "
" << std::endl; - exit(1);
- }
-
- // 准备及注册 sigaction
- struct sigaction act;
- act.sa_handler = read_childproc;
- sigemptyset(&act.sa_mask);
- act.sa_flags = 0;
- state = sigaction(SIGCHLD, &act, nullptr);
-
- // socket
- serv_sock = socket(PF_INET, SOCK_STREAM, 0);
-
- // bind
- memset(&serv_adr, 0, sizeof(serv_adr));
- serv_adr.sin_family = AF_INET;
- serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
- serv_adr.sin_port = htons(std::atoi(argv[1]));
- if (bind(serv_sock, reinterpret_cast<struct sockaddr*>(&serv_adr),
- sizeof(serv_adr)) == -1) {
- error_handling("bind() error");
- }
-
- // listen
- if (listen(serv_sock, 5) == -1) {
- error_handling("listen() error");
- }
-
- while (true) {
- // accept
- adr_sz = sizeof(clnt_adr);
- clnt_sock = accept(serv_sock, reinterpret_cast<struct sockaddr*>(&clnt_adr), &adr_sz);
- if (clnt_sock == -1) {
- continue;
- } else {
- std::cout << "New client connected..." << std::endl;
- }
-
- // fork
- pid = fork();
- if (pid == -1) { // 子进程创建失败
- close(clnt_sock);
- continue;
- }
- if (pid == 0) { // 子进程
- close(serv_sock); // 关闭子进程服务器套接字
-
- // read
- while ((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0) {
- write(clnt_sock, buf, str_len);
- }
-
- close(clnt_sock); // 关闭子进程客户端套接字
- std::cout << "Client disconnected..." << std::endl;
- return 0;
- } else { // 父进程
- close(clnt_sock); // 关闭父进程客户端套接字
- }
- }
-
- close(serv_sock); // 关闭父进程服务器套接字
- return 0;
- }
-
- void read_childproc(int sig) {
- pid_t pid;
- int status;
- pid = waitpid(-1, &status, WNOHANG); // 回收子进程
- std::cout << "Removed proc id: " << pid << std::endl;
- }
-
- void error_handling(const char* message) {
- std::cerr << message << std::endl;
- exit(1);
- }
注意:fork()只复制了父进程两个套接字(服务器端套接字、客户端套接字)的文件描述符,调用fork()函数后,2个文件描述符指向同一套接字。
只有当2个文件描述符都终止(销毁后),才能销毁套接字!

echo_mpclien.cpp
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define BUF_SIZE 1024
-
- void error_handling(const char* message);
-
- int main(int argc, char* argv[]) {
- int sock;
- int str_len, recv_len, recv_cnt;
- char message[BUF_SIZE];
-
- struct sockaddr_in serv_adr;
-
- if (argc != 3) {
- std::cout << "Usage : " << argv[0] << "
" << std::endl; - exit(1);
- }
-
- sock = socket(PF_INET, SOCK_STREAM, 0);
- if (sock == -1) {
- error_handling("socket() error");
- }
-
- memset(&serv_adr, 0, sizeof(serv_adr));
- serv_adr.sin_family = AF_INET;
- serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
- serv_adr.sin_port = htons(std::atoi(argv[2])); // 端口号
-
- if (connect(sock, reinterpret_cast<struct sockaddr*>(&serv_adr), sizeof(serv_adr)) == -1) {
- error_handling("connect() error");
- } else {
- std::cout << "Connected......" << std::endl;
- }
-
- while (true) {
- std::cout << "Input Message(Q to quit): ";
- std::cin.getline(message, BUF_SIZE);
- if (std::strcmp(message, "q") == 0 || std::strcmp(message, "Q") == 0) {
- break;
- }
- str_len = write(sock, message, strlen(message));
-
- recv_len = 0;
- while (recv_len < str_len) {
- recv_cnt = read(sock, &message[recv_len], BUF_SIZE - 1);
- if (recv_cnt == -1) {
- error_handling("read() error");
- }
- recv_len += recv_cnt;
- }
-
- message[str_len] = '\0';
- std::cout << "Message from server: " << message << std::endl;
- }
-
- close(sock);
- return 0;
- }
-
- void error_handling(const char* message) {
- std::cerr << message << std::endl;
- exit(1);
- }
(五)分割TCP的I/O模型
echo_mpclient.cpp
- #include
- #include
- #include
- #include
- #include
-
- #define BUF_SIZE 30
-
- void error_handling(const char* message);
- void read_routine(int sock, char *buf);
- void write_routine(int sock, char *buf);
-
- int main(int argc, char *argv[]) {
- int sock;
- pid_t pid;
- char buf[BUF_SIZE];
- struct sockaddr_in serv_adr;
-
- if (argc != 3) {
- std::cout << "Usage : " << argv[0] << "
" << std::endl; - exit(1);
- }
-
- // 创建套接字
- sock = socket(PF_INET, SOCK_STREAM, 0);
- memset(&serv_adr, 0, sizeof(serv_adr));
- serv_adr.sin_family = AF_INET;
- serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
- serv_adr.sin_port = htons(std::atoi(argv[2]));
-
- // 连接服务器
- if (connect(sock, reinterpret_cast<struct sockaddr*>(&serv_adr), sizeof(serv_adr)) == -1) {
- error_handling("connect() error!");
- }
-
- pid = fork();
- if (pid == 0) {
- write_routine(sock, buf);
- } else {
- read_routine(sock, buf);
- }
-
- close(sock);
- return 0;
- }
-
- void read_routine(int sock, char *buf) {
- while (true) {
- int str_len = read(sock, buf, BUF_SIZE);
- if (str_len == 0) {
- return;
- }
- buf[str_len] = 0;
- std::cout << "Message from server: " << buf << std::endl;
- }
- }
-
- void write_routine(int sock, char *buf) {
- while (true) {
- std::cin.getline(buf, BUF_SIZE);
- if (std::strcmp(buf, "q") == 0 || std::strcmp(buf, "Q") == 0) {
- shutdown(sock, SHUT_WR);
- return;
- }
- write(sock, buf, strlen(buf));
- }
- }
-
- void error_handling(const char* message) {
- std::cerr << message << std::endl;
- exit(1);
- }
通过fork函数创建子进程,子进程发送,父进程接收

二.进程间通信(管道)
(一)进程间通信的基本概念
进程间通信(Inter-Process Communication,IPC)是指不同进程之间进行数据交换和共享资源的方式和机制。常见的进程间通信方法包括管道、命名管道、信号量、消息队列、共享内存和套接字等。
1.通过管道实现进程间通信

- #include
- #include
- #define BUF_SIZE 30
- using namespace std;
-
- int main(int argc,char *argv[]){
-
- int fds[2];
- char str[]="who are you?";
- char buf[BUF_SIZE];
- pid_t pid;
-
- pipe(fds);//创建管道
- pid=fork();//创建子进程
- if(pid==0){
- write(fds[1],str,sizeof(str));//子进程写入
- }
- else{
- read(fds[0],buf,BUF_SIZE);//父进程读出
- cout<
- }
- return 0;
- }
-

2.通过管道进程进程间双向通信(创建两个管道)

- #include
- #include
- #define BUF_SIZE 30
- using namespace std;
-
- int main(int argc,char *argv[]){
- int fds[2],fds1[2];
- char str[]="你好我是进程A!";
- char str1[]="你好我是进程B!";
- char buf[BUF_SIZE];
- pid_t pidB;
-
- pipe(fds);//创建两个管道
- pipe(fds1);
- pidB=fork();
- if(pidB==0){
- write(fds1[1],str1,sizeof(str1));
- read(fds[0],buf,BUF_SIZE);
- cout<<"子进程:"<<getpid()<<" "<
- }
- else{
- read(fds1[0],buf,BUF_SIZE);
- cout<<"父进程:"<<getpid()<<" "<
- cout<<"子进程:"<
" "< - write(fds[1],str,sizeof(str));
- sleep(1);
- }
- return 0;
- }

注意:getpid获取当前进程ID号
为何不用单个管道:
在进行双向通信时,为了实现双向数据传输,需要创建两个管道。每个管道负责一个方向的数据传输。
使用两个管道的主要原因是,管道是单向的,即数据只能在一个方向上流动。如果只使用一个管道进行双向通信,那么会出现以下问题:
-
阻塞:当一个进程读取管道中的数据时,如果另一个进程也想写入数据,但在同一时间被阻塞(因为管道已被占用),则会导致进程之间的通信阻塞。
-
死锁:假设两个进程同时尝试读取和写入同一个管道,由于管道是单向的,它们可能会陷入相互等待对方完成操作的死锁状态。
通过创建两个管道,可以解决上述问题。一个管道用于父进程向子进程传递数据,另一个管道则用于子进程向父进程传递数据。这样,每个进程都有自己独立的读取端和写入端,从而避免了阻塞和死锁的问题。
总结起来,创建两个管道可以实现双向通信,确保数据在两个方向上的正常流动,避免了阻塞和死锁的问题。
(二)运用进程间通信
1.保存消息的回声服务器端
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define BUF_SIZE 100
-
- void error_handling(const char *message);
- void read_childproc(int sig);
-
- int main(int argc, char *argv[]) {
- int serv_sock, clnt_sock;
- struct sockaddr_in serv_adr, clnt_adr;
- int fds[2];
-
- pid_t pid;
- struct sigaction act;
- socklen_t adr_sz;
- int str_len, state;
- char buf[BUF_SIZE];
-
- if (argc != 2) {
- std::cout << "Usage : " << argv[0] << "
" << std::endl; - exit(1);
- }
-
- // Setup signal handling for child processes
- act.sa_handler = read_childproc;
- sigemptyset(&act.sa_mask);
- act.sa_flags = 0;
- state = sigaction(SIGCHLD, &act, 0);
-
- // Create socket
- serv_sock = socket(AF_INET, SOCK_STREAM, 0);
-
- // Bind
- memset(&serv_adr, 0, sizeof(serv_adr));
- serv_adr.sin_family = AF_INET;
- serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
- serv_adr.sin_port = htons(atoi(argv[1]));
-
- if (bind(serv_sock, reinterpret_cast<struct sockaddr *>(&serv_adr), sizeof(serv_adr)) == -1)
- error_handling("bind() error");
-
- // Listen
- if (listen(serv_sock, 5) == -1)
- error_handling("listen() error");
-
- pipe(fds);
-
- pid = fork();
- if (pid == 0) { // Child process for saving messages
- std::ofstream fp("echomsg.txt", std::ios::out | std::ios::binary);
- char msgbuf[BUF_SIZE];
- int i, len;
-
- for (i = 0; i < 10; i++) {
- len = read(fds[0], msgbuf, BUF_SIZE);
- fp.write(msgbuf, len);
- }
- fp.close();
- return 0;
- }
-
- while (1) {
- // Accept
- adr_sz = sizeof(clnt_adr);
- clnt_sock = accept(serv_sock, reinterpret_cast<struct sockaddr *>(&clnt_adr), &adr_sz);
- if (clnt_sock == -1)
- continue;
- else
- std::cout << "New client connected..." << std::endl;
-
- pid = fork();
- if (pid == 0) { // Child process
- close(serv_sock);
- while ((str_len = read(clnt_sock, buf, BUF_SIZE)) != 0) {
- write(clnt_sock, buf, str_len);
- write(fds[1], buf, str_len);
- }
-
- close(clnt_sock);
- std::cout << "Client disconnected..." << std::endl;
- return 0;
- } else { // Parent process
- close(clnt_sock);
- }
- }
-
- close(serv_sock);
- return 0;
- }
-
- void read_childproc(int sig) {
- pid_t pid;
- int status;
- pid = waitpid(-1, &status, WNOHANG);
- std::cout << "Removed proc id: " << pid << std::endl;
- }
-
- void error_handling(const char *message) {
- std::cerr << message << std::endl;
- exit(1);
- }
2.客户端
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define BUF_SIZE 30
-
- void error_handling(const char *message);
- void read_routine(int sock, char *buf);
- void write_routine(int sock, char *buf);
-
- int main(int argc, char *argv[])
- {
- int sock;
- pid_t pid;
- char buf[BUF_SIZE];
- struct sockaddr_in serv_adr;
-
- if (argc != 3) {
- std::cout << "Usage : " << argv[0] << "
" << std::endl; - exit(1);
- }
-
- sock = socket(PF_INET, SOCK_STREAM, 0);
- memset(&serv_adr, 0, sizeof(serv_adr));
- serv_adr.sin_family = AF_INET;
- serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
- serv_adr.sin_port = htons(atoi(argv[2]));
-
- if (connect(sock, reinterpret_cast<struct sockaddr *>(&serv_adr), sizeof(serv_adr)) == -1)
- error_handling("connect() error!");
-
- pid = fork();
- if (pid == 0)
- write_routine(sock, buf);
- else
- read_routine(sock, buf);
-
- close(sock);
- return 0;
- }
-
- void read_routine(int sock, char *buf)
- {
- while (1)
- {
- int str_len = read(sock, buf, BUF_SIZE);
- if (str_len == 0)
- return;
-
- buf[str_len] = '\0';
- std::cout << "Message from server: " << buf << std::endl;
- }
- }
-
- void write_routine(int sock, char *buf)
- {
- while (1)
- {
- std::cin.getline(buf, BUF_SIZE);
- if (strcmp(buf, "q") == 0 || strcmp(buf, "Q") == 0)
- {
- shutdown(sock, SHUT_WR);
- return;
- }
- write(sock, buf, strlen(buf));
- }
- }
-
- void error_handling(const char *message)
- {
- std::cerr << message << std::endl;
- exit(1);
- }



注意:服务器端设置了只保存10个字符串。
(三)总结
1.什么是进程间通信?分别从概念和内存的角度进行说明
概括性地说,进程间通信是指两个进程之间交换数据。但是从内存的角度看,可以理解为两个进程共有内存。因为共享的内存区域存在,可以进行数据交换
2.进程间通信需要特殊的IPC机制,这是由操作系统提供的。进程间通信时为何需要操作系统的帮助?
要想实现IPC机制,需要共享的内存,但由于两个进程之间不共享内存,因此需要操作系统的帮助,也就是说,两进程共享的内存空间必须由操作系统来提供
3.“管道”是典型的IPC技术。关于管道,请回答如下问题。
- 管道是进程间交换数据的路径。如何创建该路径?由谁创建?
- 管道是由pipe函数产生的,而实际产生管道的主体是操作系统
- 为了完成进程间通信,2个进程需同时连接管道。那2个进程如何连接到同一管道?
- pipe函数通过输入参数返回管道的输入输出文件描述符。这个文件描述符在fork函数中复制到了其子进程,因此,父进程和子进程可以同时访问同一管道。
- 管道允许进行2个进程间的双向通信。双向通信中需要注意哪些内容?
- 管道并不管理进程间的数据通信。因此,如果数据流入管道,任何进程都可以读取数据。因此,要合理安排共享空间的输入和读取。
-
相关阅读:
【Java----String类详解】
docker一键安装debian/ubuntu桌面环境LXDE+VNC+Firefox
【服务器搭建】教程二:快速搭建我们服务器 进来看
目标检测——行人和骑自行车者数据集
SSM+基于web的酒店预订及个性化服务系统 毕业设计-附源码241822
数说故事《汽车行业全场景数字化解决方案》之产品管理解决方案
Java中的高性能字节码工具:Javassist
嵌入式学习之链表
Linux介绍与常用命令详解
nacos简单使用
-
原文地址:https://blog.csdn.net/Ricardo_XIAOHAO/article/details/132603596