How to recv until theres nothing more to recv without eof? - c

So i need to recv an html file from the server to the client, the file is bigger than the buffer so i make several sends. Thats why i have this loop when i recv
while (i = recv(s, buf, TAM_BUFFER, 0)) {
if (i == -1) {
perror(argv[0]);
fprintf(stderr, "%s: error reading result\n", argv[0]);
exit(1);
}
while (i < TAM_BUFFER) {
j = recv(s, &buf[i], TAM_BUFFER - i, 0);
if (j == -1) {
perror(argv[0]);
fprintf(stderr, "%s: error reading result\n", argv[0]);
exit(1);
}
i += j;
}
/* Print out the file line by line. */
printf("%s", buf);
}
the send looks something like this:
while (fgets(buf, sizeof(buf), fp)){
if (send(s, buf, TAM_BUFFER, 0) != TAM_BUFFER) errout(hostname);
}
The problem is the loop never ends, becase it doesnt recv the eof and i is never 0, its just remain blocked there.
I cant do the close to send the eof because after he recv the whole file, the client will ask for another file.
I tryed to send a SIGALRM if the loop stays blocked for longer than 5 seconds but it doesnt work as expected, because the loop wont stop, and it will throw an error.
Also how can i do to be able to recv less than TAM_BUFFER?(in the send, change the TAM_BUFFER -> strlen(buf)) I know i need to change the interior loop, but then ill have the same problem, j will not be 0 never, so i dont know how could i end it.(or maybe i dont need the second loop in this case).
EDIT: i cant send the lenght of the file beucause of the protocol im following

TCP is a protocol used to transport a single unstructured octet stream in each direction. Shutdown of the connection (i.e. EOF) is the only way in TCP to signal to the peer that no more data will be sent in this connection. If you need a different way because you need to distinguish between multiple messages inside the same TCP connection then you need to use an application level protocol which can specify such message boundaries. This is usually done by fixed message size, prefixing the message with a length or by special boundary markers.

If you can't embed payload size in your protocol, you have to identify EOF by closing socket or checking for timeout. You can use select function and set timeout for it, see here Using select and recv to obtain a file from a web server through a socket and https://stackoverflow.com/a/30395738/4490542

Related

Proper way to break out of a recv() loop in C

I wrote a server/client program where I can copy files from client to server. The problem is that I can't break out of the while loop in my server when writing to the file is done. I can only open the new copied file when I close my server program because the file doesn't get closed with fclose(copyFile). The file gets copied successfully everytime. However it does work properly when I run the server/client on the same machine but when I move the client to another pc, the server keeps blocking on recv() in my server.
Server:
while (1)
{
int res = recv(s, buf, BUFSIZ, 0);
if (res == SOCKET_ERROR)
{
printf("3 error %d\n", WSAGetLastError);
break;
}
fwrite(buf, 1, res, copyFile);
if (strncmp(buf, "exit", 4) == 0)
{
break;
}
}
fclose(copyFile);
Client:
while (size == BUFSIZ)
{
size = fread(buf, 1, BUFSIZ, originalFile);
int r = send(ClientSocket, buf, size, 0);
if (r == SOCKET_ERROR)
{
printf("1 error: %d\n", WSAGetLastError);
}
}
int r = send(ClientSocket, "exit", 4, 0);
if (r == SOCKET_ERROR)
{
printf("2 error: %d\n", WSAGetLastError);
}
fclose(originalFile);
How can I properly exit the while() loop in my server?
You are attempting to indicate the end of the file with the word "exit", but there are at least two problems with that:
if the file being transferred contains the word "exit" then that could be misinterpreted as an end-of-file marker, and
you seem to be assuming that your send() calls at the client will be paired one-to-one with recv() calls at the server, so that the "exit" can be relied upon to appear at the beginning of the buffer when the server receives it. That is not a safe assumption.
(Note also that even if the server did happen to receive the "exit" at the beginning of the buffer, you would still write it to the file before recognizing it as an end-of-file marker. If this were the only issue then it would be easy to fix.)
You need a workable application-level protocol to help client and server communicate -- something that layers the needed additional structure on top of the flat byte stream that the socket connection provides. The "exit" terminator is an attempt at that, but it is not a workable solution, at least not by itself.
In contrast, consider HTTP: messages consist of a sequence of headers that are individually and as a group recognizable by their lexical form, followed by the message body. Among the things that the headers can convey is the length of the message body, and this is how the recipient can recognize the end of the message body without the sender closing the connection.
You do not need to implement HTTP, but you can be inspired by it. If you want to transmit messages with arbitrary length and content, without signaling the end of the message by closing the connection, then your best bet is to tell the recipient in advance how many bytes to expect in the message. That could be as simple as prepending a fixed-length message-length field.

One socket descriptor always blocked on write. Select not working?

Hello I have a server program and a client program. The server program is working fine, as in I can telnet to the server and I can read and write in any order (like a chat room) without any issue. However I am now working on my client program and when I use 'select' and check if the socket descriptor is set to read or write, it always goes to write and then is blocked. As in messages do not get through until the client sends some data.
How can I fix this on my client end so I can read and write in any order?
while (quit != 1)
{
FD_ZERO(&read_fds);
FD_ZERO(&write_fds);
FD_SET(client_fd, &read_fds);
FD_SET(client_fd, &write_fds);
if (select(client_fd+1, &read_fds, &write_fds, NULL, NULL) == -1)
{
perror("Error on Select");
exit(2);
}
if (FD_ISSET(client_fd, &read_fds))
{
char newBuffer[100] = {'\0'};
int bytesRead = read(client_fd, &newBuffer, sizeof(newBuffer));
printf("%s",newBuffer);
}
if(FD_ISSET(client_fd, &write_fds))
{
quit = transmit(handle, buffer, client_fd);
}
}
Here is code to transmit function
int transmit(char* handle, char* buffer, int client_fd)
{
int n;
printf("%s", handle);
fgets(buffer, 500, stdin);
if (!strchr(buffer, '\n'))
{
while (fgetc(stdin) != '\n');
}
if (strcmp (buffer, "\\quit\n") == 0)
{
close(client_fd);
return 1;
}
n = write(client_fd, buffer, strlen(buffer));
if (n < 0)
{
error("ERROR writing to socket");
}
memset(buffer, 0, 501);
}
I think you are misinterpreting the use of the writefds parameer of select(): only set the bit when you want to write data to the socket. In other words, if there is no data, do not set the bit.
Setting the bit will check if there is room for writing, and if yes, the bit will remain on. Assuming you are not pumping megabytes of data, there will always be room, so right now you will always call transmit() which waits for input from the command line with fgets(), thus blocking the rest of the program. You have to monitor both the client socket and stdin to keep the program running.
So, check for READ action on stdin (use STDIN_FILENO to get the file descriptor for that), READ on client_fd always and just write() your data to the client_fd if the amount of data is small (if you need to write larger data chunks consider non-blocking sockets).
BTW, you forget to return a proper value at the end of transmit().
Sockets are almost always writable, except when the socket send buffer is full, which indicates that you are sending faster than the receiver is receiving.
So your transmit() function will be entered every time around the loop, so it will read some data from stdin, which blocks until you type something, so nothing happens.
You should only select on writability when a prior send() has returned EWOULDBLOCK/EAGAIN. Otherwise you should just send, when you have something to send.
I would throw this code away and use two or three threads in blocking mode.
select is used to check whether a socket has become ready to read or write. If it is blocking for read then that indicates no data to read. If it is blocking in write, then that indicates the TCP buffer is likely full and the remote end has to read some data so that the socket will allow more data to be written. Since the select blocks until one of the socket descriptions is ready, you also need to use timeout in select to avoid waiting for a long time.
In your specific case, if your remote/receiving end keep reading data from the socket then the select will not block for the write on the other end. Otherwise the tcp buffer will become full on the sender side and select will block. Answers posted also indicate the importance of handling EAGAIN or EWOULDBLOCK.
Sample flow:
while(bytesleft > 0)
then
nbytes = write data
if(nbytes > 0)
bytesleft -= nbytes;
else
if write returns with EAGAIN or EWOULDBLOCK
call poll or select to wait for the socket to be come ready
endif
endif
if poll or select times out
then handle the timeout error(e.g. the remote end did not send the
data within expected time interval)
endif
end while
The code also should include handle error conditions and read/write returning with (For example, write/read returning with 0). Also note read/recv returning 0 indicates the remote end closed the socket.

C sockets, Incomplete file transfer

I'm writing a C program to transfer a file of fixed size, a little over 2Mb, from a server to a client. I'm using TCP sockets on Linux and the code I wrote is the following:
Server (sender)
while (1) {
int nread = read(file, buffer, bufsize);
if (nread == 0) // EOF
break;
if (nread < 0) {
// handle errors
}
char* partial = buffer;
while (nread > 0) {
int nwrite = write(socket, partial, nread);
if (nwrite <= 0) {
// handle errors
}
nread -= nwrite;
partial += nwrite;
}
}
// file sent
shutdown(socket, SHUT_WR);
Client (receiver)
while (filesize > 0) {
nread = read(socket, buffer, bufsize);
if (nread == 0) {
// EOF - if we reach this point filesize is still > 0
// so the transfer was incomplete
break;
}
if (nread < 0) {
// handle errors
}
char* partial = buffer;
while (nread > 0) {
nwrite = write(file, partial, nread);
if (nwrite <= 0) {
// handle errors
}
nread -= nwrite;
partial += nwrite;
filesize -= nwrite;
}
}
if (filesize > 0) {
// incomplete transfer
// handle error
}
close(socket);
When testing the code on my laptop (both client and server "are" on localhost and the communication happen on the loopback interface), sometimes the client exits because read received an EOF, and not because it received all filesize bytes. Since i use a shutdown on the server, this should mean that there is no other data to read.
(Note that the server sent all the bytes and executed the shutdown correctly)
Can you explain me why this is happening?
Where are the missing bytes gone?
-----
EDIT 1 - Clarifications
Some users asked a couple of clarifications so i am posting the answers here:
The program is using TCP blocking sockets
The filesize is a fixed value and is hardcoded in both client and server.
No special socket options as, for example, SO_LINGER are enabled/used.
When the error occur, the server (sender) has already sent all the data and executed the shutdown correctly.
The error, as of today, never happened when testing the application with the client and the server on different machines (transfer over a real network interface and not a loopback interface)
EDIT 2
User Cornstalks pointed me to a really interesting article about the, non always reliable, behaviours of TCP.
The article, which is well worth a read, describe a few tricks useful when sending an unknown amount of data between TCP sockets. The tricks described are the followings:
Take advantage of the SO_LINGER option on the sender. This will help to keep the socket open, upon a call to close(2) or shutdown(2), until all the data has successfully been sent.
On the receiver, beware of pending readable data before the actual receiving loop. It could lead to an immediate reset being sent.
Take advantage of shutdown(2) to signal the receiver the the sender has done sending data.
Let the receiver know the size of the file that will be sent before actually sending the file.
Let the receiver acknowledge the sender that the receiving loop is over. This will help to prevent the sender from closing the socket too soon.
After reading the article, i upgraded my code to implement the tricks number 1 and 5.
This is how i implemented trick number 5:
Server (sender)
// sending loop ...
// file sent
shutdown(socket, SHUT_WR);
// wait acknowledgement from the client
ack = read(socket, buffer, bufsize);
if (ack < 0) {
// handle errors
}
Client (receiver)
// receiving loop..
if (filesize > 0) {
// incomplete transfer
// handle error
}
// send acknowledgement to the server
// this will send a FIN and trigger a read = 0 on the server
shutdown(socket, SHUT_WR);
close(socket);
What about tricks number 2, 3 and 4?
Trick number 2 is not needed because as soon as the server accepts the connection the application proceed to the file transfer. NO extra messages are exchanged.
Trick number 3 is already implemented
Trick number 4 is also already implemented. As mentioned earlier the file size is hardcoded, so there is no need to exchange it.
Did this solve my original problem?
NO my problem was not solved. The error is still happening, and as of today, it only happened when testing the application with both client and server on localhost.
What do you think?
Is there a way to prevent this?
You're:
assuming that read fills the buffer, even though
you're defending magnificently against write() not writing the entire buffer.
You need to do (1), and you don't need to do (2) because you're in blocking mode and POSIX assures that write() doesn't return until all the data is written.
A simple version of both loops:
while ((nread = read(inFD, buffer, 0, sizeof buffer)) > 0)
{
write(outFD, buffer, 0, nread);
}
if (nread == -1)
; // error
A more correct version would check the result of write() for errors of course.

c socket: recv and send data simultaneously

I'm having issues with my client side implementation of client server chat program where multiple clients connect. The issue is that i'm coming across is that how exactly should i be sending (chat message to another client) and receiving (chat message from another client) at the same time? What's happening is that i'm always sending data and never reading. Do i need to fork and have one read and the other send?
here is the relevant code
client side
while(1) {
fd_set rfds, wfds;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_SET(serverSocket, &rfds);
FD_SET(serverSocket, &wfds);
if(select(serverSocket+1, &rfds, &wfds, NULL, NULL) < 0) {
perror("select");
exit(-1);
}
if (FD_ISSET(serverSocket, &rfds)) {
// we got data, read it
}
if (FD_ISSET(serverSocket, &wfds)) {
printf(">");
// read keyboard
sendLen = 0;
while ((cmd[sendLen] = getchar()) != '\n')
sendLen++;
cmd[sendLen] = '\0';
// send the data
}
}
You should put the file descriptor 0 (standard input) in the select as well, then read chars and buffer them, and when the socket is available for writing, copy the entire buffer on it. In this way you just block reading on the standard input all the time.
add
FD_SET(0, &rfds);
so select will return when user types something as well.
you must also use fcntl to set stdin as non-blocking.
Then everytime select tells you there's data on stdin do something like that:
while(read(0,buffer+filled,1)>0) {}
Make sure to put another condition to exit the loop if the buffer is full.
then when you can write on the socket do a send, of the size of the amount of bytes you have in your buffer, check if all of it has been written, or move the leftovers bytes at the beginning of the buffer.
That while(getchar()) is blocking you preventing you from receving any messages.

Unable to receive the last socket data sent using the MSG_MORE flag

server side code:
dirp=opendir(path);
if(dirp==NULL)
{
strcpy(err,"error:");
strcat(err,strerror(errno));
send(fd,err,sizeof(err),0);
}
else
{
printf("\nstream opened\n");
while((dp=readdir(dirp))!= NULL)
{
r=send(fd,dp->d_name,100,MSG_MORE);
if(r<0)
perror("\nsend:");
printf("\n%s",dp->d_name);
}
}
client:
while(recv(mainsocket,lsbuf,100,0)>0)
{
printf("\n %s",lsbuf);
bzero(lsbuf,sizeof(lsbuf));
}
the server side is printing all the filenames on the standard output,but on the client side the client is not receiving the last filename and program is getting blocked at that point
The problem is with the send syscall. You call it with MSG_MORE flag that means the more data will follow and send waits for more data without actually sending. The last chunk of data you should send without this flag. Thus your server side should look like:
dp = readdir(dirp);
if (dp != NULL)
{
/* each time check whether there are more data */
while((dp_next = readdir(dirp))!= NULL)
{
r = send(fd, dp->d_name, 100, MSG_MORE);
if (r < 0) {
perror("\nsend");
}
printf("\n%s",dp->d_name);
dp = dp_next;
}
/* send the last or the only record */
r = send(fd, dp->d_name, 100, 0);
if (r < 0) {
perror("\nsend");
}
}
Another posibility to fix the problem is to close the conenction with the close(fd) syscall. It send all data in the buffer before closing the connection. It's a less clean, but more simple solution.
Your client prints the newline before lsbuf, hence everything since the previous newline is lost in your output buffer.
Four solutions:
use printf("%s\n", lsbuf); instead of ..."\n %s"...
use puts(lsbuf);, which has the same effect, but is slightly more appropriate
use fflush(stdout) after your client loop to flush the output buffer
use unbuffered output, see setvbuf() for details
Note that this problem doesn't seem to be networking-related (although I'd substitute MSG_MORE with 0), it's merely a problem with output buffering.
On a sidenote, I strongly suggest to send strlen(dp->d_name) + 1 bytes instead of 100 bytes. This way, you won't send more bytes than necessary, and on the other hand you won't truncate the output if your directory entries happen to be larger than 100 bytes.
Also, neither your client nor your server checks whether send()/recv() returns 0, which means that the connection has been closed by the remote end.

Resources