0

To learn socket programming with TCP, I'm making a simple server and client. The client will send chunks of a file and the server will read them and write to a file. Client and server work properly without any multiprocessing. I want to make it so that multiple clients can connect simultaneously. I want to give each connected client a unique id, called "client_id". This is a number between 1 and n.

I tried to use fork() in order to spawn a child process, and in the child process I accept the connection and then read in the data and save it to the file. However, the client_id variable is not synchronized across processes so sometimes it will be incremented and sometimes not. I don't fully understand what's going on. The value of client_id should never be repeated, but sometimes I'm seeing numbers appear twice. I believe this is because on forking, the child process gets a copy of everything the parent had but there is no synchronization across parallel processes.

Here is my infinite loop that sits and waits for connecting clients. Within the child process, I transfer the file in another infinite loop that terminates when recv receives 0 bytes.

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);
            }
        }

    }

Here is the server output when I do the following, with the expected file name (client_id.file) in parentheses:

1) connect client 1, transfer file, disconnect client 1 (1.file)
2) connect client 2, transfer file, disconnect client 2 (2.file)
3) connect client 1, transfer file, disconnect client 1 (3.file)
4) connect client 1, transfer file, disconnect client 1 (4.file)
5) connect client 2, transfer file, disconnect client 2 (5.file)
6) connect client 2, transfer file, disconnect client 2 (6.file)

enter image description here

jonnyd42
  • 490
  • 1
  • 9
  • 23

1 Answers1

0

I might be wrong, but something tickles my memory about this counter approach. How your counter is defined, is it a local variable? it should have thread-wide time of life, so either a global variable or static local, then fork would copy its value to the spawned process when it copies memory pages. Local variables stored as temporary and what happens with them in C++ is undefined. If you need shared variables, there is way, by using shared memory .. mmap and so on.

Second.. it's a copy of process, which becomes to be executed from the point where fork() was called. And what you have there? infinite loop wrap up. Loop would restart. You should exit from it if pid was 0.

There is example of processes breeding on Fibonacci principle within for() loop: Visually what happens to fork() in a For Loop

  1. Parent increased counter from 0 to 1 and called fork. Now we have child that printed 1.
  2. CHild finished transmission and returned to the beginning of loop. it increased counter to 2 when it received connection. parent increased counter to two when it managed to receive connection. Now you have 4 processes.

If you made more tests in sequence, you would see that amount of duplicates rises. And you actually could see those processes in your taskmanager, or top, ps output, if you watched that data.

Community
  • 1
  • 1
Swift - Friday Pie
  • 12,777
  • 2
  • 19
  • 42