C TCP Socket using select() to get multiple inputs from same client
I'm new to socket, I am trying to send a message to the server, and if the server does not receive another message from client within 5 seconds, then send a warning to client, otherwise combine two messages and send back to client.
I'm using select, and the server is not able to recv second message once the select() is called, it's always timeout.
What did I do wrong??
Server
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <sys/socket.h>
#include <unistd.h>
#define BUF_SIZE 256
char *concat(const char *s1, const char *s2);
int main(int argc, char const *argv[]) {
struct sockaddr_in server, client;
struct timeval tv;
int sock, readSize, fd = 0;
char buf[BUF_SIZE], stringA[BUF_SIZE], stringB[BUF_SIZE];
socklen_t addressSize;
fd_set readfds;
bzero(&server, sizeof(server));
server.sin_family = PF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(6000);
// TCP check
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
printf("socket: %s\n", strerror(errno));
return 1;
}
// Handle binding error
if (bind(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
printf("bind: %s\n", strerror(errno));
return 1;
}
// Handle connection error
if (listen(sock, 5) == -1) {
printf("listen: %s\n", strerror(errno));
return 1;
}
// Handle client acceptance error
addressSize = sizeof(client);
sock = accept(sock, (struct sockaddr *)&client, &addressSize);
while ((readSize = recv(sock, stringA, sizeof(stringA), 0))) {
stringA[readSize] = '\0';
printf("Read Message A: %s", stringA);
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
FD_SET(0, &readfds);
tv.tv_sec = 5;
tv.tv_usec = 0;
if (select(sock + 1, &readfds, NULL, NULL, &tv) < 0) {
printf("ERROR in select");
}
char *result;
// If more messgae receve within 5 seconds, but the program never reached this part
if (FD_ISSET(sock, &readfds)) {
// Get string B
readSize = recv(sock, stringB, sizeof(stringB), 0);
stringB[readSize] = '\0';
result = concat(stringA, stringB);
printf("Some more input received\n");
} else {
printf("Time out\n");
}
send(sock, &result, sizeof(result), 0);
}
printf("Client has closed the connection.\n");
close(sock);
return 0;
}
char *concat(const char *s1, const char *s2) {
char *result = malloc(strlen(s1) + strlen(s2) + 1);
strcpy(result, s1);
strcat(result, s2);
return result;
}
Client
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <errno.h>
#define BUF_SIZE 256
int main(int argc, char const *argv[]) {
struct sockaddr_in server;
struct timeval tv;
int sock, readSize, addressSize;
char buf[BUF_SIZE];
bzero(&server, sizeof(server));
server.sin_family = PF_INET;
server.sin_addr.s_addr = inet_addr("127.0.0.1");
server.sin_port = htons(6000);
sock = socket(PF_INET, SOCK_STREAM, 0);
addressSize = sizeof(server);
// TCP check
sock = socket(PF_INET, SOCK_STREAM, 0);
if (sock == -1) {
printf("socket: %s\n", strerror(errno));
return 1;
}
// Handle connection error
if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
printf("connect: %s\n", strerror(errno));
return 1;
}
while (1) {
fgets(buf, 256, stdin);
if (feof(stdin)) break;
send(sock, &buf, sizeof(buf), 0);
readSize = recv(sock, buf, sizeof(buf), 0);
buf[readSize] = '\0';
printf("%s\n", buf);
}
close(sock);
return 0;
}
Solution 1:
The problem you're asking about appears to be that the client tries to receive a response from the server after each send()
. That will block until it can read at least one byte or the server closes the connection. Meanwhile, the server expects the client to send messages one right after another, without any responses to the odd-numbered messages.
That is symptomatic of a design problem. This ...
I am trying to send a message to the server, and if the server does not receive another message from client within 5 seconds, then send a warning to client, otherwise combine two messages and send back to client.
... may sound simple and seem to make sense, but in fact it is neither very simple nor very sensible. Suppose you have a well-intentioned client of of your service. It knows that the service expects two messages, one after the other, to which it will respond with a concatenation of the two. Why would such a client fail to send the expected second messages? The most likely explanations are
- it can't, because it is suspended, because a network link went down, because its host machine went down, or similar.
- it didn't, because it was killed before it could.
- it didn't, because it is buggy.
None of those is rescued by the server sending a warning message. (But if the client is killed before delivering the second message, then the server will probably see EOF on the socket and signal read-readiness.)
Moreover, suppose the client is suspended, the server sends a warning, and then the client resumes. When it tries to read the expected response from the server then it gets the warning message instead, or quite possibly the warning concatenated with the expected response. How is the client supposed to distinguish warning messages from normal responses?
I would suggest dropping the warnings altogether. The server can instead just disconnect non-responsive clients.
If you want to retain the warnings and continue to use just one socket then you need to augment your protocol to make the warning messages distinguishable and separable from normal responses. For example, the server's responses might be in two parts -- a response code identifying the message type, followed by a response body.
Alternatively, you might use separate sockets for the two distinct message streams, but I think that would be more complicated than you want to handle right now.
There are other issues with your code, though. The main ones are
-
You seem to assume that
send
/write
andrecv
/read
calls will pair up so that the data sent from one side by one call is exactly what is received by one call on the other side. That is not at all a safe assumption. You are using a stream-oriented socket, and one of the characteristics of such a socket is that the data stream does not have any built-in message boundaries. If you want to divide the data into logically separate messages, then you need to layer that on top of the stream. -
You do not account for the fact that
send
/write
andrecv
/read
may (successfully) transfer fewer bytes than you request. Generally speaking, you need to pay attention to the return value of these functions and be prepared to use multiple calls to transfer all the bytes of a given transmission.