在 C 中,将名称=值对拆分为数组

in C, split name=value pairs into arrays

我在 Arduino 上编程,其中程序是用 C 编写的。我正在接收到一个字符串的 HTTP GET 响应,并希望将请求正文中返回的 name/value 对分隔成一个 C多维数组,这样我就可以遍历它并更新内容。

这是我收到的我想要处理的示例数据:

HTTP/1.1 200 OK
Date: Sun, 10 Oct 2010 23:26:07 GMT
Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g
Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT
Accept-Ranges: bytes
Content-Length: 123
Connection: close
Content-Type: text/plain

var1=red&var2=green&var3=up&var5=down&time=123443291&key=xmskwirrrr3

我用 strtok 做了几次测试都没有成功...我是初学者 C 程序员。

这就是我最终想要达到的结果:

config[0][0] = var1
config[0][1] = red

config[1][0] = var2
config[1][1] = green

config[2][0] = var3
config[2][1] = up
...

我什至不知道我这样做是否正确,但是 HTTP 响应中的 name/value 对需要更新此远程硬件上的一些变量...更新其配置通过用新收到的更新变量。 如果 names+values 可以进入数组,或者即使只是将局部变量 NAME 设置为 VALUE 的值也失败......那将起作用。

这是我到目前为止尝试过的 C 脚本:http://tpcg.io/qqvuLW

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="HTTP/1.1 200 OK\n\
Date: Sun, 10 Oct 2010 23:26:07 GMT\n\
Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g\n\
Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT\n\
ETag: \"45b6-834-49130cc1182c0\"\n\
Accept-Ranges: bytes\n\
Content-Length: 12\n\
Connection: close\n\
Content-Type: text/html\n\
\n\
#var1=red&var2=green&var3=up&var5=down&time=123443291&key=xmskwirrrr3";
  char * pch;
  //printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str,"#"); //i had the idea of splitting the headers off by using a # at the beginning of the name/value response
  while (pch != NULL)
  {
    printf ("%s\n",pch);
    pch = strtok (NULL, "&");
    //if the names+values could get into arrays, or failing that even just set the local variable NAME to the value of VALUE... that would work.
  }
  return 0;
}

在使用 # 进行分词后,您必须使用 = 而不是 &

进行分词

代码如下:

/* strtok example */
#include <stdio.h>
#include <string.h>

int main ()
{
  char str[] ="HTTP/1.1 200 OK\n\
  Date: Sun, 10 Oct 2010 23:26:07 GMT\n\
  Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g\n\
  Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT\n\
  ETag: \"45b6-834-49130cc1182c0\"\n\
  Accept-Ranges: bytes\n\
  Content-Length: 12\n\
  Connection: close\n\
  Content-Type: text/html\n\
  \n\
  #var1=red&var2=green&var3=up&var5=down&time=123443291&key=xmskwirrrr3";
  char * pch;
  int pairs_count = 0;
  int max_pairs = 20;
  int max_var = 50;
  char config[max_pairs][2][max_var];
  //printf ("Splitting string \"%s\" into tokens:\n",str);
  pch = strtok (str,"#"); //i had the idea of splitting the headers off by using a # at the beginning of the name/value response
  while (pch != NULL)
  {
    pch = strtok(NULL, "=");
    if (pch == NULL) break;
    strcpy(config[pairs_count][0], pch);

    pch = strtok(NULL, "&");
    if (pch == NULL) break;
    strcpy(config[pairs_count][1], pch);

    pairs_count++;
    //if the names+values could get into arrays, or failing that even just set the local variable NAME to the value of VALUE... that would work.
  }
  for(int i=0;i<pairs_count;i++) {
      printf("%s = %s\n", config[i][0], config[i][1]);
  }
  return 0;
}

输出:

var1 = red
var2 = green
var3 = up
var5 = down
time = 123443291
key = xmskwirrrr3

可能的方法

存储数据基本上有两种不同的方法:

  • 您可以将指针存储到原始 strtok 字符串中
  • 您可以将字符串从原始的 strtok 字符串复制到数组中

这取决于原始 strtok 字符串的生命周期 - 如果数组中的数据需要比字符串更长寿,那么您就不能存储指针。但是,如果可以存储指针,则不必为字符串分配两次内存,从而节省内存。

因此,在我的操作示例中,我将存储指针。如果那是你现在想要的,你可以为数组分配额外的维度并使用 strcpy 来保存实际值。

如何理解 HTTP 消息

在 HTTP 消息中,消息中除 body 之外的所有行都必须以回车符 return 和换行符结束。参见 https://whosebug.com/a/27966412/2193968 and https://whosebug.com/a/5757349/2193968

如果你想在你的程序中对消息进行硬编码,你可能认为你可以只输入一个嵌入 end-of-line 个字符的长字符串 - 就像这样:

char * string = "GET / HTTP/1.1\r\
Host: hostname\r\
\r\
body";

它甚至可能会奏效——如果你幸运的话。但是你怎么知道嵌入的 end-of-line 字符是什么字符呢?这取决于您的编辑器(Unicode 编辑器将使用与 ANSI 编辑器不同的东西)并且它取决于您的 OS(Windows 使用与 Linus 不同的东西)。做出这样的假设是不好的。除此之外,它会扰乱语法高亮显示并混淆每个 body 查看您的代码的人。 但是该语言已经预料到并且会自动连接连续的字符串:

char * string = "GET / HTTP/1.1\r\n"
                "Host: hostname\r\n"
                "\r\n"
                "body";

因此,要在body中查找实际数据,body不需要包含特殊字符,如#,因为我们可以在消息中查找第一个空行旨在将 header 与 body 分开。识别该除法的方法是找到一个回车 return 和紧跟另一个回车的换行对。它必须是消息中的第一个这样的序列,因为 body 也可能包含该字符序列。

现在,body 可能包含 URL 编码的表单数据,这对于您问题中的消息可能总是正确的,但并不总是正确的。您需要检查 Content-Type header 以获得 body 的实际类型。我假设在我的示例中不需要检查它,但这是您应该注意的事情。另一个重要的基调是 Content-Length header - 它包含 body 中数据的字节数(不包括 header 和空行) For URL 像您这样的编码表单数据可能无关紧要,但对于任何其他 Content-Type 它可能无关紧要。 URL 编码表单数据的正确 Content-Type 是 application/x-www-form-urlencoded 请参见 https://whosebug.com/a/14551320/2193968

如何解析 URL 编码形式的 HTTP 正文

因此,一旦我们获得指向 body 的字符串指针(通过查找消息中的第一个空行),我们就可以对字符串进行 strtok。 URL 编码的表单数据具有由 = 分隔的键和值以及由 & 分隔的 KVP,我们可以使用分隔符字符串 "=&" 调用 strtok 除非值包含等号我们确定我们是否获取键或值与数据不同步。因此,如果我们寻找我们期望找到的特定角色并保持同步会更好——始终知道我们在哪里。

所以在我的示例中,我根据存储的是键还是值来替换分隔符。

密码

您可以在这里尝试:https://www.onlinegdb.com/Symtd53LB

希望代码中的注释对您有所帮助。如果不评论,我会添加一些细节。

#include <stdio.h>
#include <string.h>

int main ()
{
  // in valid HTTP, each line of the header and the blank
  // line must end with a carraige return and linefeed
  char str[] =  "HTTP/1.1 200 OK\r\n"
                "Date: Sun, 10 Oct 2010 23:26:07 GMT\r\n"
                "Server: Apache/2.2.8 (Ubuntu) mod_ssl/2.2.8 OpenSSL/0.9.8g\r\n"
                "Last-Modified: Sun, 26 Sep 2010 22:04:35 GMT\r\n"
                "ETag: \"45b6-834-49130cc1182c0\"\r\n"
                "Accept-Ranges: bytes\r\n"
                "Content-Length: 12\r\n"
                "Connection: close\r\n"
                "Content-Type: text/html\r\n"
                "\r\n"
                "var1=red&var2=green&var3=up&var5=down&time=123443291&key=xmskwirrrr3";

  // so, we want to find the blank line and then move to the next non-blank line
  // no error checking has been done...
  char *data = strstr(str,"\r\n\r\n")+strlen("\r\n\r\n");
  printf("\nBody = %s\n", data);

  // we will use an array of 1000 pairs of pointers 
  // so we can only handle 1000 KVP max...
  char *pointers[1000][2] = {}; // default initialise all of them to MULL

  // we need to keep track of the KVP number
  // KVP starts at 0 and might go to 999
  int KVP = 0;

  // and a flag that indicates whether it is the key or value we are parsing
  // key when key_value=0, value when key_value=1
  #define KVP_KEY 0
  #define KVP_VALUE 1
  int key_value = KVP_KEY; // the key should be first

  // first strtok the first value
  // we need to do this because the first time strtok needs different parameters
  pointers[KVP][key_value] = strtok(data,"=");

  // loop through splitting it up and saving the pointers
  // no error checking has been done...
  while(KVP < 1000 && pointers[KVP][key_value] != NULL)
  {
    // ok, we need to update KVP and key_value
    if (key_value == KVP_VALUE)
    {
      KVP++;
      key_value = KVP_KEY;
      pointers[KVP][key_value] = strtok(NULL,"=");
    }
    else // if (key_value == KVP_KEY)
    {
      key_value = KVP_VALUE;
      pointers[KVP][key_value] = strtok(NULL,"&");
    }
  }

  // now it should all be parsed
  // let's print it all out
  for(int i=0; i<KVP; i++)
  {
    printf("\nkey[%d] = %s\nvalue[%d] = %s\n",
        i, pointers[i][KVP_KEY],
        i, pointers[i][KVP_VALUE]);
  }

  return 0;
}