【网络】Socket编程TCP/UDP序列化和反序列化理解应用层(C++实现)Json::Value

news2024/11/22 4:26:26

主页:醋溜马桶圈-CSDN博客

专栏:计算机网络原理_醋溜马桶圈的博客-CSDN博客

gitee:mnxcc (mnxcc) - Gitee.com

目录

1.基于Socket的UDP和TCP编程介绍

1.1 基本TCP客户—服务器程序设计基本框架

​编辑1.2 基本UDP客户—服务器程序设计基本框架

1.3 Socket编程

1.3.1 socket API

1.3.1.1 socket()

1.3.1.2 bind()

1.3.1.3 listen()

1.3.1.4  accept()

1.3.1.5 connect()

1.3.2 TCP/IP应用编程接口(API) 

1.3.2.1 服务器

1.3.2.2 客户端 

1.3.3 UDP/IP应用编程接口(API)

1.3.3.1 服务器

1.3.3.2 客户端

1.3.4 调试 makefile

2.Socket 编程 UDP

2.1 udp echo server

2.2 udp dict server

2.3 简单聊天室

2.4 地址转换函数

2.4.1 关于inet_ntoa

3.Socket 编程 TCP

3.1 tcp echo server

3.2 多线程远程命令执行

4.应用层自定义协议与序列化

4.1 应用层

4.2 重新理解 read、write、recv、send 和 tcp 为什么支持全双工

4.3 网络版计算器

4.3.1 Jsoncpp

4.3.1.1 特性

4.3.1.2 安装

4.3.2 序列化和反序列化

4.3.2.1 序列化

4.3.2.2 反序列化

4.4 Json::Value

4.4.1 构造函数

4.4.2 访问元素

4.4.3 类型检查

4.4.4 赋值和类型转换

4.4.5 数组和对象操作 


1.基于Socket的UDP和TCP编程介绍

1.1 基本TCP客户—服务器程序设计基本框架

说明:(三次握手)

  1. 客户端发送一个SYN段(同步序号)指明客户打算连接的服务器端口,以及初始化序号(ISN) 
  2. 服务器发回包含服务器的初始序号的SYN报文段作为应答。同时,将确认序号(ACK)设置为客户的ISN加1以对客户的SYN 报文段进行确认。一个SYN将占用一个序号
  3. 客户必须将确认序号设置为服务器的ISN加1以对服务器的SYN报文段进行确认

1.2 基本UDP客户—服务器程序设计基本框架

1.3 Socket编程

        Socket接口是TCP/IP网络的API,Socket接口定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程,必须理解Socket接口。

        Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话,就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用。

1.3.1 socket API

socket调用库函数主要有:

1.创建套接字 
        socket(af,type,protocol)

2.建立地址和套接字的联系 
        bind(sockid, local addr, addrlen)

3.服务器端侦听客户端的请求 
        listen( Sockid ,quenlen)

4.建立服务器/客户端的连接 (面向连接TCP) 
        客户端请求连接 
                connect(sockid, destaddr, addrlen) 

        服务器端等待从编号为Sockid的Socket上接收客户连接请求 

                newsockid=accept(Sockid,Clientaddr, paddrlen)

5.发送/接收数据 

        面向连接:
                send(sockid, buff, bufflen) 

                recv( ) 

        面向无连接:

                sendto(sockid,buff,…,addrlen) 

                recvfrom( )

6.释放套接字 

        close(sockid)

1.3.1.1 socket()
int socket(int domain, int type, int protocol);
  • socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
  • 如果 socket()调用出错则返回-1;
  • 对于 IPv4, family 参数指定为 AF_INET;
  • 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
  • protocol 参数的介绍从略,指定为 0 即可

1.3.1.2 bind()
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一个固定的网络地址和端口号
  • bind()成功返回 0,失败返回-1
  • bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络通讯的文件描述符监听 myaddr 所描述的地址和端口号
  • struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen 指定结构体的长度

我们的程序中对 myaddr 参数是这样初始化的:

  1. 将整个结构体清零
  2. 设置地址类型为 AF_INET
  3. 网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址
  4. 端口号为 SERV_PORT

1.3.1.3 listen()
int listen(int sockfd, int backlog);

  • listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5)
  • listen()成功返回 0,失败返回-1;
1.3.1.4  accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • 三次握手完成后, 服务器调用 accept()接受连接
  • 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr 是一个传出参数,accept()返回时传出客户端的地址和端口号
  • 如果给 addr 参数传 NULL,表示不关心客户端的地址
  • addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区)

 我们的服务器程序结构是这样的:

1.3.1.5 connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

  • 客户端需要调用 connect()连接服务器
  • connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而connect 的参数是对方的地址
  • connect()成功返回 0,出错返回-1

1.3.2 TCP/IP应用编程接口(API) 

1.3.2.1 服务器

        服务器的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,然后调用listen在相应的socket上监听,当accpet接收到一个连接服务请求时,将生成一个新的socket。服务器显示该客户机的IP地址,并通过新的socket向客户端发送字符串" hi,I am server!"。最后关闭该socket

int main()
{ 
    int sock_fd,client_fd; /*sock_fd:监听socket;client_fd:数据传输socket */ 
    struct sockaddr_in ser_addr; /* 本机地址信息 */ 
    struct sockaddr_in cli_addr; /* 客户端地址信息 */ 
    char msg[MAX_MSG_SIZE];/* 缓冲区*/
    ser_sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建连接的SOCKET */
    if(ser_sockfd<0)
    {/*创建失败 */
        fprintf(stderr,"socker Error:%s\n",strerror(errno));
        exit(1);
    } 
    /* 初始化服务器地址*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=htonl(INADDR_ANY);
    ser_addr.sin_port=htons(SERVER_PORT);
    if(bind(ser_sockfd,(struct sockaddr*)&ser_addr,sizeof(struct sockaddr_in))<0)
    { /*绑定失败 */
        fprintf(stderr,"Bind Error:%s\n",strerror(errno));
        exit(1);
    } 
    /*侦听客户端请求*/
    if(listen(ser_sockfd,BACKLOG)<0)
    {
        fprintf(stderr,"Listen Error:%s\n",strerror(errno));
        close(ser_sockfd);
        exit(1);
    }
    while(1)
    {/* 等待接收客户连接请求*/
        cli_sockfd=accept(ser_sockfd,(struct sockaddr*) & cli_addr,&addrlen);
        if(cli_sockfd<=0)
        {
            fprintf(stderr,"Accept Error:%s\n",strerror(errno));
        }
        else
        {/*开始服务*/
            recv(cli_addr,msg,MAX_MSG_SIZE,0); /* 接受数据*/
            printf("received a connection from %sn",inet_ntoa(cli_addr.sin_addr));
            printf("%s\n",msg);/*在屏幕上打印出来 */ 
            strcpy(msg,"hi,I am server!");
            send(cli_addr,msg,sizeof(msg),0); /*发送的数据*/
            close(cli_addr); 
        }
    }
    close(ser_sockfd);
}
1.3.2.2 客户端 

        客户端的工作流程:首先调用socket函数创建一个Socket,然后初始化服务器地址结构,调用connect函数请求连接服务器,通过新的socket向客户端发送字符串" hi,I am client!"。最后关闭该socket。

int main()
{
    int cli_sockfd;/*客户端SOCKET */
    int addrlen;
    char seraddr[14];
    struct sockaddr_in ser_addr,/* 服务器的地址*/
    char msg[MAX_MSG_SIZE];/* 缓冲区*/
    GetServerAddr(seraddr);
    cli_sockfd=socket(AF_INET,SOCK_STREAM,0);/*创建连接的SOCKET */
    if(ser_sockfd<0)
    {/*创建失败 */
        fprintf(stderr,"socker Error:%s\n",strerror(errno));
        exit(1);
    }
    /* 初始化服务器地址*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&ser_addr,addrlen);
    ser_addr.sin_family=AF_INET;
    ser_addr.sin_addr.s_addr=inet_addr(seraddr);
    ser_addr.sin_port=htons(SERVER_PORT);
    if(connect(cli_sockfd,(struct sockaddr*)&ser_addr,&addrlen)!=0)/*请求连接*/
    {
        /*连接失败 */
        fprintf(stderr,"Connect Error:%s\n",strerror(errno));
        close(cli_sockfd);
        exit(1);
    }
    strcpy(msg,"hi,I am client!");
    send(sockfd,msg,sizeof(msg),0);/*发送数据*/
    recv(sockfd,msg,MAX_MSG_SIZE,0); /* 接受数据*/
    printf("%s\n",msg);/*在屏幕上打印出来 */
    close(cli_sockfd);
}

1.3.3 UDP/IP应用编程接口(API)

1.3.3.1 服务器

        服务器的工作流程:首先调用socket函数创建一个Socket,然后调用bind函数将其与本机地址以及一个本地端口号绑定,接收到一个客户端时,服务器显示该客户端的IP地址,并将字串返回给客户端。 

int main(int argc, char **argv)
{
    int ser_sockfd;
    int len;
    // int addrlen;

    socklen_t addrlen;
    char seraddr[100];
    struct sockaddr_in ser_addr;
    /*建立socket*/
    ser_sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (ser_sockfd < 0)
    {
        printf("I cannot socket success\n");
        return 1;
    }
    /*填写sockaddr_in 结构*/
    addrlen = sizeof(struct sockaddr_in);
    bzero(&ser_addr, addrlen);
    ser_addr.sin_family = AF_INET;
    ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    ser_addr.sin_port = htons(SERVER_PORT);
    /*绑定服务器*/
    if (bind(ser_sockfd, (struct sockaddr *)&ser_addr, addrlen) < 0)
    {
        printf("connect");
        return 1;
    }
    while (1)
    {
        bzero(seraddr, sizeof(seraddr));
        len = recvfrom(ser_sockfd, seraddr, sizeof(seraddr), 0, (struct sockaddr *)&ser_addr, &addrlen);
        /*显示client端的网络地址*/
        printf("receive from %s\n", inet_ntoa(ser_addr.sin_addr));
        /*显示客户端发来的字串*/
        printf("recevce:%s", seraddr);
        /*将字串返回给client端*/
        sendto(ser_sockfd, seraddr, len, 0, (struct sockaddr *)&ser_addr, addrlen);
    }
}
1.3.3.2 客户端

        客户端的工作流程:首先调用socket函数创建一个Socket,填写服务器地址及端口号,从标准输入设中取得字符串,将字符串传送给服务器端,并接收服务器端返回的字符串。最后关闭该socket。 

int GetServerAddr(char * addrname)
{
    printf("please input server addr:");
    scanf("%s",addrname);
    return 1;
}
int main(int argc,char **argv)
{
    int cli_sockfd;
    int len;
    socklen_t addrlen;
    char seraddr[14];
    struct sockaddr_in cli_addr;
    char buffer[256];
    GetServerAddr(seraddr);
    /* 建立socket*/
    cli_sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(cli_sockfd<0)
    {
        printf("I cannot socket success\n");
        return 1;
    }
    /* 填写sockaddr_in*/
    addrlen=sizeof(struct sockaddr_in);
    bzero(&cli_addr,addrlen);
    cli_addr.sin_family=AF_INET;
    cli_addr.sin_addr.s_addr=inet_addr(seraddr);
    //cli_addr.sin_addr.s_addr=htonl(INADDR_ANY);

    cli_addr.sin_port=htons(SERVER_PORT);
    bzero(buffer,sizeof(buffer));
    /* 从标准输入设备取得字符串*/
    len=read(STDIN_FILENO,buffer,sizeof(buffer));
    /* 将字符串传送给server端*/
    sendto(cli_sockfd,buffer,len,0,(struct sockaddr*)&cli_addr,addrlen);
    /* 接收server端返回的字符串*/
    len=recvfrom(cli_sockfd,buffer,sizeof(buffer),0,(structsockaddr*)&cli_addr,&addrlen);
    //printf("receive from %s\n",inet_ntoa(cli_addr.sin_addr));

    printf("receive: %s",buffer);
    close(cli_sockfd);
}
1.3.4 调试 makefile
CC=gcc
        all:server client
        CFLAGS=-o
        server: server.c
         $(CC) $(CFLAGS) $@ server.c
        client: client.c
        $(CC) $(CFLAGS) $@ client.c
clean:
        rm -f server client

2.Socket 编程 UDP

2.1 udp echo server

简单的回显服务器和客户端代码

udp_echo_server程序源码:mnxcc/linuxdc - Gitee.com

2.2 udp dict server

实现一个简单的英译汉的网络字典

udp_dict_server程序源码:mnxcc/linuxdc - Gitee.com

2.3 简单聊天室

udp_chat_server程序源码:mnxcc/linuxdc - Gitee.com

UDP 协议支持全双工,一个 sockfd,既可以读取,又可以写入,对于客户端和服务端同样如此

2.4 地址转换函数

基于 IPv4 socket 网络编程,sockaddr_in 中的成员 struct in_addr.sin_addr 表示 32 位 的 IP 地址in_addr转字符串的函数:

但是我们通常用点分十进制的字符串表示 IP 地址,以下函数可以在字符串表示 和 in_addr 表示之间转换;

  • 字符串转 in_addr 的函数:

  • in_addr转字符串的函数:

其中inet_ptoninet_ntop不仅可以转换IPv4in_addr,还可以转换IPv6的in6_addr,因此函数接口是void*addrptr

2.4.1 关于inet_ntoa

inet_ntoa这个函数返回了一个char*,很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果  那么是否需要调用者手动释放呢?

man手册上说,inet_ntoa函数,是把这个返回结果放到了静态存储区.这个时候不需要我们手动进行释放

那么问题来了,如果我们调用多次这个函数,会有什么样的效果呢?

因为inet_ntoa把结果放到自己内部的一个静态存储区,这样第二次调用时的结果会覆盖掉上一次的结果

多线程调用inet_ntoa代码示例如下

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *Func1(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr1: %s\n", ptr);
    }
    return NULL;
}
void *Func2(void *p)
{
    struct sockaddr_in *addr = (struct sockaddr_in *)p;
    while (1)
    {
        char *ptr = inet_ntoa(addr->sin_addr);
        printf("addr2: %s\n", ptr);
    }
    return NULL;
}
int main()
{
    pthread_t tid1 = 0;
    struct sockaddr_in addr1;
    struct sockaddr_in addr2;
    addr1.sin_addr.s_addr = 0;
    addr2.sin_addr.s_addr = 0xffffffff;
    pthread_create(&tid1, NULL, Func1, &addr1);
    pthread_t tid2 = 0;
    pthread_create(&tid2, NULL, Func2, &addr2);
    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);
    return 0;
}

3.Socket 编程 TCP

3.1 tcp echo server

tcp_echo_server程序源码:mnxcc/linuxdc - Gitee.com

3.2 多线程远程命令执行

command_server程序源码:mnxcc/linuxdc - Gitee.com

4.应用层自定义协议与序列化

4.1 应用层

我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层

协议是一种 "约定". socket api 的接口, 在读写数据时, 都是按 "字符串" 的方式来发送接收的. 如果我们要传输一些 "结构化的数据" 怎么办呢?

其实,协议就是双方约定好的结构化的数据

4.2 重新理解 readwriterecvsend tcp 为什么支持全双工

所以:

  • 在任何一台主机上,TCP 连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
  • 这就是为什么一个 tcp sockfd 读写都是它的原因
  • 实际数据什么时候发,发多少,出错了怎么办,由 TCP 控制,所以 TCP 叫做传输控制协议

4.3 网络版计算器

cal_server程序源码:mnxcc/linuxdc - Gitee.com

例如, 我们需要实现一个服务器版的加法器. 我们需要客户端把要计算的两个加数发过去, 然后由服务器进行计算, 最后再把结果返回给客户端

约定方案一:

  • 客户端发送一个形如"1+1"的字符串;
  • 这个字符串中有两个操作数, 都是整形;
  • 两个数字之间会有一个字符是运算符, 运算符只能是 + ;
  • 数字和运算符之间没有空格

约定方案二:

  • 定义结构体来表示我们需要交互的信息;
  • 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体
  • 这个过程叫做 "序列化" 和 "反序列化"

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据,在另一端能够正确的进行解析, 就是 ok . 这种约定, 就是 应用层协议

但是,为了让深刻理解协议,我们自定义实现一下协议的过程

  • 采用方案 2,也要体现协议定制的细节
  • 要引入序列化和反序列化,直接采用现成的方案 -- jsoncpp库
  • 要对 socket 进行字节流的读取处理

期望的报文格式

 

所以,完整的处理过程应该是:

4.3.1 Jsoncpp

Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。Jsoncpp 是开源的,广泛用于各种需要处理 JSON 数据的 C++ 项目中

4.3.1.1 特性
  1. 简单易用:Jsoncpp 提供了直观的 API,使得处理 JSON 数据变得简单。
  2. 高性能:Jsoncpp 的性能经过优化,能够高效地处理大量 JSON 数据。
  3. 全面支持:支持 JSON 标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和 null
  4. 错误处理:在解析 JSON 数据时,Jsoncpp 提供了详细的错误信息和位置,方便开发者调试。
  5. 当使用 Jsoncpp 库进行 JSON 的序列化和反序列化时,确实存在不同的做法和工具类可供选择
4.3.1.2 安装
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel

4.3.2 序列化和反序列化

4.3.2.1 序列化

序列化指的是将数据结构或对象转换为一种格式,以便在网络上传输或存储到文件 中。Jsoncpp 提供了多种方式进行序列化:

1.使用 Json::Value toStyledString 方法:

优点:将 Json::Value 对象直接转换为格式

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    std::string s = root.toStyledString();
    std::cout << s << std::endl;
    return 0;
}

2.使用 Json::StreamWriter

优点:提供了更多的定制选项,如缩进、换行符等

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    Json::StreamWriterBuilder wbuilder; // StreamWriter 的工厂
    std::unique_ptr<Json::StreamWriter>
        writer(wbuilder.newStreamWriter());
    std::stringstream ss;
    writer->write(root, &ss);
    std::cout << ss.str() << std::endl;
    return 0;
}

3.使用 Json::FastWriter

优点:比 StyledWriter 更快,因为它不添加额外的空格和换行符

#include <iostream>
#include <string>
#include <sstream>
#include <memory>
#include <jsoncpp/json/json.h>
int main()
{
    Json::Value root;
    root["name"] = "joe";
    root["sex"] = "男";
    Json::FastWriter writer;
    std::string s = writer.write(root);
    std::cout << s << std::endl;
    return 0;
}
4.3.2.2 反序列化

反序列化指的是将序列化后的数据重新转换为原来的数据结构或对象。Jsoncpp 提供了以下方法进行反序列化

1.使用 Json::Reader:

优点:提供详细的错误信息和位置,方便调试

#include <iostream>
#include <string>
#include <jsoncpp/json/json.h>
int main()
{
    // JSON 字符串
    std::string json_string = "{\"name\":\"张三\",\"age\":30, \"city\":\"北京\"}";
    // 解析 JSON 字符串
    Json::Reader reader;
    Json::Value root;
    // 从字符串中读取 JSON 数据
    bool parsingSuccessful = reader.parse(json_string,
                                          root);
    if (!parsingSuccessful)
    {
        // 解析失败,输出错误信息
        std::cout << "Failed to parse JSON: " << reader.getFormattedErrorMessages() << std::endl;
        return 1;
    }
    // 访问 JSON 数据
    std::string name = root["name"].asString();
    int age = root["age"].asInt();
    std::string city = root["city"].asString();
    // 输出结果
    std::cout << "Name: " << name << std::endl;
    std::cout << "Age: " << age << std::endl;
    std::cout << "City: " << city << std::endl;
    return 0;
}

2. 使用 Json::CharReader 的派生类:

在某些情况下,你可能需要更精细地控制解析过程,可以直接使用 Json::CharReader 的派生类

但通常情况下,使用 Json::parseFromStream 或 Json::Reader 的 parse 方法就足够了

4.4 Json::Value

Json::Value Jsoncpp 库中的一个重要类,用于表示和操作 JSON 数据结构。以下是一些常用的 Json::Value 操作列表

4.4.1 构造函数

  • Json::Value():默认构造函数,创建一个空的 Json::Value 对象。
  • Json::Value(ValueType type, bool allocated = false):根据给定的ValueType(如 nullValue, intValue, stringValue 等)创建一个 Json::Value

4.4.2 访问元素

  • Json::Value& operator[](const char* key):通过键(字符串)访问对象中的元素。如果键不存在,则创建一个新的元素
  • Json::Value& operator[](const std::string& key):同上,但使用std::string 类型的键
  • Json::Value& operator[](ArrayIndex index):通过索引访问数组中的元素。如果索引超出范围,则创建一个新的元素
  • Json::Value& at(const char* key):通过键访问对象中的元素,如果键不存在则抛出异常
  • Json::Value& at(const std::string& key):同上,但使用 std::string类型的键

4.4.3 类型检查

  • bool isNull():检查值是否为 null
  • bool isBool():检查值是否为布尔类型
  • bool isInt():检查值是否为整数类型
  • bool isInt64():检查值是否为 64 位整数类型
  • bool isUInt():检查值是否为无符号整数类型
  • bool isUInt64():检查值是否为 64 位无符号整数类型
  • bool isIntegral():检查值是否为整数或可转换为整数的浮点数
  • bool isDouble():检查值是否为双精度浮点数
  • bool isNumeric():检查值是否为数字(整数或浮点数)
  • bool isString():检查值是否为字符串
  • bool isArray():检查值是否为数组
  • bool isObject():检查值是否为对象(即键值对的集合)

4.4.4 赋值和类型转换

  • Json::Value& operator=(bool value):将布尔值赋给 Json::Value 对象
  • Json::Value& operator=(int value):将整数赋给 Json::Value 对象
  • Json::Value& operator=(unsigned int value):将无符号整数赋给Json::Value 对象
  • Json::Value& operator=(Int64 value):将 64 位整数赋给 Json::Value 对象
  • Json::Value& operator=(UInt64 value):将 64 位无符号整数赋给 Json::Value 对象
  • Json::Value& operator=(double value):将双精度浮点数赋给 Json::Value 对象
  • Json::Value& operator=(const char* value):将 C 字符串赋给 Json::Value 对象
  • Json::Value& operator=(const std::string& value):将 std::string 赋给 Json::Value 对象
  • bool asBool():将值转换为布尔类型(如果可能)
  • int asInt():将值转换为整数类型(如果可能)
  • Int64 asInt64():将值转换为 64 位整数类型(如果可能)
  • unsigned int asUInt():将值转换为无符号整数类型(如果可能)
  • UInt64 asUInt64():将值转换为 64 位无符号整数类型(如果可能)
  • double asDouble():将值转换为双精度浮点数类型(如果可能)
  • std::string asString():将值转换为字符串类型(如果可能)

4.4.5 数组和对象操作 

  • size_t size():返回数组或对象中的元素数量
  • bool empty():检查数组或对象是否为空
  • void resize(ArrayIndex newSize):调整数组的大小
  • void clear():删除数组或对象中的所有元素
  • void append(const Json::Value& value):在数组末尾添加一个新元素
  • Json::Value& operator[](const char* key, const Json::Value& defaultValue = Json::nullValue):在对象中插入或访问一个元素,如果键不存在则使用默认值
  • Json::Value& operator[](const std::string& key, const Json::Value& defaultValue = Json::nullValue):同上,但使用 std::string 类型的

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2245093.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小熊派Nano接入华为云

一、华为云IoTDA创建产品 创建如下服务&#xff0c;并添加对应的属性和命令。 二、小熊派接入 根据小熊派官方示例代码D6完成了小熊派接入华为云并实现属性上传命令下发。源码&#xff1a;小熊派开源社区/BearPi-HM_Nano 1. MQTT连接代码分析 这部分代码在oc_mqtt.c和oc_mq…

如何在 Ubuntu 上安装 Jellyfin 媒体服务器

Jellyfin 是一个开源的媒体服务器软件&#xff0c;让你可以整理、管理和流式传输你的个人媒体收藏&#xff0c;比如电影、音乐、电视节目和照片&#xff0c;而且完全免费&#xff0c;没有订阅费用或数据收集的担忧。 简介 媒体管理&#xff1a;Jellyfin 整理媒体库&#xff0…

Android集成FCM(Firebace Cloud Messaging )

集成FCM官方文档 Firebace主页面 将 Firebase 添加到您的 Android 应用 1、进入Firebace页面&#xff0c;创建自己的项目 2、点击自己创建好的项目&#xff0c;在右侧选择Cloud Messaging 3、点击Android去创建 google-services.json 4、将下载的 google-services.json 文件…

实时直播平台如何实现美颜功能?第三方美颜API与美颜SDK的技术

通过实时美颜技术&#xff0c;主播可以轻松实现肤色优化、五官调整以及滤镜效果&#xff0c;极大提升观众的观看体验。本篇文章&#xff0c;小编将深入讲解实时直播平台如何通过第三方美颜API与美颜SDK实现美颜功能&#xff0c;以及其中的技术实现与关键要点。 一、实时美颜的…

使用GDB或Delve对已经运行起来的Go程序进行远程调试

同步发布在我的博客&#xff0c;欢迎来点赞。 使用 GDB 或 Delve 对已经运行起来的 Go 程序进行远程调试 使用 GDB 或 Delve 对已经运行起来的 Go 程序进行远程调试 背景 Java 程序可以很方便地通过 jdwp 参数指定一个对外端口进行远程调试&#xff0c;如 java \ -agentlib…

Ubuntu问题 -- 设置ubuntu的IP为静态IP (图形化界面设置) 小白友好

目的 为了将ubuntu服务器IP固定, 方便ssh连接人在服务器前使用图形化界面设置 设置 找到自己的网卡名称, 我的是 eno1, 并进入设置界面 查看当前的IP, 网关, 掩码和DNS (注意对应eno1) nmcli dev show掩码可以通过以下命令查看完整的 (注意对应eno1) , 我这里是255.255.255.…

实现一个string的indexof方法,给出时空复杂度估计

文心快码(BaiduComate)是基于百度文心大模型&#xff0c;在研发全流程全场景下为开发者提供辅助建议的智能代码助手。结合百度积累多年的编程现场大数据、外部优秀开源数据&#xff0c;可为开发者生成更符合实际研发场景的优秀代码&#xff0c;提升编码效率&#xff0c;释放“十…

ESP8266 STA模式TCP客户端 电脑手机网络调试助手

1.STA模式TCP客户端和电脑网络调试助手 2.STA模式TCP客户端和手机网络调试助手

【lamafactory BLEU ROUGLE L评测】

1、BLEU/ROUGLE评测界面 2、这个是用BLEU 和ROUGL来评测 目录&#xff1a;saves/Qwen2-7B-Chat/lora/eval_2024-11-14-16-28-19/ 在saves文件夹 生成的文件如下 all_results.json文件 说明模型在这个测试集上是不好的 3、可以查看预测结果的文件 predict_result.json

Ros Noetic 20.04 跑通mpc_ros包保姆级教程

前言: 本文将简述mpc_ros包在noetic20.04中的安装,mpc是 一种跟踪、MPC_ROS 是一个基于ROS(Robot Operating System)的模型预测控制(Model Predictive Control,MPC)库。该项目旨在为机器人控制提供一个灵活且高效的MPC实现,使得开发者能够在ROS环境中轻松集成和使用MPC…

游戏+AI的发展历程,AI技术在游戏行业的应用有哪些?

人工智能&#xff08;AI&#xff09;与游戏的结合&#xff0c;不仅是技术进步的体现&#xff0c;更是人类智慧的延伸。从最初的简单规则到如今的复杂决策系统&#xff0c;AI在游戏领域的发展历史可谓波澜壮阔。 早在2001年&#xff0c;就有研究指出游戏人工智能领域&#xff0…

HarmonyOs DevEco Studio小技巧31--卡片的生命周期与卡片的开发

Form Kit简介 Form Kit&#xff08;卡片开发服务&#xff09;提供一种界面展示形式&#xff0c;可以将应用的重要信息或操作前置到服务卡片&#xff08;以下简称“卡片”&#xff09;&#xff0c;以达到服务直达、减少跳转层级的体验效果。卡片常用于嵌入到其他应用&#xff0…

Redis的过期删除策略和内存淘汰机制以及如何保证双写的一致性

Redis的过期删除策略和内存淘汰机制以及如何保证双写的一致性 过期删除策略内存淘汰机制怎么保证redis双写的一致性?更新策略先删除缓存后更新数据库先更新数据库后删除缓存如何选择&#xff1f;如何保证先更新数据库后删除缓存的线程安全问题&#xff1f; 过期删除策略 为了…

单元测试框架gtest学习(三)—— 事件机制

前言 上节我们学习了gtest的各种宏断言 单元测试框架gtest学习&#xff08;二&#xff09;—— 认识断言-CSDN博客 本节我们介绍gtets的事件机制 虽然 Google Test 的核心是用来编写单元测试和断言的&#xff0c;但它也允许在测试执行过程中进行事件的钩取和自定义&#xf…

Unity Inspector窗口可编辑的脚本变量

Inspector可编辑的脚本变量 默认会显示的变量 在Unity中&#xff0c;为了方便我们进行一些属性的设置及调试&#xff0c;我们所写的公有基础数据类型会显示在Inspector之中&#xff0c;我们可以对他进行设置来更改它的取值。 显示私有变量 在有些情况下&#xff0c;设计代码…

力扣 LeetCode 110. 平衡二叉树(Day8:二叉树)

解题思路&#xff1a; 等于 -1 时&#xff0c;直接 return -1 class Solution {public boolean isBalanced(TreeNode root) {return getHeight(root) ! -1;}public int getHeight(TreeNode root) {if (root null) return 0;int leftDepth getHeight(root.left);if (leftDep…

unity3d————基础篇小项目(设置界面)

代码示例&#xff1a; 设置界面 using System.Collections; using System.Collections.Generic; using UnityEngine;public class SettingPanel : BasePanel<SettingPanel> {public UIButton btnClose;public UISlider sliderMusic;public UISlider sliderSound;public…

商用密码产品认证名录说明

《商用密码产品认证目录》是为贯彻落实《中华人民共和国密码法》&#xff0c;进一步健全完善商用密码产品认证体系&#xff0c;更好满足商用密码产业发展需要&#xff0c;根据《国家密码管理局 市场监管总局关于调整商用密码产品管理方式的公告》《市场监管总局 国家密码管理局…

从零做一个遥控玩具,需要学什么

遥控玩具编程是一个充满乐趣和挑战的领域&#xff0c;它完美地结合了硬件控制和软件编程。记得小时候拿着遥控器操控玩具汽车&#xff0c;总觉得神奇。如今站在程序员的视角&#xff0c;终于明白这背后是怎样的技术在支撑。 这些有趣的遥控玩具&#xff0c;其核心都是基于单片…

java的web项目如何连接数据库

mysql-connector.jar 是一个Java库文件&#xff0c;它提供了Java应用程序与MySQL数据库进行交互的接口 首先准备好这个文件&#xff0c;没有的话在网上搜索 将它放在这个位置&#xff0c;MySQL连接配置就好了&#xff0c; 如果拖不进去&#xff0c;右击项目名称&#xff0c;…