Unix Client and Server Program - Very Odd Client and Server Socket Bug - unix

I am currently making a client and server program in the Unix Environment. I have made it so the client is able to upload the contents of a file to the client. I am now in the process of adding options and error handlers to the server example the client must enter the file name. To do this i wanted to send a message saying OK if all the options checked out however if i do this it seems to cause my file reading and sending to go Crazy and I have no idea why.
I have uploaded the functions for doing this
Client Code
int putFile (char path[256], int fd)
{
char mystring[1000];
char buffer[100];
int i , n;
FILE * pFile;
n = read(fd,buffer,100);
printf("%s", buffer);
if (strcmp(buffer, "OK") == 0)
{
pFile = fopen(path, "r");
if(pFile != NULL)
{
while(fgets(mystring, sizeof(mystring), pFile) != NULL)
{
//fputs(mystring, fd);
write(fd,mystring,strlen(mystring));
}
}
else
{
printf("Invalid File or Address \n");
}
fclose(pFile);
}
else
{
printf("%s \n", buffer);
}
}
Server Code for reading the socket
int putRequest(int fd, char buf[], char str[])
{
char data[256];
int number;
char * ptr;
char results[100];
int total = 0;
char *arguments[1024];
char temp[10];
int i;
ptr = strtok(buf," ");
while (ptr != NULL)
{
char * temp;
temp = (char *)malloc(sizeof(ptr));
temp = ptr;
arguments[total] = temp;
total++;
ptr = strtok (NULL, " ");
}
if(total == 1)
{
strcat(str, "Invaild Arguments \n");
return 1;
}
write(fd, "OK", 256);
FILE * pFile;
pFile = fopen ("myfile.txt","w");
if (pFile!=NULL)
{
while(read(fd, data, 256) != NULL)
{
fputs(data, pFile);
}
fclose (pFile);
}
else
{
strcat(str, "Invaild File");
return 0;
}
strcat(str, "Done");
return 1;
}
Thanks in advance and just post something if you need to see more code. I just placed the code which should be causing the problem.

Related

How to detect USB cable disconnect in blocking read() call?

I have a smart energy meter which sends energy consumption data every second. The daemon program I've written (C++/C Arch Linux) to read the data doesn't exit when the USB cable is disconnected and stalls indefinitely in the blocking read() call.
How to interrupt a blocking read() call (i.e. fail with EINTR instead of waiting for the next char)?
I extensively searched Google and looked here in SO and could not find an answer to this problem.
Details:
Smartmeter project source on Github
IR dongle with FT232RL USB to UART bridge
Datagrams have fixed length of 328 bytes sent every second
Read method detects beginning \ and end ! markers of a datagram
sigaction to catch CTRL+C SIGINT and SIGTERM signals
termios is setup to do blocking read() with VMIN = 1 and VTIME = 0.
Tried:
Playing with VMIN and VTIME
Removed SA_RESTART
Possible solution:
Use a non-blocking read method, maybe with select() and poll()
Or VMIN > 0 (datagram is longer than 255 characters and I would need to read the datagram in smaller chunks)
Not sure how to handle datagram begin/end detection and the one second interval between datagrams for a non-blocking read method
EDIT: The code below now buffers the read() call into an intermediate buffer of 255 bytes (VMIN = 255 and VTIME = 5) adapted from here. This avoids the small overhead of calling read() for every char. In practise this doesn't make a difference compared to reading one char at a time though. Read() still doesn't exit gracefully on cable disconnect. The daemon needs to be killed with kill -s SIGQUIT $PID. SIGKILL has no effect.
main.cpp:
volatile sig_atomic_t shutdown = false;
void sig_handler(int)
{
shutdown = true;
}
int main(int argc, char* argv[])
{
struct sigaction action;
action.sa_handler = sig_handler;
sigemptyset(&action.sa_mask);
action.sa_flags = SA_RESTART;
sigaction(SIGINT, &action, NULL);
sigaction(SIGTERM, &action, NULL);
while (shutdown == false)
{
if (!meter->Receive())
{
std::cout << meter->GetErrorMessage() << std::endl;
return EXIT_FAILURE;
}
}
Smartmeter.cpp:
bool Smartmeter::Receive(void)
{
memset(ReceiveBuffer, '\0', Smartmeter::ReceiveBufferSize);
if (!Serial->ReadBytes(ReceiveBuffer, Smartmeter::ReceiveBufferSize))
{
ErrorMessage = Serial->GetErrorMessage();
return false;
}
}
SmartMeterSerial.cpp:
#include <cstring>
#include <iostream>
#include <thread>
#include <unistd.h>
#include <termios.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include "SmartmeterSerial.h"
const unsigned char SmartmeterSerial::BufferSize = 255;
SmartmeterSerial::~SmartmeterSerial(void)
{
if (SerialPort > 0) {
close(SerialPort);
}
}
bool SmartmeterSerial::Begin(const std::string &device)
{
if (device.empty()) {
ErrorMessage = "Serial device argument empty";
return false;
}
if ((SerialPort = open(device.c_str(), (O_RDONLY | O_NOCTTY))) < 0)
{
ErrorMessage = std::string("Error opening serial device: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
if(!isatty(SerialPort))
{
ErrorMessage = std::string("Error: Device ") + device + " is not a tty.";
return false;
}
if (flock(SerialPort, LOCK_EX | LOCK_NB) < 0)
{
ErrorMessage = std::string("Error locking serial device: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
if (ioctl(SerialPort, TIOCEXCL) < 0)
{
ErrorMessage = std::string("Error setting exclusive access: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
struct termios serial_port_settings;
memset(&serial_port_settings, 0, sizeof(serial_port_settings));
if (tcgetattr(SerialPort, &serial_port_settings))
{
ErrorMessage = std::string("Error getting serial port attributes: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
cfmakeraw(&serial_port_settings);
// configure serial port
// speed: 9600 baud, data bits: 7, stop bits: 1, parity: even
cfsetispeed(&serial_port_settings, B9600);
cfsetospeed(&serial_port_settings, B9600);
serial_port_settings.c_cflag |= (CLOCAL | CREAD);
serial_port_settings.c_cflag &= ~CSIZE;
serial_port_settings.c_cflag |= (CS7 | PARENB);
// vmin: read() returns when x byte(s) are available
// vtime: wait for up to x * 0.1 second between characters
serial_port_settings.c_cc[VMIN] = SmartmeterSerial::BufferSize;
serial_port_settings.c_cc[VTIME] = 5;
if (tcsetattr(SerialPort, TCSANOW, &serial_port_settings))
{
ErrorMessage = std::string("Error setting serial port attributes: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
tcflush(SerialPort, TCIOFLUSH);
return true;
}
char SmartmeterSerial::GetByte(void)
{
static char buffer[SmartmeterSerial::BufferSize] = {0};
static char *p = buffer;
static int count = 0;
if ((p - buffer) >= count)
{
if ((count = read(SerialPort, buffer, SmartmeterSerial::BufferSize)) < 0)
{
// read() never fails with EINTR signal on cable disconnect
ErrorMessage = std::string("Read on serial device failed: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
p = buffer;
}
return *p++;
}
bool SmartmeterSerial::ReadBytes(char *buffer, const int &length)
{
int bytes_received = 0;
char *p = buffer;
bool message_begin = false;
tcflush(SerialPort, TCIOFLUSH);
while (bytes_received < length)
{
if ((*p = GetByte()) == '/')
{
message_begin = true;
}
if (message_begin)
{
++p;
++bytes_received;
}
}
if (*(p-3) != '!')
{
ErrorMessage = "Serial datagram stream not in sync.";
return false;
}
return true;
}
Many thanks for your help.
While the code below is not a solution to the original question on how to interrupt a blocking read() call, at least it would be a vialble workaround for me. With VMIN = 0 and VTIME = 0 this is now a non-blocking read():
bool SmartmeterSerial::ReadBytes(char *buffer, const int &length)
{
int bytes_received = 0;
char *p = buffer;
tcflush(SerialPort, TCIOFLUSH);
bool message_begin = false;
const int timeout = 10000;
int count = 0;
char byte;
while (bytes_received < length)
{
if ((byte = read(SerialPort, p, 1)) < 0)
{
ErrorMessage = std::string("Read on serial device failed: ")
+ strerror(errno) + " (" + std::to_string(errno) + ")";
return false;
}
if (*p == '/')
{
message_begin = true;
}
if (message_begin && byte)
{
++p;
bytes_received += byte;
}
if (count > timeout)
{
ErrorMessage = "Read on serial device failed: Timeout";
return false;
}
++count;
std::this_thread::sleep_for(std::chrono::microseconds(100));
}
if (*(p-3) != '!')
{
ErrorMessage = "Serial datagram stream not in sync.";
return false;
}
return true;
}
However, I am still curious to know if it is actually possible to interrupt a blocking `read()´ as this workaround is constantly polling the serial port.
I believe reading one char at a time is not a problem as the received bytes from the UART are buffered by the OS - but constantly polling the buffer with read() is! Maybe I'll try a ioctl(SerialPort, FIONREAD, &bytes_available) before read(), although I don't know if this would actually make a difference.
Any suggestions?

Write String to permanent flash memory of Arduino ESP32

I want to write some text into the flash memory of an Arduino ESP32. It works kinda but not as I want it to.
void writeString(const char* toStore, int startAddr) {
int i = 0;
for (; i < LENGTH(toStore); i++) {
EEPROM.write(startAddr + i, toStore[i]);
}
EEPROM.write(startAddr + i, '\0');
EEPROM.commit();
}
My call
writeString("TEST_STRING_TO_WRITE", 0);
only writes TEST into the memory. I do not understand why. Is that because of the _? Or am I missing something different?
Here is the used LENGTH macro
#define LENGTH(x) (sizeof(x)/sizeof(x[0]))
and the method I use to read the string from the memory again (which seems to work correctly):
String readStringFromFlash(int startAddr) {
char in[128];
char curIn;
int i = 0;
curIn = EEPROM.read(startAddr);
for (; i < 128; i++) {
curIn = EEPROM.read(startAddr + i);
in[i] = curIn;
}
return String(in);
}
Where on earth did you get that LENGTH macro from? It’s surreal.
sizeof will not do what you want here. It’s a compile-time function that computes the storage requirements of its argument. In this case it should return the length in bytes of a character pointer, not the string it points to.
You want to use strlen(), assuming your char* is a properly terminated C string. Add one to make sure the ‘\0’ at the end gets stored, too.
#define LENGTH(x) (strlen(x) + 1)
Below is the code to demonstrate the storing as well as retrieving of the string ssid in the EEPROM (permanent storage).
#include "EEPROM.h"
int addr = 0;
#define EEPROM_SIZE 64
// the sample text which we are storing in EEPROM
char ssid[64] = "CARNIVAL OF RUST";
void setup() {
Serial.begin(115200);
Serial.println("starting now...");
if (!EEPROM.begin(EEPROM_SIZE)) {
Serial.println("failed to init EEPROM");
while(1);
}
// writing byte-by-byte to EEPROM
for (int i = 0; i < EEPROM_SIZE; i++) {
EEPROM.write(addr, ssid[i]);
addr += 1;
}
EEPROM.commit();
// reading byte-by-byte from EEPROM
for (int i = 0; i < EEPROM_SIZE; i++) {
byte readValue = EEPROM.read(i);
if (readValue == 0) {
break;
}
char readValueChar = char(readValue);
Serial.print(readValueChar);
}
}
void loop() {
}

Arduino 1 not reading correctly the string from Serial Monitor

I am having a problem with the serial monitor on Arduino Uno.
Basically I want to write some commands on the Serial Monitor, read the string and according to the string do something.
The problem is the following: supposing I type the command 'read 4' in the Serial Monitor, sometimes the string is read correctly, sometimes it is read like: 'ead 4', missing the first character.
I even put a delay between two readings from the Serial Monitor. Does anyone have an explanation?
For completeness I post my code (basically it reads/writes from/to the EEPROM: for example 'read 5' will read the 5 block of EEPROM, 'write 4 5' will write the value 5 to the 4th block of memory).
#define MAX_STRING_LENGTH 14
#include <ctype.h>
#include <EEPROM.h>
//The function initializes the string to spaces
void initString(char* mystr, char strLength);
//The function returns true if it is a read operation, false otherwise
boolean isReadEEPROM(char *myStr, char strLength);
//The function returns true if it is a write operation, false otherwise
boolean isWriteEEPROM(char *myStr, char strLength);
//The function returns the EEPROM address from the string
unsigned int findAddress(char *myStr, char strLength);
char findValue(char *myStr, char strLength);
//Check the address range
boolean isAddressOk(unsigned int address);
void setup() {
// put your setup code here, to run once:
Serial.begin(9600);
}
void loop() {
char pos = 0;
bool newDataFound = false;
char serialStr[MAX_STRING_LENGTH];
unsigned int address = 0;
char val = 0;
while(Serial.available()){
val = Serial.read();
}
val = 0;
initString(&serialStr[0], (char) MAX_STRING_LENGTH);
while(Serial.available() && pos < MAX_STRING_LENGTH){
serialStr[pos] = Serial.read();
pos ++;
newDataFound = true;
delay(200);
}
if (newDataFound){
Serial.print("New Command found: ");
Serial.println(serialStr);
address = 0;
address = findAddress(&serialStr[0], MAX_STRING_LENGTH);
if (isReadEEPROM(&serialStr[0], MAX_STRING_LENGTH) && isAddressOk(address)){
Serial.println("Reading from EEPROM");
Serial.print("Address is ");
Serial.println(address);
val = EEPROM.read(address);
Serial.print("Value is: ");
Serial.println( (uint8_t) val );
Serial.println(" ");
}
else if (isWriteEEPROM(&serialStr[0], MAX_STRING_LENGTH) && isAddressOk(address)){
Serial.println("Writing to EEPROM");
Serial.print("Address is ");
Serial.println(address);
Serial.println(" ");
val = findValue(&serialStr[0], MAX_STRING_LENGTH);
EEPROM.write(address, val);
}
else{
if (!isAddressOk(address)){
Serial.write(address);
Serial.println("Address out of range");
Serial.println("");
}
Serial.println("Not recognized operation\n");
}
delay(2000);
}
}
void initString(char* mystr, char strLength){
for(char ii=0; ii<strLength; ii++){
(*mystr) = ' ';
mystr++;
}
}
//The function returns true if it is a read operation, false otherwise
boolean isReadEEPROM(char *myStr, char strLength){
//The string should contain first the 'read' operation
char expected[] = "read";
int ii =0;
while (ii<4){
if ( *(myStr + ii) != expected[ii]){
return false;
Serial.println("Not a Read Operation\n");
}
ii++;
}
return true;
Serial.println("Read Operation");
}
//The function returns true if it is a write operation, false otherwise
boolean isWriteEEPROM(char *myStr, char strLength){
//The string should contain first the 'read' operation
char expected[] = "write";
int ii =0;
while (ii<5){
if ( *(myStr + ii) != expected[ii]){
return false;
}
ii++;
}
return true;
}
//The function returns the EEPROM address from the string
unsigned int findAddress(char *myStr, char strLength){
unsigned int address;
char tmpStr[strLength];
char strAddress[] = " ";
int ii = 0;
while(ii< strLength){
tmpStr[ii] = *(myStr+ii);
ii++;
}
Serial.print("The address found is: ");
Serial.println(strAddress);
ii= 0;
if (isReadEEPROM(myStr, strLength)){
while (ii<=4){
if (isdigit(*(myStr + 5 + ii))){
strAddress[ii] = *(myStr + 5 + ii);
}
else{
break;
}
ii++;
}
address = atoi(strAddress);
}
else if(isWriteEEPROM(myStr, strLength)){
while (ii<=4){
if (isdigit(*(myStr + 6 + ii))){
strAddress[ii] = *(myStr + 6 + ii);
}
else{
break;
}
ii++;
}
address = atoi(strAddress);
}
else{
address = 0;
//Serial.println("Address not available in function 'findAddress'");
}
return address;
}
//The function returns the value to be written to the EEPROM from the string
char findValue(char *myStr, char strLength){
char val;
char tmpStr[strLength];
char strVal[] = " ";
int ii, idx = 0;
while(ii< strLength){
tmpStr[ii] = *(myStr+ii);
ii++;
}
ii= 0;
// first found the first digits corresponding to the address
while (ii<=4){
if (isdigit(*(myStr + 6 + ii))){
;//strAddress[ii] = *(myStr + 6 + ii);
}
else{
ii++;
break;
}
ii++;
}
// now find the value
while (ii<=4+3){
Serial.println(*(myStr + 6 + ii));
if (isdigit(*(myStr + 6 + ii))){
strVal[idx] = *(myStr + 6 + ii);
}
else{
break;
}
ii++;
idx++;
}
Serial.print("original string: ");
Serial.println(tmpStr);
Serial.print("Value found: ");
Serial.println(strVal);
val = (char)atoi(strVal);
return val;
}
boolean isAddressOk(unsigned int address){
if (address < 1024 && address >= 0){
return true;
}
else{
return false;
}
}
This snippet:
char val=0;
while(Serial.available()){
val = Serial.read();
}
val = 0;
Is just consuming any characters that may be left in the input buffer. You could also do:
while (Serial.avaialble())
Serial.read();
The next while loop does not wait for the entire command. Sometimes, it gets the 'r', and then doesn't get the 'ead...' in time. They will be there the next time loop executes, so it looks like the 'r' is missing. It was just consumed in the previous loop.
Things sent over the USB (from the Serial Monitor window) can have odd delays in them.
To gather up a complete line, you should save characters until a '\n' is received:
for (;;) {
if (Serial.available()) {
char c = Serial.read();
if (c == '\n')
break;
if (pos < MAX_LINE_LENGTH) {
serialStr[pos] = c;
pos ++;
}
newDataFound = true;
}
}
The delay call is totally unnecessary, because the for loop waits until a '\n' character is received (be sure that in the Serial Monitor pull down menu either 'New Line' or 'both NL & CR' is selected). Then you know you've read all the characters in the line.

Arduino Sketch - Reading Serial Bytes

I have the following code on my Arduino that constantly checks for a serial command that's sent over TCP using a Wifly library.
What the following code does is split a string like the following when sent over serial:
{power,tv}
It sets these properties accordingly:
char command[32];
char value[32];
It then executes certain methods using sendCommand(command, value); based on the properties set in the loop below.
Keep in mind this works just fine using the Wifly library.
void loop() {
Client client = server.available();
if (client) {
boolean start_data = false;
boolean next = false;
char command[32];
char value[32];
int index = 0;
while (client.connected()) {
if (client.available()) {
char c = client.read();
Serial.print(c);
if (c == '}') {
break;
}
if(start_data == true) {
if(c != ',') {
if(next)
value[index] = c;
else
command[index] = c;
index++;
} else {
next = true;
command[index] = '\0';
index = 0;
}
}
if (c == '{') {
start_data = true;
}
}
}
value[index] = '\0';
client.flush();
client.stop();
sendCommand(command,value);
}
}
Instead of using WiFi I've purchased some Xbee modules. They basically allow you to send serial bytes as well. The only problem is that I'm not quite sure how to handle the looping considering there's no while(client.connected()) anymore. Instead of that I've used while(Serial.available()) thinking that will work, but it doesn't set the value property for some reason.
I get command but I don't get value.
Also I'm not sure whether the loop above is the best way of doing what I'm after, all I know is that it works just fine the way it is. :)
Here is my new loop, which only returns command and not value for some reason:
void loop() {
// if there are bytes waiting on the serial port
if (Serial.available()) {
boolean start_data = false;
boolean next = false;
char command[32];
char value[32];
int index = 0;
while (Serial.available()) {
char c = Serial.read();
Serial.print(c);
if (c == '}') {
break;
}
if(start_data == true) {
if(c != ',') {
if(next)
value[index] = c;
else
command[index] = c;
index++;
} else {
next = true;
command[index] = '\0';
index = 0;
}
}
if (c == '{') {
start_data = true;
}
}
value[index] = '\0';
sendCommand(command,value);
}
}
If the following works with the new loop, I'll be very happy!
void sendCommand(char *command, char *value) {
// do something wonderful with command and value!
}
Got it working by using the following code:
#define SOP '{'
#define EOP '}'
bool started = false;
bool ended = false;
char inData[80];
byte index;
void setup()
{
Serial.begin(9600);
// Other stuff...
}
void loop()
{
// Read all serial data available, as fast as possible
while(Serial.available() > 0)
{
char inChar = Serial.read();
if(inChar == SOP)
{
index = 0;
inData[index] = '\0';
started = true;
ended = false;
}
else if(inChar == EOP)
{
ended = true;
break;
}
else
{
if(index < 79)
{
inData[index] = inChar;
index++;
inData[index] = '\0';
}
}
}
// We are here either because all pending serial
// data has been read OR because an end of
// packet marker arrived. Which is it?
if(started && ended)
{
// The end of packet marker arrived. Process the packet
char *cmd = strtok(inData, ",");
if(cmd)
{
char *val = strtok(NULL, ",");
if(val)
{
sendCommand(cmd, val);
}
}
// Reset for the next packet
started = false;
ended = false;
index = 0;
inData[index] = '\0';
}
}
I would change the structure similar to:
while( c != '}') {
if (Serial.available()) {
.
.
.
}
}
Serial characters are received significantly slower then the loop.

Why recv() in winsock client cannot get any data once httpRetransmition happens?

I am trying to record the time between 'http request' package and 'http response' package.
I write an socket client using winsock. The code is below
if (send(sock, request.c_str(), request.length(), 0) != request.length())
die_with_error("send() sent a different number of bytes than expected");
// Record the time of httpRequestSent
::QueryPerformanceCounter(&httpRequestSent);
::QueryPerformanceFrequency(&frequency);
//get response
response = "";
resp_leng= BUFFERSIZE;
http_leng= 381;
while(resp_leng==BUFFERSIZE||http_leng>0)
{
resp_leng= recv(sock, (char*)&buffer, BUFFERSIZE, 0);
http_leng= http_leng - resp_leng;
if (resp_leng>0)
response+= string(buffer).substr(0,resp_leng);
//note: download lag is not handled in this code
}
::QueryPerformanceCounter(&httpResponseGot);
//display response
cout << response << endl;
// Display the HTTP duration
httpDuration = (double)(httpResponseGot.QuadPart - httpRequestSent.QuadPart) / (double)frequency.QuadPart;
printf("The HTTP duration is %lf\n", httpDuration);
The code works nicely except one situation: HTTP Retransmition. I used wireshark to monitor packages and found out once there is a retransmition the code seems block on recv(), but cannot get any data from the socket buffer. I wonder why would this happen. Could somebody explain the reasons?
Any help will be appreciated.
Here is a second answer with more dynamic buffer handling and more error checking:
void send_data(SOCKET sock, void *data, unsigned int data_len)
{
unsigned char *ptr = (unsigned char*) data;
while (data_len > 0)
{
int num_to_send = (int) std::min(1024*1024, data_len);
int num_sent = send(sock, ptr, num_to_send, 0);
if (num_sent < 0)
{
if ((num_sent == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
continue;
die_with_error("send() failed");
}
if (num_sent == 0)
die_with_error("socket disconnected");
ptr += num_sent;
data_len -= num_sent;
}
}
unsigned int recv_data(SOCKET sock, void *data, unsigned int data_len, bool error_on_disconnect = true)
{
unsigned char *ptr = (unsigned char*) data;
unsigned int total = 0;
while (data_len > 0)
{
int num_to_recv = (int) std::min(1024*1024, data_len);
int num_recvd = recv(sock, ptr, num_to_recv, 0);
if (num_recvd < 0)
{
if ((num_recvd == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
continue;
die_with_error("recv() failed");
}
if (num_recvd == 0)
{
if (error_on_disconnect)
die_with_error("socket disconnected");
break;
}
ptr += num_recvd;
datalen -= num_recvd;
total += num_recvd;
}
while (true);
return total;
}
std::string recv_line(SOCKET sock)
{
std::string line;
char c;
do
{
recv_data(sock, &c, 1);
if (c == '\r')
{
recv_data(sock, &c, 1);
if (c == '\n')
break;
line += '\r';
}
else if (c == '\n')
break;
line += c;
}
return line;
}
void recv_headers(SOCKET sock, std::vector<std::string> *hdrs)
{
do
{
std::string line = recv_line(sock);
if (line.length() == 0)
return;
if (hdrs)
hdrs->push_back(line);
}
while (true);
}
unsigned int recv_chunk_size(SOCKET sock)
{
std::string line = recv_line(sock);
size_t pos = line.find(";");
if (pos != std::string::npos)
line.erase(pos);
char *endptr;
unsigned int value = strtoul(line.c_str(), &endptr, 16);
if (*endptr != '\0')
die_with_error("bad Chunk Size received");
return value;
}
std::string find_header(const std::vector<std::string> &hrds, const std::string &hdr_name)
{
std::string value;
for(size_t i = 0; i < hdrs.size(); ++i)
{
const std::string hdr = hdrs[i];
size_t pos = hdr.find(":");
if (pos != std::string::npos)
{
if (hdr.compare(0, pos-1, name) == 0)
{
pos = hdr.find_first_not_of(" ", pos+1);
if (pos != std::string::npos)
return hdr.substr(pos);
break;
}
}
}
return "";
}
{
// send request ...
std::string request = ...;
send_data(sock, request.c_str(), request.length());
// Record the time of httpRequestSent
::QueryPerformanceCounter(&httpRequestSent);
::QueryPerformanceFrequency(&frequency);
// get response ...
std::vector<std::string> resp_headers;
std::vector<unsigned char> resp_data;
recv_headers(sock, &resp_headers);
std::string transfer_encoding = find_header(resp_headers, "Transfer-Encoding");
if (transfer_encoding.find("chunked") != std::string::npos)
{
unsigned int chunk_len = recv_chunk_size(sock);
while (chunk_len != 0)
{
size_t offset = resp_data.size();
resp_data.resize(offset + chunk_len);
recv_data(sock, &resp_data[offset], chunk_len);
recv_line(sock);
chunk_len = recv_chunk_size(sock);
}
recv_headers(sock, NULL);
}
else
{
std::string content_length = find_header(resp_headers, "Content-Length");
if (content_length.length() != 0)
{
char *endptr;
unsigned int content_length_value = strtoul(content_length.c_str(), &endptr, 10);
if (*endptr != '\0')
die_with_error("bad Content-Length value received");
if (content_length_value > 0)
{
resp_data.resize(content_length_value);
recv_data(sock, &resp_data[0], content_length_value);
}
}
else
{
unsigned char buffer[BUFFERSIZE];
do
{
unsigned int buffer_len = recv_data(sock, buffer, BUFFERSIZE, false);
if (buffer_len == 0)
break;
size_t offset = resp_data.size();
resp_data.resize(offset + buffer_len);
memcpy(&resp_data[offset], buffer, buffer_len);
}
while (true)
}
}
::QueryPerformanceCounter(&httpResponseGot);
// process resp_data as needed
// may be compressed, encoded, etc...
// Display the HTTP duration
httpDuration = (double)(httpResponseGot.QuadPart - httpRequestSent.QuadPart) / (double)frequency.QuadPart;
printf("The HTTP duration is %lf\n", httpDuration);
}
You are not doing adequate error checking on the calls to send() and recv(). Try something like this instead:
char *req_ptr = request.c_str();
int req_leng = request.length();
int req_index = 0;
do
{
int req_sent = send(sock, req_ptr, req_leng, 0);
if (req_sent < 1)
{
if ((req_sent == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
continue;
die_with_error("send() failed");
}
req_ptr += req_sent;
req_leng -= req_sent;
}
while (req_leng > 0);
// Record the time of httpRequestSent
::QueryPerformanceCounter(&httpRequestSent);
::QueryPerformanceFrequency(&frequency);
//get response
std::string response;
int resp_leng = BUFFERSIZE;
int http_leng = -1;
bool http_leng_needed = true;
do
{
if (http_leng_needed)
{
std::string::size_type pos = response.find("\r\n\r\n");
if (pos != std::string::npos)
{
std::string resp_hdrs = response.substr(0, pos);
// look for a "Content-Length" header to see
// if the server sends who many bytes are
// being sent after the headers. Note that
// the server may use "Transfer-Encoding: chunked"
// instead, which has no "Content-Length" header...
//
// I will leave this as an excercise for you to figure out...
http_leng = ...;
// in case body bytes have already been received...
http_leng -= (response.length() - (pos+4));
http_leng_needed = false;
}
}
if (http_leng_needed)
resp_leng = BUFFERSIZE;
else
resp_leng = min(http_leng, BUFFERSIZE);
if (resp_leng == 0)
break;
resp_leng = recv(sock, buffer, resp_leng, 0);
if (resp_leng < 1)
{
if ((resp_leng == SOCKET_ERROR) && (WSAGetLastError() == WSAEWOULDBLOCK))
continue;
die_with_error("send() failed");
}
response += string(buffer, resp_leng);
if (!http_leng_needed)
http_leng -= resp_leng;
}
while ((http_leng_needed) || (http_leng > 0));
::QueryPerformanceCounter(&httpResponseGot);
//display response
cout << response << endl;
// Display the HTTP duration
httpDuration = (double)(httpResponseGot.QuadPart - httpRequestSent.QuadPart) / (double)frequency.QuadPart;
printf("The HTTP duration is %lf\n", httpDuration);
With this said, the "correct" way to handle HTTP in general is to read the inbound data line by line, rather than buffer by buffer, until you encounter the end of the response headers, then you can read the rest of the data buffer by buffer based on the data length indicated by the headers.

Resources