【问题标题】:How to connect Arduino to C++ using sockets?如何使用套接字将 Arduino 连接到 C++?
【发布时间】:2018-10-11 02:04:33
【问题描述】:

我的 C++ 客户端套接字代码只收集 Arduino 套接字服务器提供的输出的第一行(我想我可以这么称呼它)。你能告诉我我的 C++ 代码哪里出了问题以及如何解决它吗?如果问题太冗长,请跳到底部的 C++ 代码。

硬件设置:带有以太网卡(屏蔽)的 Arduino Mega 和带有 Ubuntu 16.04 的英特尔 NUC。这两个设备使用电缆和非管理型交换机连接。

Arduino Side:我从 Arduino 以太网库中的一个 Web 服务器示例开始,并修改了代码,直到我能够收集所有 I/O 的状态,处理 I/O数据,并将结果提供给 Web 客户端。下图显示了我的 Arduino 输出的 HTML 快照。

处理套接字服务器端的 Arduino 代码如下(一切正常,但认为包含显示一切):

#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");
  }
}

带有 Ubuntu 16.04 和 C++11 的 Intel NUC:我一直在关注教程 (https://www.binarytides.com/socket-programming-c-linux-tutorial/) 来了解套接字客户端,并从 https://www.learncpp.com 学习 C++。到目前为止,我能够向 Google 服务器发送请求并使用我的套接字收集 HTML 页面,并将 HTML 打印到终端:

这是我的 C++ 代码:

#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;
 }

问题:当我在我的 C++ 程序中将 IP 地址从 Google 更改为我的 Arduino 时,cpp 客户端套接字程序只收集 Arduino 套接字服务器输出的第一行。我知道这是第一行,因为我通过添加“..但没关系”修改了 Arduino 服务器的第一行,并且更改显示在 c++ 程序的标准输出窗口中。我需要 C++ 程序来收集整个输出,而不仅仅是第一行。对于我的一生,我无法弄清楚如何。

您能帮我解决以下问题吗:

  1. 如何收集整个 Arduino 消息(而不仅仅是第一行)?我需要对我的 C++ 程序进行哪些修改?系统需要能够将数据从一个设备传递到另一个设备。
  2. 此设置的全部目标是将 2 个浮点数和 6 个整数从 Arduino 传递到我的 C++ 程序。很快我将取消整个 HTML 的东西。在传递数据时,您会推荐我使用哪种协议?我正在考虑用字母填充每个值。例如:“Aint1A Bint2B Cint3C Dfloat1D ...”等等。您能否推荐一些帖子/教程/网页,这些帖子/教程/网页建议了打包和处理通过套接字到达 C++ 程序的数据的最佳方式?

我提前为“fix-my-codez”问题道歉,但我读过的所有问题对我来说都太高级了,因为它们涉及缓冲区溢出、安全性、字节序问题、错误处理、格式错误的消息等on,这远远超出了我的需要(能力可能更准确)。非常感谢您的时间和帮助。

【问题讨论】:

  • recv() 不保证一次性返回所有数据。您需要循环调用它。
  • 您的 C++ 代码实际上并未实现 HTTP 协议。特别是,没有任何代码可以接收 HTTP 回复。因此,我们的代码无法接收 HTTP 回复,而是从 TCP 流中接收到任意数据块也就不足为奇了。
  • @Mike 您的 HTTP 请求格式错误(缺少必需的 Host 标头)。但更重要的是,您只调用了 1 次 recv(),因此您会收到最大 1..20000 字节之间的任何位置,其中 MAYMAY NOT(可能不)包含一口气完整回复。您需要循环调用recv(),直到到达响应的实际结尾。这对于 HTTP 来说并不简单,因为 HTTP 是一种具有消息传递结构的协议,而您根本没有遵循该结构,甚至没有接近。您必须改用 logic like this pseudo code

标签: c++ sockets arduino


【解决方案1】:

非常感谢 Jonathan Potter 和 Remy Lebeau 在 cmets 中提供了有效的答案。我执行了您的建议/答案,一切正常。 Arduino代码我没碰,CPP代码做了如下改动:

在问题的 CPP 代码中,从char buffer [20000] = {}; 中删除所有内容(包括)并将其替换为:

//***********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;

这些变化(我确信可以从这里到月球再回来批评)使我能够获得 arduino 发送的数据而不会丢失一个 ASCII 字符。谢谢!

另外,感谢 David Schwartz 和 Remy Lebeau 指出我使用的 HTTP 协议非常糟糕。我使用了 HTTP,因为我知道它是 Arduino 代码中的一个工作示例;现在的目标是删除 HTML 并找到一种更有效的方式将值传递给 cpp 代码(仅使用套接字)。非常感谢您的 cmets!

****编辑****

好的,所以如果你还在阅读这篇文章,你必须将一些信息从 Arduino 传递到使用以太网的 cpp 程序。如果是这样,请继续阅读。在指导我如何接收完整的 Arduino 响应后,我删除了所有 HTML 和 HTTP,并简单地从 Arduino 发送了我需要的值,并带有字母填充(例如:Aint1B、Cint2D、Efloat1F 等)。我用~~~ 字符标记了来自Arduino 的传输结束。凉爽的。但是,由于某种原因,有时我会得到整个 Arduino 响应,有时它会丢失一些消息的尾部。这是我学到的:

  1. recv 读取的位置(我不知道的内存或系统调用)有时可能只有一个 char 值。
  2. 有时\n 字符都是recv 提取的!
  3. 有时recv 读取的位置可以有多个值!有时 recv 方法返回 6 个字符,有时只返回 4 个字符。这种行为似乎无法预测。

考虑到这种行为,我修改了我的 cpp 代码。此代码接收整个消息,当它接收到时,recv 方法的额外不必要循环将停止。希望对您有用:

//***********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;

【讨论】:

    猜你喜欢
    • 2014-10-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-09-18
    相关资源
    最近更新 更多