I am new in socket programming and at this moment I am confronted with a problem that I can not solve. I have read from several sources that the C++ standard template (STL) containers are not thread-safe, so that one as a programmer has to impose a mechanism that ensures that several threads do not modify the data of a container concurrently.
For instance, Thread safety std::vector push_back and reserve
I have used the std::mutex
class to make sure that nobody writes data in the same container at the same time when programming threads
. However, this is not working for me when I use sockets
.
Suppose I have 4 clients, each one sending data (int
) to the server in the following order:
client_0: 4
client_1: 8
client_2: 5
client_4: 7
Observe the following code for a simple server:
#define PORT 60000
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <vector>
#include <string>
#include <iostream>
#include <mutex>
using namespace std;
vector<int> inputQueue; //<--------!
mutex mtx; //<---------------------!
void printVector(vector<int> input) {
cout << "inputQueue: [";
for (unsigned int i = 0; i < input.size(); i++ ) {
if (i != input.size() - 1)
cout << input[i] << ", ";
else
cout << input[i];
}
cout << "]." << endl;
}
int main(int argc, char const *argv[])
{
int server_fd, client_fd;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons( PORT );
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address))<0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 10) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
while(1) {
char buffer[4];
if ((client_fd = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
perror("accept");
exit(EXIT_FAILURE);
}
if (!fork()) {
recv(client_fd, buffer, 4, MSG_WAITALL);
int receivedInt = int(
(unsigned char)(buffer[0]) << 24 |
(unsigned char)(buffer[1]) << 16 |
(unsigned char)(buffer[2]) << 8 |
(unsigned char)(buffer[3])
);
mtx.lock(); //<-------------------------------------!
inputQueue.push_back(receivedInt); //<--------------!
cout << "Client context. Integer registered: " << receivedInt << ": inputQueue length is " << inputQueue.size() << endl;
printVector(inputQueue); //<------------------------!
mtx.unlock(); //<-----------------------------------!
close(server_fd); close(client_fd);
}
cout << "Server context: inputQueue length is " << inputQueue.size() << endl;
printVector(inputQueue);
}
return 0;
}
The server must receive data making sure that they do so in the same order and registering their respective data in a vector of integers, that is, std::vector<int> inputQueue
, using the push_back()
method, so that inputQueue = {4, 8, 5, 7}
at the end of the reception of all the data by the clients.
I must clarify that inputQueue
is a global variable, which when starting the execution of the server, does not contain elements, but they are added as the clients register.
The problem is that none of the client registers elements in inputQueue. Notice in the following code that, depending on where you put the cout <<
instruction, you can see that the inputQueue
size is different. This shows that within the context of the client, each client overwrites the first element of inputQueue
, but outside it none of the clients is able to register a single element in inputQueue
.
Apparently, each socket has its own copy of inputQueue
, so when it is destroyed, the modified copy of inputQueue
is also destroyed.
Output is the following:
Server context: inputQueue length is 0
inputQueue: [].
Client context. Integer registered: 4: inputQueue length is 1
inputQueue: [4].
Server context: inputQueue length is 1
inputQueue: [4].
Server context: inputQueue length is 0
inputQueue: [].
Client context. Integer registered: 8: inputQueue length is 1
inputQueue: [8].
Server context: inputQueue length is 0
inputQueue: [].
Server context: inputQueue length is 1
inputQueue: [8].
Client context. Integer registered: 5: inputQueue length is 1
inputQueue: [5].
Server context: inputQueue length is 1
inputQueue: [5].
Server context: inputQueue length is 0
inputQueue: [].
Client context. Integer registered: 7: inputQueue length is 1
inputQueue: [7].
Server context: inputQueue length is 1
inputQueue: [7].
Does anyone have any idea why this happens and how could they solve it? I hope you can help me. Thank you