在 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;
}
我在 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;
}