My C++ client side socket code only collects the very first line of the output provided by an Arduino socket server (I guess I can call it that). Can you please tell me where I am going wrong with my C++ code and how to fix it? If the question is too verbose please jump to the C++ code at the bottom.
Hardware setup : Arduino Mega with an Ethernet card (shield) and an Intel NUC with Ubuntu 16.04. The two devices are connected using cable and an unmanaged switch.
Arduino Side : I started out with a web server example from the Arduino Ethernet library, and modified the code until I was able to collect the status of all I/O, process the I/O data, and make results available to a web client. Image below shows a snapshot of the HTML my Arduino dishes out.
The Arduino code that handles the socket server side of things is as follows (everything works, but thought to include to show everything):
#include <SPI.h>
#include <Ethernet.h>
byte mac[] = {0xBA, 0xDA, 0x55, 0x12, 0x34, 0x56};
IPAddress ip(192, 168, 0, 21);
EthernetServer server(80);
void setup()
{
Ethernet.begin(mac, ip);
// Check for Ethernet hardware present
if (Ethernet.hardwareStatus() == EthernetNoHardware)
{
Serial.println("Ethernet shield was not found. Sorry, can't run without hardware.");
while (true)
{
delay(1); // do nothing, no point running without Ethernet hardware
}
}
if (Ethernet.linkStatus() == LinkOFF)
{
Serial.println("Ethernet cable is not connected.");
}
server.begin();
}
void loop()
{
//true when there is an incoming connection
if (client)
{
Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected())
{
if (client.available())
{
//the next two lines print the client HTTP GET request to serial.
char c = client.read();
Serial.write(c);
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank)
{
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close");
client.println("Refresh: 1");
client.println();
client.println("<!DOCTYPE HTML>");
client.println("<html>");
//whole bunch of client.println("...."); to dish out a web page.
client.println("</html>");
break;
}
if (c == '\n')
{
// you're starting a new line
currentLineIsBlank = true;
} else if (c != '\r')
{
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
//delay(1);
// close the connection:
client.stop();
Serial.println("client disconnected");
}
}
Intel NUC with Ubuntu 16.04 and C++11 : I have been following a tutorial ( https://www.binarytides.com/socket-programming-c-linux-tutorial/ ) to figure out the socket client side, and learning C++ from https://www.learncpp.com . So far I am able to send a request to a Google server and collect the HTML page using my socket, and print the HTML to the terminal:
Here is my C++ code:
#include <iostream> //for std::cout & std::endl
#include<arpa/inet.h> //inet_addr
#include<string.h> // for strlen
int main()
{
int my_socket = socket(AF_INET , SOCK_STREAM , 0);
//make sure the socket we got is OK
if (my_socket == -1)
{
std::cout << "problem creating a socket." << std::endl;
}
struct sockaddr_in connectionToServer;
connectionToServer.sin_addr.s_addr = inet_addr("172.217.2.99");//google IP
connectionToServer.sin_family = AF_INET; //type of IP addres. where AF_INET is IPv4
connectionToServer.sin_port = htons(80); //port is set via a method
if (connect(my_socket , (struct sockaddr *)&connectionToServer , sizeof(connectionToServer)) < 0)
{
std::cout << "connect error" << std::endl;
return 1;
}
std::cout << "Connected" << std::endl;
//send a request to get a page
char *message = "GET / HTTP/1.1\r\n\r\n";
if( send(my_socket , message , strlen(message) , 0) < 0)
{
std::cout << "Send failed" << std::endl;
return 1;
}
std::cout << "Data Sent\n" << std::endl;
char buffer [20000] = {};
if( recv(my_socket, buffer , sizeof(buffer) , 0) < 0)
{
std::cout << "recv failed" << std::endl;
}
for(int i=0; i<20000 ; i++)
{
std::cout<< buffer[i];
}
return 0;
}
Problem : when I change the IP address from Google to my Arduino in my C++ program, the cpp client socket program only collects the very first line that the Arduino socket server outputs. I know it is the very first line as I modified the first line the Arduino server dishes out by adding "..but it doesn't matter" and the change showed up in the standard output window of the c++ program. I need the C++ program to collect the entire output, not just the first line. for the life of me I can't figure out how.
Could you please help me with the following questions:
I apologize in advance for the 'fix-my-codez' question, but all the questions I have read are a bit too advanced for me as they address buffer overflows, security, endian issues, error handling, malformed messages and so on, which is way beyond my needs (ability is probably more accurate). Thank you very much for your time and help.
A huge thanks to Jonathan Potter and Remy Lebeau for providing a working answer in the comments. I carried out your suggestions/answers and everything worked. I did not touch the Arduino code, and made the following changes in the CPP code:
In the CPP code in the question remove everything from (and including) char buffer [20000] = {};
and replace it with:
//***********receive the results**************************
//initialize a string
std::string totalResults;
//create a temp array. This will hold a single line recieved from
//the arduino.
char tempBuffer [300] = {};
//the number of lines I want to recieve from the arduino
int magicNumber = 100;
//recieve the arduino response
for(int i = 0; i < magicNumber ; i++)
{
//call the recv method over and over as it gets a single arduino line with
//every iteration.
if( recv(my_socket, tempBuffer , sizeof(tempBuffer) , 0) < 0)
{
std::cout << "recv failed" << std::endl;
}
//write out the single line we recieved to a string (which grows on the fly)
for(int i = 0; i < 300; i++ )
{
totalResults = totalResults+tempBuffer[i];
//kill the loop the moment there is a null character. When i created the
//array i initialized it with NULL characters. so if I am seeing a null
//character it means that the data I recieved from the arduino has all been
//given to the string.
if(tempBuffer[i] == NULL)
{
break;
}
}
//empty array - see: https://stackoverflow.com/questions/632846/clearing-a-char-array-c
std::fill(&tempBuffer[0], &tempBuffer[300], 0);
}
//print the results to the standard output.
std::cout << totalResults << std::endl;
These changes (which I am sure can be critiqued from here to the moon and back) allowed me to get the data the arduino was sending without missing a single ASCII character. Thanks!
On a separate note, thank you to David Schwartz and Remy Lebeau for pointing out that the HTTP protocol I was using is pretty bad. I used the HTTP as I knew it was a working example in the Arduino code; and now the objective is to remove the HTML and to find a more efficient way to pass the values to the cpp code (using only sockets). Thank you very much for your comments!
****EDIT****
OK, so if you are still reading this, you must be passing some information from an Arduino to a cpp program using Ethernet. If so read on. After I was guided on how to receive the full Arduino response, I removed all the HTML and HTTP, and simply sent the values I needed from the Arduino with letter padding (ex: Aint1B, Cint2D, Efloat1F, etc...). I marked the end of transmission from the Arduino with ~~~
characters. Cool. But, for some reason sometimes I would get the entire Arduino response, and sometimes it would be missing some amount of the tail of the message. Here is what I learned:
\\n
characters is all a recv fetches! Taking this behavior into account I modified my cpp code. This code receives the entire message, and when it does, the additional needless looping of the recv method is stopped. Hope you find it useful:
//***********receive the results**************************
//initialize a string
std::string totalResults = "";
//create a temp array. This will hold a single line recieved from
//the arduino.
char tempBuffer [300] = {};
//the number of lines I want to receive from the Arduino. This is an unusual
//value for the following reasons (figured out via println):
//(1) sometimes the buffer where the recv method reads from has only one value.
// ex: letter A only (as per my,*ahem", "protocol".
//(2) sometimes the \n is all a recv fetches!
//(3) sometimes the buffer where the recv method reads has multiple values, so
// the recv fetches many items that get unpacked in the second loop. This is
// why sometimes we increase the value by only 1, but get WAY more values. I
// observed this behaviour to be non repeating. Sometimes it reads 5 values,
// and sometimes it reads only 3 values.
// At a value of 60 I am always getting the message, and run the recv command
// unnecesserily. For this reason I have implemented the "end transmission"
// characters (~~~), which allow me to kill the for loop once the full message is
// retrieved.
int numberOfTimesRecvRuns = 60;
//number of characters per line. do not reduce as it is needed to be this size to
// get the full insult if the protocol is not followed.
int arduinoNumberOfCharsPerLine = 50;
bool fullResponseRecieved = false;
//recieve the entire arduino response. The magic number is the number of times
// we call the recv method (which reads a line from the socket).
for(int i = 0; i < numberOfTimesRecvRuns; i++)
{
//call the recv method over and over as it gets a single arduino line with
//every iteration.
if( recv(my_socket, tempBuffer , sizeof(tempBuffer) , 0) < 0)
{
std::cout << "recv failed" << std::endl;
}
//write out the single line we recieved to a string (which grows on the fly). 300 because
//i dont believe I will have more than 300 characters per line.
for(int j = 0; j < arduinoNumberOfCharsPerLine; j++ )
{
totalResults = totalResults+tempBuffer[j];
std::cout << "i: " << j << " value recv read: " << tempBuffer[j]<< std::endl;
//kill the loop the moment there is a null character. When i created the
//array i initialized it with NULL characters. so if I am seeing a null
//character it means that the data I recieved from the arduino has all been
//given to the string.
if(tempBuffer[j] == NULL )
{
std::cout << "I ran... See ya" << std::endl;
break;
}
//end of transmission detected
if(tempBuffer[j] == '~')
{
fullResponseRecieved = true;
}
}
//empty array - see: https://stackoverflow.com/questions/632846/clearing-a-char-array-c
std::fill(&tempBuffer[0], &tempBuffer[300], 0);
// A '~' character means the full message has been recieved and there is no
// need to keep looping for the purpose of running the recv method.
if(fullResponseRecieved == true)
{
//reset the value
fullResponseRecieved = false;
std::cout << "killing recv loop" << std::endl;
break;
}
}
//print the results to the standard output.
std::cout << totalResults << std::endl;
return 0;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.