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

时间:2017-04-18 18:34:34

标签: c++ multithreading sockets

要学习使用TCP的套接字编程,我制作了一个简单的服务器和客户端。客户端将发送文件块,服务器将读取它们并写入文件。客户端和服务器无需任何多处理即可正常工作。我想这样做,以便多个客户端可以同时连接。我想给每个连接的客户端一个唯一的id,名为" client_id"。这是介于1和n之间的数字。

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

这是我的无限循环,等待连接客户端。在子进程中,我将文件转移到另一个无限循环中,当循环收到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)

enter image description here

1 个答案:

答案 0 :(得分:0)

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

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

在for循环中有斐波纳契原理的过程育种的例子: Visually what happens to fork() in a For Loop

  1. Parent将计数器从0增加到1并调用fork。现在我们有孩子打印1。
  2. CHild完成传输并返回循环开始。收到连接时,它增加到2。父母在设法接收连接时增加了两个。现在你有4个流程。
  3. 如果按顺序进行更多测试,您会发现重复数量增加。如果你看过这些数据,你实际上可以在taskmanager或top,ps输出中看到这些进程。