So, I'm having a problem, I'm trying to do a simple c server-client, where basically a client connects to the server, and from time to time the server pings the client, shouldn't be this complicated, but I'm being met with a strange behaviour from both "select", "send" and "recv".
The server code is (without the whole connection part) basically this:
while (1) {
read_set = set; write_set = set; error_set = set; timeout = ref_timeout;
int result = select(NULL, &read_set, &write_set, &error_set, &timeout); //I'm on windows that's why the first parameter is null
if (result < 0) {
puts("Error select");
return 1;
}
if (result == 0) puts("timed out");
else {
for (int index = 0; index < 2; index++) {
SOCKET i = sockets[index];
if (FD_ISSET(i, &error_set)) {
printf("%zu error\n", i);
FD_CLR(i, &set);
closesocket(i);
}
if (FD_ISSET(i, &write_set)) {
const char* ping = "PING";
int size = send(i, ping, strlen(ping)+1, 0);
printf("%d bytes sent\n", size);
if (size < 0) {
puts("Error send");
closesocket(i);
FD_CLR(i, &set);
}
send(i, NULL, 0, 0);
}
if (FD_ISSET(i, &read_set)) {
if (i == listen_socket){
struct sockaddr addr;
int addrlen;
SOCKET new_socket = accept(listen_socket, &addr, &addrlen);
FD_SET(new_socket, &set);
if (new_socket > biggest) biggest = new_socket;
printf("new conn %zu\n", new_socket);
sockets[1] = new_socket;
}
else {
char msg[100];
memset(msg, 0, sizeof(msg));
if (recv(i, msg, sizeof(msg), 0) < 0) {
closesocket(i);
FD_CLR(i, &set);
}
else {
printf("%zu %s\n", i, msg);
}
}
}
}
}
}
And the client code (without the connection part) is:
char buffer[100];
const char* pong = "PONG";
for (int i = 0; i < 3; i++) {
memset(buffer, 0, sizeof(buffer));
int result = recv(client_socket, buffer, sizeof(buffer), 0);
if (result < 0) {
puts("something went wrong receiving");
closesocket(client_socket);
return 1;
}
if (send(client_socket, pong, strlen(pong)+1, 0) < 0) {
puts("something went wrong sending");
return 1;
}
printf("msg: %s\n", buffer);
Sleep(10000);
}
The behaviour I would expect is, whenever the client block from recv, the select on the server returns with the client socket on the write_set, the server writes the 5 bytes to the client, reads the 5 bytes, and then blocks until the next recv from the client.
But that's not what's happening, after the first recv, the server keeps writing 5 bytes, it receives "PONG" correctly, but doesn't stop writing to the client.
And I know it is writing to the client because when I kill the client process the send immediately fails.
Is this windows being weird? Am I not understanding how to use select? How can the client tell the server it's not up to reading after the first recv?
..., whenever the client block from recv, the select on the server returns with the client socket on the write_set, the server writes the 5 bytes to the client, reads the 5 bytes,
There is no kind of notification to the server if the client waits for data in recv
. There is no other tight coupling between send
on one side and recv
on the other.recv
, send
and select
all only work on the send and receive buffers of the socket:
recv
will return data from the sockets receive buffer. Note that it might not return all data requested if there are not as much data in the buffer, so check the return value for how much was actually read. It will block if there are no data in the buffer until the operating system has received such data and put it into the buffer.send
will return if there was enough space in the sockets send buffer to place the new data - no matter if the peer is currently calling recv
. Note that there might not be space for all data, so you need to check what send
returns to find out how many data were actually placed in the send buffer. send
will block if there is no space in the buffer and wait until the operating system has made some space by sending the data to the peer.select
will return a socket as readable if there are any data in the sockets receive buffer.select
will return a socket as writable if there is space in the sockets send buffer for new data