通过分叉为每个子进程提供唯一 ID

Posted

技术标签:

【中文标题】通过分叉为每个子进程提供唯一 ID【英文标题】:Giving Each Child Process a Unique ID with Forking 【发布时间】:2017-04-18 18:34:33 【问题描述】:

为了学习使用 TCP 进行套接字编程,我正在制作一个简单的服务器和客户端。客户端将发送文件块,服务器将读取它们并写入文件。客户端和服务器在没有任何多处理的情况下正常工作。我想让它让多个客户端可以同时连接。我想给每个连接的客户端一个唯一的 id,称为“client_id”。这是一个介于 1 和 n 之间的数字。

我尝试使用 fork() 来生成子进程,在子进程中我接受连接,然后读入数据并将其保存到文件中。但是,client_id 变量不会跨进程同步,因此有时会增加,有时不会。我不完全明白发生了什么。 client_id 的值永远不应重复,但有时我会看到数字出现两次。我相信这是因为在分叉时,子进程会获得父进程所有内容的副本,但并行进程之间没有同步。

这是我等待连接客户端的无限循环。在子进程中,我在另一个无限循环中传输文件,该循环在 recv 接收到 0 字节时终止。

int client_id = 0;
while(1)

        // accept a new connection
        struct sockaddr_in clientAddr;
        socklen_t clientAddrSize = sizeof(clientAddr);

        //socket file descriptor to use for the connection
        int clientSockfd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddrSize);
        if (clientSockfd == -1) 
            perror("accept");
            return 4;
        
        else   //handle forking
            client_id++;
            std::cout<<"Client id: "<<client_id<<std::endl;

            pid_t pid = fork();
            if(pid == 0) 
                //child process
                std::string client_idstr = std::to_string(client_id);

                char ipstr[INET_ADDRSTRLEN] = '\0';
                inet_ntop(clientAddr.sin_family, &clientAddr.sin_addr, ipstr, sizeof(ipstr));

                std::string connection_id = std::to_string(ntohs(clientAddr.sin_port));
                std::cout << "Accept a connection from: " << ipstr << ":" << client_idstr
                 << std::endl;

                // read/write data from/into the connection
                char buf[S_BUFSIZE] = 0;
                std::stringstream ss;

                //Create file stream
                std::ofstream file_to_save;

                FILE *pFile;

                std::string write_dir = filedir+"/" + client_idstr + ".file";

                std::string write_type = "wb";
                pFile = fopen(write_dir.c_str(), write_type.c_str());
                std::cout<<"write dir: "<<write_dir<<std::endl;


                while (1) 
                    memset(buf, '\0', sizeof(buf));

                    int rec_value = recv(clientSockfd, buf, S_BUFSIZE, 0);

                    if (rec_value == -1) 
                      perror("recv");
                      return 5;
                    else if(rec_value == 0)
                        //end of transmission, exit the loop
                        break;
                    


                    fwrite(buf, sizeof(char), rec_value, pFile);

                
                fclose(pFile);
                close(clientSockfd);
            
            else if(pid > 0)
                //parent process
                continue;
            else
                perror("failed to create multiple new threads");
                exit(-1);
            
        

    

这是我执行以下操作时的服务器输出,括号中是预期的文件名(client_id.file):

1) 连接客户端 1,传输文件,断开客户端 1 (1.file) 2)连接客户端2,传输文件,断开客户端2(2.file) 3)连接客户端1,传输文件,断开客户端1(3.file) 4)连接客户端1,传输文件,断开客户端1(4.file) 5)连接客户端2,传输文件,断开客户端2(5.file) 6)连接客户端2,传输文件,断开客户端2(6.file)

【问题讨论】:

这个std::cout&lt;&lt;的语法不是C,把标签改成C++ 不会使用 std::string、std::ostream 等表示更多的 C++ 。程序?进程与线程也不一样 不确定是否是这个原因,但您应该在子进程中close(sockfd); @infixed 好点。更改了措辞。 @dbush 没有修复它。 【参考方案1】:

我可能错了,但这种反方法让我记忆犹新。你的计数器是如何定义的,它是一个局部变量吗?它应该具有线程范围的生命周期,因此无论是全局变量还是静态局部变量,fork 都会在复制内存页面时将其值复制到生成的进程中。存储为临时变量的局部变量以及它们在 C++ 中发生的情况是未定义的。 如果你需要共享变量,有办法,通过使用共享内存.. mmap 等等。

其次.. 它是进程的副本,它将从调用 fork() 的位置开始执行。你有什么?无限循环结束。循环将重新启动。如果 pid 为 0,你应该退出它。

在 for() 循环中有一个基于斐波那契原理的过程的例子: Visually what happens to fork() in a For Loop

    父级将计数器从 0 增加到 1 并调用 fork。现在我们有了打印 1 的孩子。 CHild 完成传输并返回到循环的开头。收到连接时,计数器增加到 2。当它设法接收连接时,父级将计数器增加到两个。现在您有 4 个进程。

如果您按顺序进行更多测试,您会看到重复次数增加。如果您查看了这些数据,您实际上可以在您的任务管理器或顶部、ps 输出中看到这些进程。

【讨论】:

@jonnyd42 添加静态然后就足够了 忘记退出是我的问题。非常感谢!

以上是关于通过分叉为每个子进程提供唯一 ID的主要内容,如果未能解决你的问题,请参考以下文章

分叉后退出子进程

子进程似乎在一段时间循环中陷入睡眠

程序if(fork() == 0)是啥意思?

linux进程原语之fork()(原创!)

分叉的子进程是不是使用相同的信号量?

在父进程恢复执行之前等待所有子进程 UNIX