JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,广泛用于嵌入式系统、Web应用程序和其他需要数据交换的场景。它基于JavaScript语法,但独立于编程语言,具有易读性、易解析性和广泛支持的特点。以下是JSON的定义、概念和关键特性的详细介绍:
1. JSON 的定义
JSON是一种文本格式,用于表示结构化数据。它由键值对组成,支持多种数据类型,包括数字、字符串、布尔值、数组和对象。JSON的设计目标是易于人阅读和编写,同时也易于机器解析和生成。
2. JSON 的基本概念
JSON的核心是通过键值对组织数据,其结构包括以下主要元素:
对象(Object)
用大括号 {} 包裹,表示一组无序的键值对集合。键和值之间用冒号 : 分隔,键值对之间用逗号 , 分隔。
示例:
<JSON>
{
"name": "John",
"age": 30,
"isStudent": false
}
数组(Array)
用中括号 [] 包裹,表示一组有序的值。值之间用逗号 , 分隔。
示例:
<JSON>
["apple", "banana", "orange"]
值(Value)
可以是以下数据类型:
字符串(String):用双引号 “” 包裹,例如 “Hello, World!”。
数字(Number):整数或浮点数,例如 42 或 3.14。
布尔值(Boolean):true 或 false。
空值(Null):null。
对象或数组。
3. JSON 的特性
轻量级:JSON的语法简洁,数据体积小,传输效率高,适用于嵌入式系统等资源受限环境。
易读性:JSON以纯文本形式存储,人类可以轻松阅读和理解。
跨平台兼容性:JSON独立于编程语言,几乎所有编程语言都提供了JSON的解析和生成工具。
广泛支持:JSON广泛应用于API数据交换、配置文件、日志记录等场景。
4. JSON 的示例
以下是一个包含对象、数组和嵌套结构的JSON示例:
<JSON>
{
"name": "Alice",
"age": 25,
"isStudent": true,
"courses": ["Math", "Physics", "Chemistry"],
"address": {
"street": "123 Main St",
"city": "New York",
"zipcode": "10001"
}
}
5. JSON 在嵌入式系统中的应用
数据交换:JSON常用于嵌入式系统与服务器、客户端或其他设备之间的数据通信。
配置文件:JSON格式的配置文件易于修改和维护,例如存储设备参数或系统设置。
日志记录:将日志信息以JSON格式存储,便于后续分析和处理。
6. JSON 解析与生成
在嵌入式系统中,通常需要解析和生成JSON数据。常用的工具和库包括:
C语言:cJSON 是一个轻量级的JSON解析库,非常适合嵌入式系统。
Python:内置的 json 模块支持JSON的解析和生成。
JavaScript:JSON.parse() 和 JSON.stringify() 是内置的JSON处理方法。
综合案例
json-prase.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 文件操作需要的头文件
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "cJSON/cJSON.h"
char *loadJsonFile(const char *filepath)
{
// 定义文件状态的结构体变量
struct stat s = {0};
// 调用系统函数获取文件大小
int r = stat(filepath, &s);
if (-1 == r)
{
perror("stat failed");
return NULL; //""
}
// 从文件状态结构体变量中获取文件大小
long total_size = s.st_size;
// 根据文件的大小在堆内存中申请空间
char *p = (char *)calloc(1, total_size);
// 打开文件
FILE *fp = fopen(filepath, "r");
if (fp == NULL)
{
perror("fopen failed");
free(p);
return NULL;
}
// 读取文件内存存储到堆内存中
fread(p, total_size, 1, fp);
// 关闭文件描述符
fclose(fp);
return p;
}
int main(int argc, char const *argv[])
{
// char buf[1000] = "{\"name\":\"value0\", \"password\":\"aaaaa\"}";
// 0.调用自己封装的函数,读取文件中的json格式的字符串
char *buf = loadJsonFile("./data.json");
// printf("test: %s\n", buf);
// 1.调用cJSON库中的cJSON_Parse函数解析json格式的字符串
cJSON *root = cJSON_Parse(buf);
if (root == NULL)
{
goto end;
}
cJSON *message = cJSON_GetObjectItem(root, "message");
if (message != NULL && cJSON_IsString(message) && message->valuestring != NULL)
{
printf("mes: %s\n", message->valuestring);
}
// cJSON* status = cJSON_GetObjectItem(root, "status");
// double status_num = cJSON_GetNumberValue(status);
// printf("status: %lf\n", status_num);
cJSON *status = cJSON_GetObjectItem(root, "statuS");
if (status != NULL && cJSON_IsNumber(status))
{
printf("status: %d\n", status->valueint);
}
/*
cJSON_GetObjectItem --- 大小写不敏感:指定的键一定要和json串中的键大小写可以不一致
cJSON_GetObjectItemCaseSensitive --- 大小写敏感:指定的键一定要和json串中的键大小写一致
*/
cJSON *cityInfo = cJSON_GetObjectItemCaseSensitive(root, "cityInfo");
cJSON *city = cJSON_GetObjectItem(cityInfo, "city");
if (city != NULL && cJSON_IsString(city) && city->valuestring != NULL)
{
printf("city: %s\n", city->valuestring);
}
cJSON *data = cJSON_GetObjectItem(root, "data");
cJSON *wendu = cJSON_GetObjectItem(data, "wendu");
if (wendu != NULL && cJSON_IsString(wendu) && wendu->valuestring != NULL)
{
printf("wendu: %s\n", wendu->valuestring);
}
cJSON *forecast = cJSON_GetObjectItem(data, "forecast");
cJSON *element = NULL;
cJSON_ArrayForEach(element, forecast)
{
cJSON *high = cJSON_GetObjectItem(element, "high");
if (high != NULL && cJSON_IsString(high) && high->valuestring != NULL)
{
printf("high: %s\n", high->valuestring);
}
}
end:
cJSON_Delete(root);
free(buf);
return 0;
}
1. 代码功能概述
从文件中读取 JSON 数据:程序首先从指定的 JSON 文件中读取数据到内存中。
解析 JSON 数据:使用 cJSON 库解析 JSON 数据,并提取其中的字段值。
输出解析结果:根据解析结果输出 JSON 数据中的指定字段。
2. 代码结构与模块分析
(1) loadJsonFile 函数
功能:加载指定路径的 JSON 文件并返回文件内容。
实现步骤:
使用 stat 获取文件大小。
使用 calloc 分配内存以存储文件内容。
使用 fopen 打开文件,fread 读取文件内容到内存中。
返回文件内容指针。
注意事项:
如果文件打开失败或获取文件大小失败,函数会释放已分配的内存并返回 NULL。
(2) main 函数
功能:解析 JSON 文件并输出指定字段的值。
实现步骤:
调用 loadJsonFile 加载 JSON 文件内容。
使用 cJSON_Parse 解析 JSON 数据。
使用 cJSON_GetObjectItem 和 cJSON_GetObjectItemCaseSensitive 提取 JSON 字段值。
输出提取的字段值。
释放资源(cJSON_Delete 和 free)。
3. 关键代码分析
(1) 加载 JSON 文件
char *buf = loadJsonFile("./data.json"); 调用 loadJsonFile 函数加载 JSON 文件内容,返回的字符串存储到 buf 中。 #### (2) 解析 JSON 字符串 cJSON *root = cJSON_Parse(buf); 使用 cJSON_Parse 将 JSON 字符串解析为 cJSON 对象。root 是解析后的 JSON 树的根节点。 #### (3) 提取字段值cJSON *message = cJSON_GetObjectItem(root, "message");
if (message != NULL && cJSON_IsString(message) && message->valuestring != NULL)
{
printf("mes: %s\n", message->valuestring);
}
使用 cJSON_GetObjectItem 获取 message 字段的值。
通过 cJSON_IsString 检查字段是否为字符串类型,并输出其值。
(4) 遍历数组
<C>
cJSON_ArrayForEach(element, forecast)
{
cJSON *high = cJSON_GetObjectItem(element, "high");
if (high != NULL && cJSON_IsString(high) && high->valuestring != NULL)
{
printf("high: %s\n", high->valuestring);
}
}
使用 cJSON_ArrayForEach 宏遍历 forecast 数组中的每个元素。
从每个元素中提取 high 字段的值并输出。
(5) 资源释放
cJSON_Delete(root);
free(buf);
使用 cJSON_Delete 释放 cJSON 对象占用的内存。
使用 free 释放 loadJsonFile 分配的内存。
4. 代码优化建议
(1) 错误处理
在 loadJsonFile 和 main 函数中,增加更详细的错误处理逻辑,例如输出错误原因或记录日志。
例如,在 cJSON_Parse 失败时,可以输出 cJSON_GetErrorPtr 以获取解析错误的详细信息。
(2) 性能优化
如果 JSON 文件较大,可以使用流式解析(如 cJSON_ReadFromStream)而不是一次性加载整个文件到内存中。
在嵌入式系统中,尽量减少动态内存分配,可以使用静态内存池或栈内存。
(3) 代码可读性
将 JSON 字段的提取逻辑封装为单独的函数,例如 extractMessage 或 extractForecast,以提高代码的模块化和可读性。
使用常量定义 JSON 字段名称,例如 #define FIELD_MESSAGE “message”,避免硬编码。
(4) 安全性
在 fread 中检查实际读取的字节数,确保文件读取完整。
在嵌入式系统中,确保 JSON 文件和数据的安全性,防止恶意数据导致的崩溃或漏洞。
5. 示例 JSON 文件
假设 data.json 文件内容如下:
<JSON>
{
"message": "success",
"status": 200,
"cityInfo": {
"city": "Beijing"
},
"data": {
"wendu": "24",
"forecast": [
{
"high": "27°C"
},
{
"high": "26°C"
}
]
}
}
json-packong.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "cJSON/cJSON.h"
char *packingJsontoString()
{
// 准备要被封装的数据
char *message = "yueqiankeji 天气预报";
int status = 200;
char *city = "西安市";
char *wendu = "10.1";
char *highs[] = {"高温 12℃", "高温 13℃", "高温 13℃", "高温 10℃", "高温 14℃"};
int len = sizeof(highs) / sizeof(highs[0]);
// 1.创建json对象
cJSON *root = cJSON_CreateObject();
if (root == NULL)
{
return NULL;
}
cJSON *messageItem = cJSON_CreateString(message);
cJSON_AddItemToObject(root, "message", messageItem);
cJSON_AddNumberToObject(root, "status", status);
cJSON *cityInfo = cJSON_CreateObject();
cJSON_AddStringToObject(cityInfo, "city", city);
cJSON_AddItemToObject(root, "cityInfo", cityInfo);
cJSON *data = cJSON_CreateObject();
cJSON_AddStringToObject(data, "wendu", wendu);
cJSON_AddItemToObject(root, "data", data);
cJSON *forecast = cJSON_AddArrayToObject(data, "forecast");
for (int i = 0; i < len; i++)
{
cJSON *item = cJSON_CreateObject();
cJSON_AddStringToObject(item, "high", highs[i]);
cJSON_AddItemToArray(forecast, item);
}
// 将封装的json转换成字符串,测试打印
char *all_data = cJSON_Print(root);
printf("%s\n", all_data);
// 转存数据
char *str = (char *)malloc(strlen(all_data) + 1); // +1 用于存储字符串结尾的 '\0'
strcpy(str, all_data);
// 清理内存
cJSON_Delete(root);
free(all_data);
return str;
}
void save(char *filepath, char *str)
{
// 检查路径和字符串是否有效
if (filepath == NULL || str == NULL)
{
fprintf(stderr, "无效的路径或字符串\n");
return;
}
// 打开文件(以写模式打开,如果文件不存在则创建,如果文件存在则覆盖)
FILE *file = fopen(filepath, "w");
if (file == NULL)
{
perror("打开文件失败");
return;
}
// 将字符串写入文件
if (fputs(str, file) == EOF)
{
perror("写入文件失败");
fclose(file);
return;
}
// 关闭文件
fclose(file);
printf("JSON 数据已成功保存到文件: %s\n", filepath);
}
int main(int argc, char const *argv[])
{
char *str = packingJsontoString();
if (str == NULL)
{
puts("解析失败");
return -1;
}
printf("%s\n", str);
// 调用自己封装的函数,将字符串写到 ./data.json文件中
save("./data.json", str);
// 释放动态分配的内存
free(str);
return 0;
}
这段代码的主要功能是封装 JSON 数据并将其保存到文件中。它使用了 cJSON 库来创建和操作 JSON 对象,并将生成的 JSON 字符串写入本地文件。以下是对代码的详细分析:
1. 代码功能概述
封装 JSON 数据:将一组数据(如消息、状态、城市信息、温度等)封装为 JSON 格式的字符串。
保存到文件:将生成的 JSON 字符串保存到指定路径的文件中。
2. 代码结构与模块分析
(1) packingJsontoString 函数
功能:封装 JSON 数据并返回 JSON 字符串。
实现步骤:
准备需要封装的数据(如消息、状态、城市信息等)。
使用 cJSON_CreateObject 创建 JSON 对象。
使用 cJSON_AddItemToObject 和 cJSON_AddArrayToObject 等函数将数据添加到 JSON 对象中。
使用 cJSON_Print 将 JSON 对象转换为字符串。
将字符串拷贝到动态分配的内存中,并返回该内存的指针。
注意事项:
如果创建 JSON 对象失败,函数返回 NULL。
返回的字符串需要在调用方手动释放。
(2) save 函数
功能:将字符串保存到指定路径的文件中。
实现步骤:
检查路径和字符串是否有效。
使用 fopen 以写模式打开文件。
使用 fputs 将字符串写入文件。
使用 fclose 关闭文件。
注意事项:
如果文件打开或写入失败,函数会输出错误信息并返回。
(3) main 函数
功能:调用 packingJsontoString 封装 JSON 数据,并调用 save 将数据保存到文件。
实现步骤:
调用 packingJsontoString 封装 JSON 数据。
如果成功,调用 save 将 JSON 字符串保存到文件。
释放动态分配的内存。
3. 关键代码分析
(1) 创建 JSON 对象
<C>
cJSON *root = cJSON_CreateObject();
创建一个空的 JSON 对象 root,作为 JSON 树的根节点。
(2) 添加字段到 JSON 对象
<C>
cJSON_AddStringToObject(root, "message", message);
cJSON_AddNumberToObject(root, "status", status);
使用 cJSON_AddStringToObject 和 cJSON_AddNumberToObject 将字符串和数字字段添加到 JSON 对象中。
(3) 添加嵌套对象
<C>
cJSON *cityInfo = cJSON_CreateObject();
cJSON_AddStringToObject(cityInfo, "city", city);
cJSON_AddItemToObject(root, "cityInfo", cityInfo);
创建一个嵌套的 JSON 对象 cityInfo,并将其作为字段添加到 root 对象中。
(4) 添加数组
cJSON *forecast = cJSON_AddArrayToObject(data, "forecast");
for (int i = 0; i < len; i++)
{
cJSON *item = cJSON_CreateObject();
cJSON_AddStringToObject(item, "high", highs[i]);
cJSON_AddItemToArray(forecast, item);
}
使用 cJSON_AddArrayToObject 创建一个数组 forecast。
使用循环将多个对象添加到数组中。
(5) 转换为字符串
<C>
char *all_data = cJSON_Print(root);
使用 cJSON_Print 将 JSON 对象转换为格式化字符串。
(6) 保存到文件
<C>
FILE *file = fopen(filepath, "w");
fputs(str, file);
fclose(file);
使用 fopen 以写模式打开文件,fputs 写入字符串,fclose 关闭文件。
4. 代码优化建议
(1) 错误处理
在 cJSON_CreateObject 和 cJSON_AddItemToObject 等函数调用后,增加错误处理逻辑,例如输出错误信息或返回错误码。
在 malloc 动态分配内存后,检查返回值是否为 NULL。
(2) 性能优化
如果生成的 JSON 字符串较大,可以使用 cJSON_PrintUnformatted 生成未格式化的字符串,减少字符串长度和内存占用。
在嵌入式系统中,尽量减少动态内存分配,可以使用静态内存池或栈内存。
(3) 代码可读性
将 JSON 对象的创建和字段添加逻辑封装为单独的函数,例如 createCityInfo 或 createForecast,以提高代码的模块化和可读性。
使用常量定义 JSON 字段名称,例如 #define FIELD_MESSAGE “message”,避免硬编码。
(4) 安全性
在 strcpy 拷贝字符串时,使用 strncpy 避免缓冲区溢出。
在嵌入式系统中,确保文件路径和数据的安全性,防止恶意数据导致的崩溃或漏洞。
综合练习
客户端发送请求类型(登录/注册) 及用户名和密码,服务器作出响应:
如果客户端发送登录请求,则服务器需要读取保存了已注册用户的文件(user.dat),比对此刻客户端发送的用户名和密码是否
是
文件中已记录的数据,并发送登录成功或失败的信息给客户端
如果客户端发送注册请求,服务器将客户端发送的用户名和密码数据写入文件(user.dat)
2.2. 客户端交互界面设计如下
=======================
1. 用户注册
2. 用户登录
0. 退出
=======================
当客户端注册成功后可选择登录操作,登录成功会退出进程,否则一直停留在本界面,继续登录,或选择退出,结束进程
要求:网络数据通讯采用 JSON 数据格式来传递。
服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "cJSON/cJSON.h"
#define PORT 8080
#define MAX_CLIENTS 5
#define BUFFER_SIZE 1024
// 用户数据文件路径
const char *USER_FILE = "user.dat";
// 检查用户名是否已存在
int is_user_exist(const char *username)
{
FILE *file = fopen(USER_FILE, "r");
if (!file)
return 0;
char buffer[BUFFER_SIZE];
while (fgets(buffer, BUFFER_SIZE, file))
{
cJSON *user = cJSON_Parse(buffer);
if (!user)
continue;
cJSON *user_name = cJSON_GetObjectItem(user, "username");
if (user_name && strcmp(user_name->valuestring, username) == 0)
{
cJSON_Delete(user);
fclose(file);
return 1;
}
cJSON_Delete(user);
}
fclose(file);
return 0;
}
// 检查用户名和密码是否匹配
int check_user(const char *username, const char *password)
{
FILE *file = fopen(USER_FILE, "r");
if (!file)
return 0;
char buffer[BUFFER_SIZE];
while (fgets(buffer, BUFFER_SIZE, file))
{
cJSON *user = cJSON_Parse(buffer);
if (!user)
continue;
cJSON *user_name = cJSON_GetObjectItem(user, "username");
cJSON *user_pass = cJSON_GetObjectItem(user, "password");
if (user_name && user_pass &&
strcmp(user_name->valuestring, username) == 0 &&
strcmp(user_pass->valuestring, password) == 0)
{
cJSON_Delete(user);
fclose(file);
return 1;
}
cJSON_Delete(user);
}
fclose(file);
return 0;
}
// 注册新用户
int register_user(const char *username, const char *password)
{
FILE *file = fopen(USER_FILE, "a");
if (!file)
return 0;
cJSON *user = cJSON_CreateObject();
cJSON_AddStringToObject(user, "username", username);
cJSON_AddStringToObject(user, "password", password);
char *user_json = cJSON_PrintUnformatted(user);
fprintf(file, "%s\n", user_json);
fclose(file);
cJSON_Delete(user);
free(user_json);
return 1;
}
// 处理客户端请求
void handle_client(int client_socket)
{
char buffer[BUFFER_SIZE];
while (1)
{
memset(buffer, 0, BUFFER_SIZE);
int read_size = recv(client_socket, buffer, BUFFER_SIZE, 0);
if (read_size <= 0)
break;
// 解析 JSON 请求
cJSON *request = cJSON_Parse(buffer);
if (!request)
{
send(client_socket, "无效的 JSON 数据", 16, 0);
break;
}
cJSON *type = cJSON_GetObjectItem(request, "type");
cJSON *username = cJSON_GetObjectItem(request, "username");
cJSON *password = cJSON_GetObjectItem(request, "password");
if (!type || !username || !password)
{
send(client_socket, "请求字段缺失", 13, 0);
cJSON_Delete(request);
break;
}
// 处理请求
char response[BUFFER_SIZE];
if (strcmp(type->valuestring, "login") == 0)
{
if (check_user(username->valuestring, password->valuestring))
{
strcpy(response, "登录成功");
}
else
{
strcpy(response, "登录失败:用户名或密码错误");
}
}
else if (strcmp(type->valuestring, "register") == 0)
{
if (is_user_exist(username->valuestring))
{
strcpy(response, "注册失败:用户名已存在");
}
else if (register_user(username->valuestring, password->valuestring))
{
strcpy(response, "注册成功");
}
else
{
strcpy(response, "注册失败");
}
}
else
{
strcpy(response, "无效的请求类型");
}
// 发送响应
send(client_socket, response, strlen(response), 0);
cJSON_Delete(request);
}
close(client_socket);
}
int main()
{
int server_socket, client_socket;
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
// 创建 socket
server_socket = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket < 0)
{
perror("Socket 创建失败");
exit(EXIT_FAILURE);
}
// 绑定地址和端口
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
if (bind(server_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("绑定失败");
close(server_socket);
exit(EXIT_FAILURE);
}
// 监听连接
if (listen(server_socket, MAX_CLIENTS) < 0)
{
perror("监听失败");
close(server_socket);
exit(EXIT_FAILURE);
}
printf("服务器已启动,端口:%d\n", PORT);
while (1)
{
// 接受客户端连接
client_socket = accept(server_socket, (struct sockaddr *)&client_addr, &addr_len);
if (client_socket < 0)
{
perror("接受连接失败");
continue;
}
printf("新客户端已连接\n");
// 处理客户端请求
handle_client(client_socket);
printf("客户端已断开连接\n");
}
close(server_socket);
return 0;
}
这段代码是一个基于 TCP 服务器 的简单用户管理系统,支持用户登录和注册功能。代码使用了 cJSON 库解析和生成 JSON 数据,并通过网络与客户端进行通信。以下是对代码的详细分析:
1. 代码功能概述
用户管理:
支持用户注册:将用户名和密码保存到文件中。
支持用户登录:验证用户名和密码是否匹配。
网络通信:基于 TCP,服务器监听指定的端口,并处理客户端的请求。
数据格式:使用 JSON 格式传输数据。
2. 代码结构与模块分析
(1) is_user_exist 函数
功能:检查用户名是否已存在。
实现:
打开用户数据文件 user.dat。
逐行读取文件内容,解析成 JSON 对象。
比较用户名,如果找到匹配的用户名则返回 1,否则返回 0。
注意事项:
文件打开失败时返回 0。
每次调用都会重新打开和关闭文件,性能较低。
(2) check_user 函数
功能:检查用户名和密码是否匹配。
实现:
打开用户数据文件 user.dat。
逐行读取文件内容,解析成 JSON 对象。
比较用户名和密码,如果匹配则返回 1,否则返回 0。
注意事项:
文件打开失败时返回 0。
(3) register_user 函数
功能:注册新用户。
实现:
打开用户数据文件 user.dat(追加模式)。
创建 JSON 对象,包含用户名和密码。
将 JSON 对象转换为字符串并写入文件。
注意事项:
文件打开失败时返回 0。
动态分配的 JSON 字符串在写入后需要释放。
(4) handle_client 函数
功能:处理客户端的请求。
实现:
接收客户端发送的 JSON 数据。
解析 JSON 数据,提取请求类型、用户名和密码。
根据请求类型调用相应的函数(check_user 或 register_user)。
发送响应给客户端。
注意事项:
如果 JSON 数据解析失败或字段缺失,发送错误响应。
如果客户端断开连接,关闭套接字。
(5) main 函数
功能:启动服务器并处理客户端连接。
实现:
创建 TCP 套接字。
绑定地址和端口。
监听客户端连接。
接受客户端连接并调用 handle_client 处理请求。
注意事项:
每次只处理一个客户端请求,不支持并发。
3. 关键代码分析
(1) JSON 解析与生成
<C>
cJSON *request = cJSON_Parse(buffer);
cJSON *type = cJSON_GetObjectItem(request, "type");
cJSON *username = cJSON_GetObjectItem(request, "username");
cJSON *password = cJSON_GetObjectItem(request, "password");
使用 cJSON_Parse 解析客户端发送的 JSON 数据。
使用 cJSON_GetObjectItem 提取字段值。
(2) 用户注册
<C>
cJSON *user = cJSON_CreateObject();
cJSON_AddStringToObject(user, "username", username);
cJSON_AddStringToObject(user, "password", password);
char *user_json = cJSON_PrintUnformatted(user);
fprintf(file, "%s\n", user_json);
创建 JSON 对象,包含用户名和密码。
将 JSON 对象转换为字符串并写入文件。
(3) 网络通信
int read_size = recv(client_socket, buffer, BUFFER_SIZE, 0);
send(client_socket, response, strlen(response), 0);
使用 recv 接收客户端数据。
使用 send 发送响应给客户端。
4. 代码优化建议
(1) 性能优化
文件读写:
每次检查用户或注册用户时都会打开和关闭文件,效率较低。可以将文件内容加载到内存中,减少文件 I/O 操作。
并发支持:
当前服务器是单线程的,无法同时处理多个客户端。可以使用多线程或多线程技术(如 select、poll 或 epoll)支持并发。
(2) 错误处理
文件操作:
在文件打开失败时,提供更详细的错误信息。
JSON 解析:
在 cJSON_Parse 失败时,使用 cJSON_GetErrorPtr 获取错误位置。
(3) 安全性
密码存储:
直接存储明文密码不安全,建议对密码进行加密(如使用哈希算法)。
缓冲区溢出:
在字符串操作(如 strcpy)时,使用 strncpy 避免缓冲区溢出。
(4) 代码可读性
函数拆分:
将 handle_client 函数中的登录和注册逻辑拆分为单独的函数。
常量定义:
使用常量定义 JSON 字段名称,例如 #define FIELD_USERNAME “username”。
5. 示例请求与响应
(1) 登录请求
<JSON>
{
"type": "login",
"username": "user1",
"password": "pass1"
}
响应:
登录成功
(2) 注册请求
<JSON>
{
"type": "register",
"username": "user2",
"password": "pass2"
}
客户端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include "cJSON/cJSON.h"
#define SERVER_IP "127.0.0.1"
#define PORT 8080
#define BUFFER_SIZE 1024
// 发送请求并接收响应
void send_request(const char *type, const char *username, const char *password, char *response)
{
int client_socket;
struct sockaddr_in server_addr;
// 创建 socket
client_socket = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket < 0)
{
perror("Socket 创建失败");
return;
}
// 连接服务器
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
inet_pton(AF_INET, SERVER_IP, &server_addr.sin_addr);
if (connect(client_socket, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0)
{
perror("连接服务器失败");
close(client_socket);
return;
}
// 构造 JSON 请求
cJSON *request = cJSON_CreateObject();
cJSON_AddStringToObject(request, "type", type);
cJSON_AddStringToObject(request, "username", username);
cJSON_AddStringToObject(request, "password", password);
char *request_json = cJSON_PrintUnformatted(request);
send(client_socket, request_json, strlen(request_json), 0);
// 接收响应
memset(response, 0, BUFFER_SIZE);
recv(client_socket, response, BUFFER_SIZE, 0);
cJSON_Delete(request);
free(request_json);
close(client_socket);
}
// 登录成功后显示菜单
void logged_in_menu()
{
int choice;
while (1)
{
printf("=======================\n");
printf("1. 返回主菜单\n");
printf("2. 退出\n");
printf("=======================\n");
printf("请输入选项: ");
scanf("%d", &choice);
if (choice == 1)
{
break; // 返回主菜单
}
else if (choice == 2)
{
printf("退出程序。\n");
exit(0);
}
else
{
printf("无效选项,请重新输入。\n");
}
}
}
int main()
{
int choice;
char username[50], password[50];
char response[BUFFER_SIZE];
while (1)
{
printf("=======================\n");
printf("1. 用户注册\n");
printf("2. 用户登录\n");
printf("0. 退出\n");
printf("=======================\n");
printf("请输入选项: ");
scanf("%d", &choice);
if (choice == 0)
break;
printf("请输入用户名: ");
scanf("%s", username);
printf("请输入密码: ");
scanf("%s", password);
if (choice == 1)
{
send_request("register", username, password, response);
printf("服务器响应: %s\n", response);
}
else if (choice == 2)
{
send_request("login", username, password, response);
printf("服务器响应: %s\n", response);
// 如果登录成功,显示登录后的菜单
if (strcmp(response, "登录成功") == 0)
{
logged_in_menu();
}
}
else
{
printf("无效选项\n");
}
}
return 0;
}
这段代码是一个客户端程序,通过 TCP 协议与服务器通信,支持用户注册和登录功能,并在登录成功后显示一个简单的菜单。以下是对代码的详细分析:
1. 代码功能概述
与服务器通信:通过 TCP 连接服务器,发送 JSON 格式的请求并接收响应。
用户注册:将用户名和密码发送到服务器进行注册。
用户登录:将用户名和密码发送到服务器进行验证,登录成功后显示菜单。
菜单功能:
返回主菜单。
退出程序。
2. 代码结构与模块分析
(1) send_request 函数
功能:向服务器发送请求并接收响应。
实现步骤:
创建 TCP 套接字。
连接服务器。
构造 JSON 请求(包含请求类型、用户名和密码)。
将 JSON 请求发送到服务器。
接收服务器响应。
关闭套接字。
注意事项:
如果套接字创建或连接失败,函数会输出错误信息并返回。
动态分配的 JSON 字符串在发送后需要释放。
(2) logged_in_menu 函数
功能:显示登录成功后的菜单。
实现步骤:
显示菜单选项(返回主菜单、退出程序)。
根据用户输入执行相应操作。
注意事项:
如果用户选择退出程序,调用 exit(0) 结束程序。
(3) main 函数
功能:主循环,显示主菜单并处理用户输入。
实现步骤:
显示主菜单选项(注册、登录、退出)。
根据用户输入调用 send_request 发送请求。
如果登录成功,调用 logged_in_menu 显示登录后的菜单。
注意事项:
如果用户选择退出程序,主循环结束。
3. 关键代码分析
(1) 创建并发送 JSON 请求
cJSON *request = cJSON_CreateObject();
cJSON_AddStringToObject(request, "type", type);
cJSON_AddStringToObject(request, "username", username);
cJSON_AddStringToObject(request, "password", password);
char *request_json = cJSON_PrintUnformatted(request);
send(client_socket, request_json, strlen(request_json), 0);
使用 cJSON_CreateObject 创建 JSON 对象。
使用 cJSON_AddStringToObject 添加字段。
使用 cJSON_PrintUnformatted 将 JSON 对象转换为字符串。
使用 send 发送 JSON 字符串到服务器。
(2) 接收服务器响应
memset(response, 0, BUFFER_SIZE);
recv(client_socket, response, BUFFER_SIZE, 0);
printf(“服务器响应: %s\n”, response);
使用 recv 接收服务器响应。
将响应存储在 response 缓冲区中并输出。
(3) 登录成功后的菜单
if (strcmp(response, "登录成功") == 0)
{
logged_in_menu();
}
如果服务器响应为 “登录成功”,调用 logged_in_menu 显示菜单。
4. 代码优化建议
(1) 错误处理
套接字操作:
在 send 和 recv 失败时,提供更详细的错误信息。
JSON 构造:
在 cJSON_CreateObject 或 cJSON_AddStringToObject 失败时,增加错误处理逻辑。
(2) 代码可读性
函数拆分:
将 send_request 函数中的 JSON 构造和网络通信逻辑拆分为单独的函数。
常量定义:
使用常量定义 JSON 字段名称,例如 #define FIELD_USERNAME “username”。
(3) 安全性
缓冲区溢出:
在 scanf 读取用户名和密码时,使用 fgets 并指定缓冲区大小,避免缓冲区溢出。
密码处理:
在客户端和服务器之间传输密码时,建议对密码进行加密。
(4) 功能扩展
增加密码修改、注销等功能。
支持多用户并发操作(需要服务器端支持)。
5. 示例交互
(1) 注册
=======================
1. 用户注册
2. 用户登录
0. 退出
=======================
请输入选项: 1
请输入用户名: user1
请输入密码: pass1
服务器响应: 注册成功
(2) 登录
=======================
1. 用户注册
2. 用户登录
0. 退出
=======================
请输入选项: 2
请输入用户名: user1
请输入密码: pass1
服务器响应: 登录成功
=======================
1. 返回主菜单
2. 退出
=======================
请输入选项: 1
(3) 退出
=======================
1. 用户注册
2. 用户登录
0. 退出
=======================
请输入选项: 0