【网络编程】自定义协议+Json序列化与反序列化

news2024/10/5 14:26:54

文章目录

  • 一、序列化与反序列化概念
  • 二、自定义协议设计网络计算机
    • 2.1 服务端
      • 2.1.1 服务端业务处理流程
      • 2.1.2 TCP的发送与接收缓冲区
      • 2.1.3 保证读取完整报文
      • 2.1.4 自定义协议——序列化与反序列化
        • 2.1.4.1 请求
        • 2.4.1.2 响应
      • 2.1.5 计算流程
      • 2.1.6 在有效载荷前添加长度报头
      • 2.1.7 发送响应send
      • 2.1.8 读取一个完整的报文recv
    • 2.2 客户端
    • 2.3 结果
  • 三、使用Json进行序列化和反序列化

一、序列化与反序列化概念

上一章讲解了TCP通信【网络编程】demo版TCP网络服务器实现,我们知道TCP是面向字节流的方式进行通信。
在这里插入图片描述
但是这里就会引发一个问题:怎么保证正好就读到一个完整的数据呢?

举个例子:我们使用QQ发送消息的时候别人接收到的不仅仅只有消息,而是包含了头像信息,昵称,消息。这就叫做结构化的数据。这些结构化的数据可以打包成一个报文(变成一个整体),这个过程就叫做序列化。而把这个整体报文解开的过程就叫做反序列化

结构化数据要先序列化再发送到网络中,收到序列字节流后,要先反序列化再使用。

而这里序列化和反序列化的过程用的就是业务协议

二、自定义协议设计网络计算机

2.1 服务端

自定义协议里要包含两各类,一个是请求,一个是响应
服务端会收到请求,客户端收到响应。

// 请求
class Request
{
public:
    
public:
    int _x = 0;
    int _y = 0;
    char _op = 0;
};

// 响应
class Response
{
public:
    int _exitcode = 0;// 退出码
    int _result = 0;// 结果
};

请求就是左操作符、右操作符和符号
响应包含了退出码和结果,如果正常结束退出码为0,如果有错误,我们可以自定义不同的退出码表示不同的错误。

2.1.1 服务端业务处理流程

先来看一下服务端处理数据流程

客户端发过来的数据已经序列化成了一个序列字节流数据(报文),所以服务端首先要先把报文反序列化,构成一个结构化请求对象Request。然后就可以进行计算处理形成一个Response对象,再序列化后发送给客户端。

可以看到计算处理这一步其实跟接收发送消息、序列化与反序列化没什么关系,所以可以把计算处理任务在服务端启动的时候传递进去

计算处理函数:
typedef std::function<bool(const Request& req, Response& resp)> func_t;
这里的req是输入型参数(已经反序列化好的对象),resp是输出型参数,为了获取计算结果。

2.1.2 TCP的发送与接收缓冲区

在这里插入图片描述

我们前面使用的write和read接口并不是直接往网络里发送数据或者从网络里读取数据,write其实是把数据拷贝到传输层的缓冲区,由TCP协议决定什么时候把缓冲区的数据发送到网络中。所以TCP协议也叫传输控制协议
发送数据的本质就是将数据从发送缓冲区拷贝到接收缓冲区。

所以客户端/服务端发送数据不会影响接受数据。
所以TCP是全双工的。

而这就会导致一个问题:可能数据堆积在缓冲区来不及度,一次会读取多个报文挨在一起。那么怎么保证读取完整报文呢?

2.1.3 保证读取完整报文

因为TCP是面向字节流的,所以要明确报文与报文的分界。
为什么要这样呢?举个例子:
现在要把两个数字合并成字符串发送,1、12,如果不处理的话就是"112",这样我们反序列化的时候就不知道到底怎么组合了。
而如果我们在分割的地方加一个符号比如,,序列化后:"1,12",这样就很容易拆分。

保证报文读取完整性的方法:
1️⃣ 定长: 规定长度,每次就读取这么多。
2️⃣ 特殊字符: 就是上面的方法。
3️⃣ 自描述方式: 比如在报文前面带上四个字节的字段,标识报文长度。

2.1.4 自定义协议——序列化与反序列化

先来看请求的序列化与反序列化

2.1.4.1 请求

int _x = 0;
int _y = 0;
char _op = 0;

我们希望序列化成这样:"_x _op _y"

#define SEP " "
#define SEP_LEN strlen(SEP)
#define SEP_LINE "\r\n"
#define SEP_LINE_LEN strlen(SEP_LINE)

// 请求
class Request
{
public:
    Request(int x, int y, char op)
       : _x(x)
       , _y(y)
       , _op(op)
    {}
    
    Request()
    {}

    // 序列化
    bool serialize(std::string* out/*输出型参数*/)
    {
        // "_x _op _y"
        std::string sx = std::to_string(_x);
        std::string sy = std::to_string(_y);
        *out = sx + SEP + _op + SEP + sy;
        return true;
    }

    // 反序列化
    bool deserialize(const std::string& in)
    {
        // "_x _op _y"
        auto lsep = in.find(SEP);
        auto rsep = in.rfind(SEP);
        if(lsep == std::string::npos || rsep == std::string::npos
        || lsep == rsep) return false;
        std::string sx = in.substr(0, lsep);
        std::string sy = in.substr(rsep + SEP_LEN);
        if(sx.empty() || sy.empty()) return false;
        _x = stoi(sx);
        _y = stoi(sy);
        _op = in[lsep + SEP_LEN];
        return true;
    }
public:
    int _x = 0;
    int _y = 0;
    char _op = 0;
};

这里的反序列化我们传进去的字符串已经把"\r\n"去掉了。
先来看响应的序列化与反序列化

2.4.1.2 响应

我们希望序列化成这样:"_exitcode _result"

// 响应
class Response
{
public:
    Response(int exitcode, int result)
        : _exitcode(exitcode)
        , _result(result)
    {}

    Response()
    {}

    // 序列化
    bool serialize(std::string* out/*输出型参数*/)
    {
        std::string se = std::to_string(_exitcode);
        std::string sr = std::to_string(_result);
        *out = se + SEP + sr;
        return true;
    }
    
    // 反序列化
    bool deserialize(const std::string& in)
    {
        // "_exitcode _result"
        auto pos = in.find(SEP);
        if(pos == std::string::npos) return false;
        std::string se = in.substr(0, pos);
        std::string sr = in.substr(pos + SEP_LEN);
        if(se.empty() || sr.empty()) return false;
        _exitcode = stoi(se);
        _result = stoi(sr);
        return true;
    }
public:
    int _exitcode = 0;// 退出码
    int _result = 0;// 结果
};

2.1.5 计算流程

计算结果会形成一个resp响应,里面包含了退出码,我们可以自己设置退出码数值含义:

enum {
    OK,
    DIV_ZERO,
    OP_ERROR
};

计算逻辑:

std::unordered_map<char, std::function<int(int, int)>> hash = 
{
    {'+', [](int x, int y)->int{return x + y;}},
    {'-', [](int x, int y)->int{return x - y;}},
    {'*', [](int x, int y)->int{return x * y;}},
    {'/', [](int x, int y)->int{return x / y;}},
    {'%', [](int x, int y)->int{return x % y;}},
};

bool calc(const Request& req, Response& resp)
{
    // req已经反序列化好了
    if(!hash.count(req._op)) 
    {
        resp._exitcode = OP_ERROR;
        return false;
    }
    if(req._op == '/' || req._op == '%')
    {
        if(req._y == 0)
        {
            resp._exitcode = DIV_ZERO;
            return false;
        }
    }
    resp._result = hash[req._op](req._x, req._y);
    return true;
}

2.1.6 在有效载荷前添加长度报头

在这里插入图片描述

"_x _op _y" -> "content_len\r\n_x _op _y\r\n"
"_exitcode _result" -> "content_len\r\n_exitcode _result\r\n"
// 给有效载荷添加报头信息
std::string enLength(const std::string& text)
{
    std::string send_str = std::to_string(text.size());
    send_str += SEP_LINE + text + SEP_LINE;
    return send_str;
}  

2.1.7 发送响应send

#include <sys/types.h>
#include <sys/socket.h>

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

RETURN VALUE
On success, these calls return the number of characters sent.  
On error, -1 is returned, and errno is set appropriately.

服务端收到请求到把响应发送出去的整个流程:

// 处理请求的入口
void handler(int sock, func_t func)
{
    // 得到序列化好的请求对象
    
    std::string req_str;
    // 得到结构化请求对象
    Request req;
    if(!req.deserialize(req_str)) return;
    // 计算,得到响应
    Response resp;
    func(req, resp);
    // 序列化响应
    std::string resp_str;
    resp.serialize(&resp_str);
    // 添加报头
    std::string send_str = enLength(resp_str);
    // 发送响应
    send(sock, send_str.c_str(), send_str.size(), 0);
}

那么这里的第一步是怎么读取请求的呢?
这个请求必须是恰好一个完整的请求。

2.1.8 读取一个完整的报文recv

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

RETURN VALUE
These  calls return the number of bytes received, or -1 if an error occurred.  In the event of an error, errno is set to indicate the error.  The
return value will be 0 when the peer has performed an orderly shutdown.
// "content_len\r\n_x _op _y\r\n"
// 读取一个完整报文
bool recvPackage(int sock, std::string& inbuf, std::string* out)
{
    char buf[1024];
    while(true)
    {
        ssize_t n = recv(sock, buf, sizeof buf - 1, 0);
        if(n > 0)
        {
            buf[n] = '\0';
            inbuf += buf;
            auto pos = inbuf.find(SEP_LINE);
            if(pos == std::string::npos) continue;// 还得继续读取
            std::string text_len = inbuf.substr(0, pos);
            int content_len = stoi(text_len);
            int total_len = text_len.size() + 2 * SEP_LINE_LEN + content_len;// 一个完整报文长度
            if(inbuf.size() < total_len) continue;// 还得继续读取
            // 至少有一个完整报文
            *out = inbuf.substr(0, total_len);
            inbuf.erase(0, total_len);
            return true;
        }
        else return false;
    }
    return true;
}

收到的请求还需要去掉报头

// 去掉有效载荷的报头信息
bool deLength(const std::string& pack, std::string *out)
{
    auto pos = pack.find(SEP_LINE);
    if(pos == std::string::npos) return false;
    std::string text_len_string = pack.substr(0, pos);
    int text_len = stoi(text_len_string);
    *out = pack.substr(pos + SEP_LINE_LEN, text_len);
    return true;
}

这样服务端的业务逻辑就完成了:

// 处理请求的入口
void handler(int sock, func_t func)
{
    std::string inbuf;// 输入缓冲区
    while(1)
    {
        // 得到序列化好的请求对象
        std::string req_text;
        if(!recvPackage(sock, inbuf, &req_text)) return;
        std::string req_str;
        if(!deLength(req_text, &req_str)) return;
        // 得到结构化请求对象
        Request req;
        if(!req.deserialize(req_str)) return;
        // 计算,得到响应
        Response resp;
        func(req, resp);
        // 序列化响应
        std::string resp_str;
        resp.serialize(&resp_str);
        // 添加报头
        std::string send_str = enLength(resp_str);
        // 发送响应
        send(sock, send_str.c_str(), send_str.size(), 0);
    }
}

2.2 客户端

大致流程跟服务端差不多:

void start()
{
    struct sockaddr_in si;
    bzero(&si, sizeof si);
    si.sin_family = AF_INET;
    si.sin_port = htons(_serverport);
    si.sin_addr.s_addr = inet_addr(_serverip.c_str());
    if(connect(_sock, (struct sockaddr*)&si, sizeof si) < 0)
    {
        std::cout << "connect socket error" << std::endl;
    }
    else
    {
        std::string msg;
        std::string inbuf;// 输入缓冲区
        while(1)
        {
            std::cout << "Please Enter#";
            std::getline(std::cin, msg);// 1+2
            // 解析字符串
            Request req = PraseMsg(msg);
            // 序列化
            std::string content;
            req.serialize(&content);
            // 添加报头
            std::string send_str = enLength(content);
            // 发送
            send(_sock, send_str.c_str(), send_str.size(), 0);
            // 获取响应结果
            std::string package;
            // "content_len\r\n_x _op _y\r\n"
            if(!recvPackage(_sock, inbuf, &package)) continue;// 还要继续读
            // 去掉报头,提取正文
            std::string text;
            if(!deLength(package, &text)) continue;
            // 反序列化获取退出码和结果
            Response resp;
            resp.deserialize(text);
            std::cout << "exitcode: " << resp._exitcode << std::endl;
            std::cout << "result: " << resp._result << std::endl;
        }
    }
}

// 解析字符串
Request PraseMsg(const std::string& msg)
{
    // "123+456"
    int idx_op = 0;
    int idx = 0, n = msg.size();
    // 找符号位置
    while(idx < n)
    {
        if(hash.count(msg[idx]))
        {
            idx_op = idx;
            break;
        }
        idx++;
    }
    Request req;
    std::string sx = msg.substr(0, idx_op);
    std::string sy = msg.substr(idx_op + 1);
    req._x = stoi(sx);
    req._y = stoi(sy);
    req._op = msg[idx_op];
    return req;
}

流程就是序列化请求,添加报头,发送,接收响应,去掉报头,反序列化,获取结果。

2.3 结果

客户端:
在这里插入图片描述
服务端:
在这里插入图片描述

三、使用Json进行序列化和反序列化

序列化与反序列化其实C++提供了Json的库。我们可以直接使用:

Json(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于Web应用程序中的数据传输。它是一种基于文本的格式,易于读写和解析。Json格式的数据可以被多种编程语言支持,包括JavaScript、Python、Java、C#、C++等。Json数据由键值对组成,使用大括号表示对象,使用方括号表示数组。

首先先安装Json库。

sudo yum install -y jsoncpp-devel

头文件:#include <jsoncpp/json/json.h>

使用jsoncpp库记得在编译时加上-ljsoncpp

Makefile:

.PHONY:all
all:CalcServer CalcClient

CalcClient:CalcClient.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp #-DMYSELF
CalcServer:CalcServer.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp #-DMYSELF

.PHONY:clean
clean:
	rm -f CalcClient CalcServer
// 请求
class Request
{
public:
    Request(int x, int y, char op)
       : _x(x)
       , _y(y)
       , _op(op)
    {}

    Request()
    {}

    // 序列化
    bool serialize(std::string* out/*输出型参数*/)
    {
#ifdef MYSELF
        // "_x _op _y"
        std::string sx = std::to_string(_x);
        std::string sy = std::to_string(_y);
        *out = sx + SEP + _op + SEP + sy;
#else
        Json::Value root;// 万能对象,可接收任何对象
        root["first"] = _x;// 自动将_x转换为字符串
        root["second"] = _y;
        root["oper"] = _op;
        // 序列化
        Json::FastWriter writer;
        *out = writer.write(root);// 将root进行序列化
#endif
        return true;
    }

    // 反序列化
    bool deserialize(const std::string& in)
    {
#ifdef MYSELF
        // "_x _op _y"
        auto lsep = in.find(SEP);
        auto rsep = in.rfind(SEP);
        if(lsep == std::string::npos || rsep == std::string::npos
        || lsep == rsep) return false;
        std::string sx = in.substr(0, lsep);
        std::string sy = in.substr(rsep + SEP_LEN);
        if(sx.empty() || sy.empty()) return false;
        _x = stoi(sx);
        _y = stoi(sy);
        _op = in[lsep + SEP_LEN];
#else
        //Json反序列化
        Json::Value root;// 万能对象,可接收任何对象
        Json::Reader reader;
        reader.parse(in,root);// 第一个参数:解析哪个流;第二个参数:将解析的数据存放到对象中
        //反序列化
        _x = root["first"].asInt();// 默认是字符串,转换为整型
        _y = root["second"].asInt();
        _op = root["oper"].asInt();// 转换为整型,整型可以给char类型
#endif
        return true;
    }
public:
    int _x = 0;
    int _y = 0;
    char _op = 0;
};

// 响应
class Response
{
public:
    Response(int exitcode, int result)
        : _exitcode(exitcode)
        , _result(result)
    {}

    Response()
    {}

    // 序列化
    bool serialize(std::string* out/*输出型参数*/)
    {
#ifdef MYSELF
        std::string se = std::to_string(_exitcode);
        std::string sr = std::to_string(_result);
        *out = se + SEP + sr;
#else
        Json::Value root;// 万能对象,可接收任何对象
        root["exitcode"] = _exitcode;// 自动将_exit转换为字符串
        root["result"] = _result;
        // 序列化
        Json::FastWriter writer;
        *out = writer.write(root);// 将root进行序列化
#endif
        return true;
    }
    
    // 反序列化
    bool deserialize(const std::string& in)
    {
        // "_exitcode _result"
#ifdef MYSELF
        auto pos = in.find(SEP);
        if(pos == std::string::npos) return false;
        std::string se = in.substr(0, pos);
        std::string sr = in.substr(pos + SEP_LEN);
        if(se.empty() || sr.empty()) return false;
        _exitcode = stoi(se);
        _result = stoi(sr);
#else
        //Json反序列化
        Json::Value root;// 万能对象,可接收任何对象
        Json::Reader reader;
        reader.parse(in,root);// 第一个参数:解析哪个流;第二个参数:将解析的数据存放到对象中
        //反序列化
        _exitcode = root["exitcode"].asInt();// 默认是字符串,转换为整型
        _result = root["result"].asInt();
#endif
        return true;
    }
public:
    int _exitcode = 0;// 退出码
    int _result = 0;// 结果
};


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

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

相关文章

解决vmWare ESXI 7.3报错,客户机操作系统已禁用 CPU。请关闭或重置虚拟机(ESXI使用遇到问题解决记录文持续使用持续更新)

一&#xff1a;分析客户机操作系统已禁用 CPU" 这个错误通常是由以下原因之一引起的&#xff1a; 1. 虚拟机配置不正确&#xff1a;可能是您在虚拟机配置中选择了不受支持的 CPU 类型或功能。某些操作系统可能需要特定的 CPU 功能才能正常运行。如果您的虚拟机配置与操作…

下载【T0】指令微调数据集

T0 benchmark&#xff08;或者叫P3&#xff09;是一个大规模的人工标注instruction tuning数据集&#xff0c;在ICLR 2021 T0一文中提出&#xff0c;其收集了来自huggingface hub上的多任务数据&#xff0c;并为每一个task都装备了来自prompt source的人工撰写指令。 P3数据集…

9、DataX安装部署

1、 DataX简介 1.1 DataX概述 DataX 是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 源码地址&#xff1a;https://github.com/alibaba/…

基于高校图书馆的用户画像、可视化、模型预测、推荐算法项目实现

需要本项目的可以私信博主获取源码及项目&#xff01;&#xff01;&#xff01; 本研究基于高校图书馆的借阅信息、馆藏图书信息、读者入馆信息、用户信息等多维度的数据表&#xff0c;首先将不同年份的数据拼接在一起&#xff0c;按照时间维度进行整合&#xff0c;并保证数据…

快慢指针专题

总体思想&#xff1a;详解为什么用一步两步快慢指针&#xff1f;三步四步可以吗 - 预言2018 - 博客园 (cnblogs.com) 1. 为什么快慢指针一定会相遇&#xff1a; 2. 为什么 quick 指针每次走两步&#xff0c;而不是3、4步骤&#xff0c;是因为 如上图所示&#xff0c;若走2步&a…

TDsql增量merge导入load_data

TDsql增量merge导入load_data 项目组最近用了腾讯的TencentDB分布式数据库作为传统关系型数据库来保存少量应用数据。因此需要开发相对应的ETL功能代码&#xff0c;根据新数据库特性&#xff0c;使用自带的工具load_data作为导入的工具 准备表 使用load_data导入的表&#xf…

Python运维自动化Paramiko模块

paramiko学习笔记 为什么要用到paramiko模块安装paramiko模块paramiko介绍SSHClient---连接服务器exec_command---执行命令SFTPClient---传输文件下载和上传文件其它方法 为什么要用到paramiko模块 工作中常用到Linux服务器需要更新开发的代码&#xff0c;之前一直是使用xshell…

Python_多任务:进程、线程、协程

目录 进程 实现多进程 进程池 实现进程池 线程 实现多线程 多线程的资源竞争问题 互斥锁解决资源竞争问题 死锁 协程 gevent 进程 进程是一个具有一定独立功能的程序在一个数据集上的一次动态执行的过程&#xff0c;是操作系统进行资源分配和调度的一个独立单位&am…

“多”维演进:智能编码的深度进化

我们在追求怎样的编码未来&#xff1f; 无处不在的视频渗透、井喷式的流量增长、多元的场景技术需求、用户对视频体验的“不将就”……音视频行业的快速发展却伴随着“编码标准升级速度缓慢”、“硬件红利见底”、“编码复杂度带来的成本问题”等众多挑战。 视频编码还“卷”得…

SQL Server 2008 r2 修改sa密码 通过sql server 身份验证登陆

SQL Server的两种身份验证模式是Windows验证模式和SQL Server验证模式。 sql server 2008 r2 修改sa密码 通过sql server 身份验证登陆 【解决方法如下】 通过windows 身份验证登陆后,首先选中服务器(右键)->属性 ->安全性->服务器身份验证修改为"SQL SERVER和W…

浅谈电瓶车在线充电管理系统的设计与应用

安科瑞 华楠 摘要&#xff1a;基于ARM 硬件平台&#xff0c;研究了电瓶车充电技术&#xff0c;提出了一种智能型电瓶车在线安全充电系统&#xff0c;该系统可根据实际充电情况实现智能断电&#xff0c;同时提供给用户一种远程充电、断电的平台&#xff0c;目的是防止电瓶车过度…

港联证券|个人的分红要交税吗?

近年来&#xff0c;随着经济的快速发展&#xff0c;越来越多的人开始关注个人财务管理&#xff0c;其中一个重要的问题就是个人的分红是否需要缴纳税款。这个问题并不简单&#xff0c;需要从多个角度进行综合分析。 首先&#xff0c;我们需要明确一点&#xff0c;个人的分红属于…

浅谈数据中心机房动环监控系统可视化设计与研究

安科瑞电气股份有限公司 上海嘉定 201801 摘要&#xff1a;为了维护好数据中心机房动力环境&#xff0c;及时发现隐患和排除故障&#xff0c;降低管理成本控制能耗&#xff0c;提高运维效率&#xff0c;保障数据中心安全、高效、环保、稳定的运行&#xff0c;针对目前机房管理…

通过五点判断CRM系统是否好用

CRM管理系统在当今市场竞争中扮演着越来越重要的角色。因此&#xff0c;企业选择一款适合自己的CRM系统是非常关键的。那么&#xff0c;如何评价一款CRM系统是否好用&#xff1f; 1、功能是否全面 好用的CRM系统应该能够覆盖企业与客户交互的全过程&#xff0c;包括营销管理、…

【分布式技术专题】「缓存解决方案」一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战(数据缓存不一致分析)

一文带领你好好认识一下企业级别的缓存技术解决方案的运作原理和开发实战&#xff08;数据缓存不一致问题分析&#xff09; 数据不一致的原因逻辑失败导致的数据不一致物理失败导致的数据不一致 数据一致性的解决方案消费消息异步删除缓存主要流程如下图所示 订阅Binlog利用队列…

只出现一次的数字

题目链接 只出现一次的数字 题目描述 注意点 1 < nums.length < 30000-30000 < nums[i] < 30000除了某个元素只出现一次以外&#xff0c;其余每个元素均出现两次 解答思路 最初想到使用一种数据结构将元素存储起来&#xff0c;但是空间复杂度为O(n)&#xff0…

Linux(ubuntu)上安装vmware workstation虚拟机

Linux&#xff08;ubuntu&#xff09;上安装vmware workstation虚拟机 首先下载vmware workstation 官网下载地址&#xff1a;https://www.vmware.com/products/workstation-pro/workstation-pro-evaluation.html 下滑至页面下方下载&#xff1a; 或者点击此处下载 下载完成后…

首发价11999元?华为智慧屏S3Pro电视7月10日上市

华为最新推出了两款全新的智慧屏 S3 Pro&#xff0c;分别是65英寸和75英寸版本&#xff0c;售价分别为5999元和7999元。除此之外&#xff0c;华为还推出了全新的S3 Pro 86英寸型号&#xff0c;首发价为11999元。这款电视将于7月10日上市&#xff0c;对于感兴趣的用户来说&#…

押注数字人,百度、科大讯飞“短兵相接”

配图来自Canva可画 近两年&#xff0c;小雀斑、柳夜熙、AYAYI等大量网红数字人的相继出现&#xff0c;以及虚拟数字技术在《指环王》和《阿凡达》以及《刺杀小说家》等电影中的广泛应用&#xff0c;还有北京冬奥会期间数字人在手语解说、节目直播等众多场合亮相&#xff0c;使…

2024浙大GMSCM提面第一场:全英文项目的生死选择题

本周末是今年浙大MBA非全英文项目GMSCM的第一场提前批面试&#xff0c;作为诸多方向中相对比较稳定的项目之一&#xff0c;GMSCM项目每年的提前批面试申请也都表现的比较稳健&#xff0c;而其目前主要的招生也是依托于提前批面试&#xff0c;根据这几年的情况&#xff0c;每年浙…