I'm trying to figure out why recv is blocking in the code below, if I telnet and send a 'GET / HTTP/1.1', recv keeps waiting for data and blocks another telnet connection as well. However, it works fine and doesn't block if I just use a fixed buffer instead of the do{} while
, i.e.,
char buffer[1024];
nbytes = recv(i, buffer, sizeof buffer, 0);
What I understand so far, select()
at that point is in a ready-to-read state. Do I need to set recv
to O_NONBLOCK
?
char *buffer = NULL;
unsigned long LEN = 200;
unsigned long bytes_received = 0;
unsigned long cur_size = 0;
int status = 0;
FD_SET(listener, &master);
fdmax = listener;
for(;;){
read_fds = master; // copy it
if(select(fdmax+1, &read_fds, NULL, NULL, NULL) == -1){
exit(4);
}
for(i = 0; i <= fdmax; i++){
if(FD_ISSET(i, &read_fds)){
if(i == listener){
// handle new connections
addrlen = sizeof remoteaddr;
newfd = accept(listener, (struct sockaddr *)&remoteaddr, &addrlen);
if(newfd == -1){
perror("accept");
}else{
FD_SET(newfd, &master);
if(newfd > fdmax) {
fdmax = newfd;
}
}
}else{
do{
if(bytes_received >= cur_size){
char * tmp;
cur_size += LEN;
tmp = realloc(buffer, cur_size);
buffer = tmp;
}
status = recv(i, buffer + bytes_received, LEN, 0);
if(status == 0){
printf("Done\n");
}
else if(status > 0){
bytes_received += status;
}
else{
fprintf(stderr, "socket error\n");
}
} while (status > 0);
if(send(i, buffer, strlen(buffer), 0) == -1){
perror("send");
}
}
}
}
}
First, you have to set the socket non-blocking if you don't want to block. No matter what you do, it is not possible to assure that you never block if your socket operations are blocking. This is a commonly-made mistake that has caused serious bugs with significant security implications. If you must not block, you must set the socket non-blocking.
What I understand so far, select() at that point is in a ready-to-read state.
You mean was in a ready-to-read state. The select
function is a status-reporting function and, like other status-reporting functions, it reports the status as of some point in-between when you called it and when it returned. There is no guarantee that is still the status at some later point.
Do not ever think you can rule out every other possible way the status might change. History is filled with people who thought that and got burned.
But the specific reason that you are blocking is that you are calling recv
without first calling select
(because you have a do
loop that calls recv
again).
No matter what, you must correctly handle a WOULDBLOCK
indication from recv
. That is essential.
In addition, you may wish to call recv
only once per select
hit. If you fix the other issues, it won't matter.