http client blocks on recv() - c++

I need some help writing an http client. The trouble comes when I try to receive data from a webserver. The recv() call blocks the program. Any better direction would be extremely helpful, I'll post my code below:
if ( argc != 2 )
{
cerr << "Usage: " << argv[0];
cerr << " <URI>" << endl;
return 1;
}
else
{
uri_string = argv[1];
}
// Create URI object and have it parse the uri_string
URI *uri = URI::Parse(uri_string);
if ( uri == NULL )
{
cerr << "Error: Cannot parse URI." << endl;
return 2;
}
// Check the port number specified, if none use port 80
unsigned port = 80;
if ( uri->Is_port_defined() )
{
port = uri->Get_port();
}
// Create TCP socket and connect to server
int tcp_sock = socket( AF_INET, SOCK_STREAM, 0 );
if ( tcp_sock < 0 )
{
cerr << "Unable to create TCP socket." << endl;
return 3;
}
sockaddr_in server;
socklen_t slen = sizeof(server);
server.sin_family = AF_INET;
server.sin_port = htons( port );
hostent *hostp = gethostbyname( uri->Get_host().c_str() );
memcpy( &server.sin_addr, hostp->h_addr, hostp->h_length );
if ( connect( tcp_sock, (sockaddr*)&server, slen ) < 0 )
{
cerr << "Unable to connect to server via TCP." << endl;
close( tcp_sock );
return 4;
}
// Build HTTP request to send to server
HTTP_Request *request = HTTP_Request::Create_GET_request( uri->Get_path() );
request->Set_host( uri->Get_host() );
string request_string = "";
request->Print( request_string );
//cout << request_string << endl;
// Send it to the server, wait for reply and use HTTP_Response to get reply
send( tcp_sock, &request_string, sizeof(request_string), 0 );
char recv_buffer[1024];
int bytes_recv = 0;
while ( bytes_recv < 1024 )
{
int recv_len = recv( tcp_sock, recv_buffer + bytes_recv,
1024 - bytes_recv, 0 );
if ( recv_len == -1 )
{
cerr << "Error receiving response from server." << endl;
close( tcp_sock );
return 5;
}
bytes_recv += recv_len;
}
HTTP_Response *response = HTTP_Response::Parse(recv_buffer, bytes_recv);
string response_string = "";
response->Print( response_string );
cout << response_string << endl;
return 0;
}

You are using a blocking TCP/IP socket, but you are not looking at the HTTP reply's "Content-Length" header to know how many bytes to read. Your current reading logic is calling recv() in a loop until 1024 bytes max have been received. If the server sends less than 1024 bytes, you are going to be blocked indefinately because you are calling recv() too many times asking for too many bytes.

recv() is supposed to block until it gets a response. Are you sure you're writing your request properly, and that the server is responding to it? It's possible to put the file descriptor into nonblocking mode and test it using select() or poll(), but my guess is that you simply have a protocol bug somewhere. What is the behavior you are expecting?

Is this a problem?
If the client is comamnd line fine.
If it is GUI then the thread retrieving data should be different from the UI thread.
But a solution is to use the select()
This will tell you if there is anything to be read from a port.
Thus allowing you to do other work while waiting.

The HTTP request should end with an empty line, i.e.
GET / HTTP/1.1
Host: blah.com
<- this here is an empty line
It looks like your code is not doing that (it should probably say request->Print(request_string + "\n").
Off-topic: you know there are readily available HTTP clients in C, right? (such as libcurl).

You must receive many bytes what is in content-length field.
You must change line: if ( recv_len == -1 )
to:
if ( recv_len <= 0 ) break;
else if ( recv_len == -1 )
because 0 is when server will disconnect after sending all data.

Related

How to correctly response to HTTP request

I am trying to create simple server which will repond to every HTTP request with 200 OK. I write code below, but when I called the server with Postman I ended with wrong result. If I do NOT call closesocket(clientSocket), Postman is waiting infinitely long for response with Sending requst.... I i do call closesocket(clientSocket), Postman shows Error: socket hang up.
What I have to do to correctly close connection?
void main()
{
std::cout << "Start..." << std::endl;;
int port = 54000;
// Initialize WinSock
WSAData data;
WORD ver = MAKEWORD(2, 2);
int wsResult = WSAStartup(ver, &data);
if (wsResult != 0)
{
std::cout << "Can't start Winsock, Err #" << wsResult << std::endl;
return;
}
// Create socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
std::cout << "Can't create socket, Err #" << WSAGetLastError() << std::endl;
WSACleanup();
return;
}
// Bind the ip address and port to a socket
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(port);
hint.sin_addr.S_un.S_addr = INADDR_ANY;
bind(sock, (sockaddr*)&hint, sizeof(hint));
listen(sock, SOMAXCONN);
// Wait for a connection
sockaddr_in client;
int clientSize = sizeof(client);
// While loop: accept and echo message back to client
char buf[16384];
while (true)
{
SOCKET clientSocket = accept(sock, (sockaddr*)&client, &clientSize);
//print(client);
ZeroMemory(buf, 16384);
// Wait for client to send data
int bytesReceived = recv(clientSocket, buf, 16384, 0);
if (bytesReceived == SOCKET_ERROR)
{
std::cout << "Error in recv(). Quitting" << std::endl;
break;
}
const char* reply =
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n"
"Content-Length: 0\n"
"Keep - Alive: timeout=1, max=1\n"
"Accept-Ranges: bytes\n"
"Connection: close\n";
send(clientSocket, reply, strlen(reply), 0);
//closesocket(clientSocket);
} |
}
edit. Edited code. I corrected line endings, added better check for recv result and also I am checking number of sent bytes (and yes, all bytes are send). I also tried to add shutdown function, but result is still the same - infinite waiting or error. When I try to add do / while section, I will stuck in infinite waiting for another request. Do you have any other advice?
void main()
{
std::cout << "Start..." << std::endl;;
int port = 54000;
// Initialize WinSock
WSAData data;
WORD ver = MAKEWORD(2, 2);
int wsResult = WSAStartup(ver, &data);
if (wsResult != 0)
{
std::cout << "Can't start Winsock, Err #" << wsResult << std::endl;
return;
}
// Create socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
{
std::cout << "Can't create socket, Err #" << WSAGetLastError() << std::endl;
WSACleanup();
return;
}
// Bind the ip address and port to a socket
sockaddr_in hint;
hint.sin_family = AF_INET;
hint.sin_port = htons(port);
hint.sin_addr.S_un.S_addr = INADDR_ANY;
bind(sock, (sockaddr*)&hint, sizeof(hint));
listen(sock, SOMAXCONN);
// Wait for a connection
sockaddr_in client;
int clientSize = sizeof(client);
// While loop: accept and echo message back to client
char buf[16384];
while (true)
{
SOCKET clientSocket = accept(sock, (sockaddr*)&client, &clientSize);
//print(client);
ZeroMemory(buf, 16384);
// Wait for client to send data
//do {
bytesReceived = recv(clientSocket, buf, 16384, 0);
if (bytesReceived > 0)
{
std::cout << std::string(buf, 0, bytesReceived) << std::endl;
}
else if (bytesReceived == 0)
{
std::cout << "Connection closed" << std::endl;
}
else
{
std::cout << "Error in recv(). Quitting = " << WSAGetLastError() << std::endl;
break;
}
//} while (bytesReceived > 0);
const char* reply =
"HTTP/1.1 200 OK\r\n"
"Content-Type: text/html\r\n"
"Content-Length: 0\r\n"
"Accept-Ranges: bytes\r\n"
"Connection: close";
bytesSent = send(clientSocket, reply, strlen(reply), 0);
if (bytesSent < 0)
{
std::cout << "Send failed = " << WSAGetLastError() << std::endl;
}
std::cout << "Sent: " << bytesSent << " (" << strlen(reply) << ")" << std::endl;
shutdown(clientSocket, SD_BOTH);
closesocket(clientSocket);
} |
}
const char* reply =
"HTTP/1.1 200 OK\n"
...
"Keep - Alive: timeout=1, max=1\n"
...
"Connection: close\n";
send(clientSocket, reply, strlen(reply), 0);
This is not a valid HTTP response for multiple reasons:
The line ending should be \r\n not \n.
The HTTP header should end with an additional \r\n (i.e. empty line). This means the HTTP response you send is not complete and thus Postman hangs on waiting for the rest.
The Keep - Alive header has not the valid syntax for a HTTP header. The field name is not allowed to contain spaces.
Apart from that,
send is not guaranteed to send everything in the given buffer, i.e. you should check the return code.
A Keep-Alive header makes no sense at all if Connection: close is used.
Note that HTTP is not an ad-hoc protocol but an actual standard. Implementations should follow the standard and thus the developers would need to study the actual standard and not blindly rely on (wrong) assumptions of how things might work. Even if it works with some client it might not work with another one, since some clients adhere more to the standard while others are more tolerant regarding errors.
There are multiple problems with the shown code.
int bytesReceived = recv(clientSocket, buf, 16384, 0);
On any TCP socket, HTTP or not, you get no guarantees whatsoever that whatever the other side of the socket sent you, it will get returned in a single recv() call.
If, for example, the client sent you the following HTTP request:
GET / HTTP/1.0<CR><LF>
Host: localhost<CR><LF>
<CR><LF>
Nobody is going to guarantee you that this recv() will return this entire thing to you. recv() could, for example, return only the "GET" part, and return the value of 3 to indicate that only 3 bytes were received. You are required to call recv() as many times as necessary to receive the complete message. The 2nd call to recv() could, theoretically, return " /" and the value of 2 to indicate that two more bytes were received.
You cannot completely ignore the return code from recv(), which tells you what was actually read, and you will need to implement appropriate logic to keep recv()ing until you received the entire message. Your code needs to understand that an HTTP message consists of a message line, an arbitrary list of headers, then a single blank line, all terminated by <CR><LF>, or "\r\n", and keep trying until it receives the complete message.
const char* reply =
"HTTP/1.1 200 OK\n"
"Content-Type: text/html\n"
"Content-Length: 0\n"
"Keep - Alive: timeout=1, max=1\n"
"Accept-Ranges: bytes\n"
"Connection: close\n";
HTTP uses <CR><LF> as the end of line terminator, all of these should be \r\n.
"Keep - Alive:" is not a valid HTTP header name. Header names cannot contain spaces.
A blank line, a single \r\n, should follow the last HTTP header. It is missing fom your response. Even though you're closing the socket immediately, without a well-formed HTTP response message the client can rightfully conclude that the HTTP server is faulty and did not send a valid response.
send(clientSocket, reply, strlen(reply), 0);
Like with recv(), you are not guaranteed that send() will transmit the requested number of bytes. Just like with recv() you must check the return value of send() to see how many bytes were sent, and then try again to send the remaining part of the message, if there's any, until you send the entire message.
You must fix all of these problems in order to properly implement HTTP over TCP.

Why does connecting to server with 2nd socket while using select() "break" first connection?

I'm following the tutorial (big code block near the bottom of that section)
here:http://beej.us/guide/bgnet/output/html/multipage/advanced.html#select
And the main server code code is like so:
while (true)
{
read_fds = master;
if (select(fd_max + 1, &read_fds, NULL, NULL, NULL) == -1)
{
cerr << "ERROR. Select failed" << endl;
return -1;
}
for (int i = 0; i <= fd_max; i++)
{
if (FD_ISSET(i, &read_fds))
{
if (i == welcome_socket)
{
cout << "NEW CONNECTION" << endl;
client_len = sizeof(struct sockaddr_in);
client_sock = accept(welcome_socket, (struct sockaddr *) &client_addr, &client_len);
if (client_sock != -1)
{
FD_SET(client_sock, &master);
if (client_sock > fd_max)
{
fd_max = client_sock;
}
}
}
else
{
int length, total_read = 0;
// CONNECTION CLOSED BY CLIENT
if (safe_recv(client_sock, &length, sizeof(int)) <= 0)
{
cout << "CONNECTION CLOSED" << endl;
close(i);
FD_CLR(i, &master);
}
else
{
char *message = (char *)memset((char *)malloc(length + 1), 0, length);
// while ((total_read += safe_recv(client_sock, message + total_read, length - total_read)) < length) {}
safe_recv(client_sock, message, length);
// RESPOND WITH MESSAGE
cout << "MESSAGE: " << message << endl;
write(client_sock, process(message), length);
free(message);
}
}
}
}
}
What I'm doing is first sending (from the client) the length of the string, then the string itself. Then the server sends back process(message).
When I only have 1 connection, I'm seeing correct behaviour. However if 1 is connected already and I connect a new client, what I'm seeing is:
1st client no longer sends or receives anything from server (concluded because nothing is printed to stdout on client side)
2nd client is working as expected
When 2nd connection exits, server counts that as both connections exiting (prints CONNECTION CLOSED twice)
I've tried to keep this very similar to the tutorial code. I've run the tutorial server, and that works as intended with several clients.
I'm new to network programming, so I apologise if this is a beginner problem or just something dumb I overlooked.
The code reads from and writes to only client_sock, and client_sock is replaced with the new socket in the accept handling portion of the code.
Most likely you want to interact with i rather than client_sock.

Timeout for dropped packets (UDP)

I'm trying to create a timeout using select() for UDP socket transfer. I want to send an int from client to server, wait 300ms, and if I don't get an ACK, resend the packet. I'm not sure how to set this up properly with the timeout. From what I've gathered online and on the notes I have from class, select should be used on the receiving end.
the client at the server send back and forth the numbers 1-100. I have a separate router simulated code that randomly drops packets
Here is the code i have for the client side
int sent = 1;
int received = 1;
for (int i = 0; i < 100; i++)
{
string sent1 = to_string(sent);
char const *pchar = sent1.c_str();
if(!sendto(s, pchar, sizeof(pchar), 0, (struct sockaddr*) &sa_in, sizeof(sa_in)))
cout << "send NOT successful\n";
else
{
cout << "Client sent " << sent << endl;
sent++;
}
// receive
fd_set readfds; //fd_set is a type
FD_ZERO(&readfds); //initialize
FD_SET(s, &readfds); //put the socket in the set
if(!(outfds = select (1 , &readfds, NULL, NULL, & timeouts)))
break;
if (outfds == 1) //receive frame
{
if (!recvfrom(s, buffer2, sizeof(buffer2), 0, (struct sockaddr*) &client, &client_length))
cout << "receive NOT successful\n";
else
{
received = atoi(buffer2);
cout << "Client received " << received << endl;
received++;
}
}
}
The code is identical for the receiving side except it is in reverse: receive first, then send
My code doesn't utilize the timeout at all. This is basically what I want to do:
send packet(N)
if (timeout)
resend packet(N)
else
send packet(N+1)
If the receiver gets a timeout it needs to tell the sender, or else not tell the sender. In other words you have to implement either a NACK-based protocol or an ACK-based protocol.

Winsock recv loop not functioning correctly

For some reason when I connect to my C++ server with Putty I get this
Recv: (string I typed)
Recv:
This happens everytime I send characters to my server using Putty
Source follows. If nessicary I will post the rest of my source. TYIA -Roland
void recvthread( void *pParams ) {
char buffer[128]
int err;
bool gonow = true;
while( true ) {
memset( buffer, '\0', 128 );
err = -1;
err = recv( datasock, buffer, 128, 0 );
if( err != -1 ) {
std::cout << "Recv: " << buffer << '\n';
std::cout << "Err = " << err << '\n';
}
Sleep(10);
}
}
I get this:
Recv: (string I typed)
Recv:
You get the string you typed plus whatever else was left over in the buffer from the previous time. If err is positive it is the number of bytes actually received. If it is zero it means the peer has disconnected and you should stop reading. Don't ignore these values.

socket, detect connection is lost

I'm connecting a server process and a client process with a TCP connection, and I have to detect
that physical connection between the two machines is down. I'm trying to do this using the keepalive,
decreasing the default system wide values to:
TCP_KEEPIDLE=5
TCP_KEEPCNT = 5
TCP_KEEPINTVL = 1
When the connection goes down ( I disconnect the cable ) only the server in 10 seconds detect that the connection has been lost, the client just hangs on the send.
This is the client code:
#include <iostream>
#include <string.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/tcp.h>
int main(int argc, char** argv) {
char myVector[1600];
int mySocket = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
if (mySocket < 0 ) {
std::cout << "error creating the socket" << strerror(errno) << std::endl;
::exit(-1);
}
struct sockaddr_in sin;
memset( (char *)&sin, 0, sizeof( sin ) );
sin.sin_addr.s_addr = inet_addr("192.168.21.27");
sin.sin_port = htons(7788);
sin.sin_family = AF_INET;
if ( connect( mySocket, (struct sockaddr *)&sin, sizeof( sin )) < 0 ) {
std::cout << "Error on connection: " << strerror(errno) << std::endl;
::exit(-1);
}
int optval = 1;
socklen_t optlen = sizeof(optval);
/*Enabling keep alive*/
if(setsockopt(mySocket, SOL_SOCKET, SO_KEEPALIVE, &optval, optlen) < 0) {
std::cout << "Error setting SO_KEEPALIVE: " << strerror(errno) << std::endl;
}
optval = 5;
optlen = sizeof(optval);
if(setsockopt(mySocket, SOL_TCP, TCP_KEEPIDLE, &optval, optlen) < 0) {
std::cout << "Error setting TCP_KEEPIDLE: " << strerror(errno) << std::endl;
}
optval = 5;
optlen = sizeof(optval);
if(setsockopt(mySocket, SOL_TCP, TCP_KEEPCNT, &optval, optlen) < 0) {
std::cout << "Error setting TCP_KEEPCNT: " << strerror(errno) << std::endl;
}
optval = 1;
optlen = sizeof(optval);
if(setsockopt(mySocket, SOL_TCP, TCP_KEEPINTVL, &optval, optlen) < 0) {
std::cout << "Error setting TCP_KEEPINTVL: " << strerror(errno) << std::endl;
}
for (;;) {
ssize_t myRet= ::send(mySocket,
myVector,
sizeof(myVector),
0);
if (myRet < 0) {
std::cout << "Error: " << strerror(errno) << std::endl;
break;
}
std::cout << myRet << "."; std::cout.flush();
sleep(1);
}
}
I'm sure I'm missing something, but what ?
TCP Keepalive isn't intended for this use.
If you want to detect outages at the application layer, do what protocols like SSH, IMAP and IRC do - implement an echo/ping type message at the application layer. Send them on a regular basis, and if you don't get a timely reply, the connection can be assumed to be down.
We wondered about that question in our company a while ago : "how to detect that connection went down?".
To adress this issue reliably, we had to implement a "heart-beat" system, ie the client regularly check (each second in our case) that the server is still there, by doing a pseudo-ping.
If you don't want to do that, you can wait that the OS actually detects that connection went down, but don't expect it to be reliable...
So,
after further investigaion, even if "TCP Keepalive" is not intended for this use, I have discovered that keep alive probes are started to being sent on a "idle connection". The question is now: "when a connection is considered in idle state?".
A connection is considered idle when there is no data "being transmitted" so if one of the
two peers are blocked on a send(...) there are actually some data being transmitted and the connection is not considered idle. I guess the only option I have now is to do a ping/pong using sends/recv with timeout, declaring a connection "lost" when those timers expires.
Gaetano, IMO, TCP keep-alives can be used to detect dead connections. In your example, the client might actually be hanging in the send waiting for the TCP retries to exhaust themselves. Depending on the back-off algorithm and TCP stack state machine, this can last several minutes without any keep-alive probes, and thus no way to exhaust keepcnt.
I assume that the server is mostly read-blocked, in which case, its keep-alives would be sent out every keepidle/slowhz seconds (slowhz is often 2 instead of 1), and it will detect the connection loss fairly quickly.
If you capture a packet trace with tcpdump, you'll see exactly what's happening on the wire.
You should replace SOL_TCP with IPPROTO_TCP.
For more information follow these links
TCP-Keepalive-HOWTO
Socket keepalive not working