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)