首页 前端知识 网络编程(JSON 数据格式)

网络编程(JSON 数据格式)

2025-03-09 15:03:36 前端知识 前端哥 778 724 我要收藏

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
转载请注明出处或者链接地址:https://www.qianduange.cn//article/23027.html
标签
评论
发布的文章

面试题之强缓存协商缓存

2025-03-11 15:03:21

【C语言】数组篇

2025-03-11 15:03:19

正则表达式(复习)

2025-03-11 15:03:17

大家推荐的文章
会员中心 联系我 留言建议 回顶部
复制成功!