当前位置: 首页 > news >正文

Linux/C++:基于TCP协议实现网络版本计算器(自定义应用层协议)

目录

Sock.hpp

TcpServer.hpp

Protocol.hpp

CalServer.cc

CalClient.cc

分析


因为,TCP面向字节流,所以TCP有粘包问题,故我们需要应用层协议来区分每一个数据包。防止读取到半个,一个半数据包的情况。

Sock.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "log.hpp"// 对于一些TCP相关调用的封装
class Sock
{
private:const static int gback_log = 20;
public:int Socket(){// 1. 创建套接字,成功返回对应套接字,失败直接进程exitint listen_sock = socket(AF_INET, SOCK_STREAM, 0); // 网络套接字, 面向字节流(tcp)if (listen_sock < 0){logMessage(FATAL, "create listen socket error, %d:%s", errno, strerror(errno));exit(2);}logMessage(NORMAL, "create listen socket success: %d", listen_sock); // 1111Logreturn listen_sock;}void Bind(int sock, uint16_t port, std::string ip = "0.0.0.0"){// 2. bind,注意云服务器不能绑定公网IP,不允许。// 成功bind则成功bind,失败进程exit(bind不在循环语句内,故失败直接进程退出。)struct sockaddr_in local;memset(&local, 0, sizeof local);local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = inet_addr(ip.c_str());if (bind(sock, (struct sockaddr *)&local, sizeof local) < 0){logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));exit(3);}}void Listen(int sock){// 3. listen监听: 因为TCP是面向连接的,在我们正式通信之前,需要先建立连接// listen: 将套接字状态设置为监听状态。服务器要一直处于等待状态,这样客户端才能随时随地发起连接。// 成功则成功,失败则exitif (listen(sock, gback_log) < 0) // gback_log后面讲,全连接队列的长度。我猜测就是这个服务器同一时刻允许连接的客户端的数量最大值?也不太对呀,这么少么?{logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));exit(4);}logMessage(NORMAL, "listen success");}// 一般经验// const std::string &: 输入型参数// std::string *: 输出型参数// std::string &: 输入输出型参数int Accept(int sock, uint16_t *port, std::string *ip){// accept失败进程不退出,返回-1// 成功则返回对应的通信套接字struct sockaddr_in client;socklen_t len = sizeof client;// 其实accept是获取已经建立好的TCP连接。建立好的连接在一个内核队列中存放,最大数量的第二个参数+1int service_sock = accept(sock, (struct sockaddr *)&client, &len); // 返回一个用于与客户端进行网络IO的套接字,不同于listen_sock// On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket.  On error, -1 is returned, and errno is set appropriately.if (service_sock < 0){logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));return -1;  // accept失败不直接exit,而是返回-1。因为在循环语句内部。}if (port)*port = ntohs(client.sin_port);if (ip)*ip = inet_ntoa(client.sin_addr);logMessage(NORMAL, "link(accept) success, service socket: %d | %s:%d", service_sock,(*ip).c_str(), *port);return service_sock;}int Connect(int sock, const std::string &ip, const uint16_t &port){// 惯例写一下:失败返回-1,成功则客户端与服务端连接成功,返回0struct sockaddr_in server;memset(&server, 0, sizeof server);server.sin_family = AF_INET;server.sin_addr.s_addr = inet_addr(ip.c_str());server.sin_port = htons(port);if (connect(sock, (struct sockaddr *)&server, sizeof server) < 0){return -1;}return 0;}
public:Sock() = default;~Sock() = default;
};

TcpServer.hpp

#ifndef _TCP_SERVER_HPP_
#define _TCP_SERVER_HPP_#include "Sock.hpp"
#include <vector>
#include <functional>// 说实话,这个TcpServer类实现的非常棒,真的很棒,网络和服务进行了解耦。
// 使用者直接BindServer, 然后start即可
namespace ns_tcpserver
{using func_t = std::function<void(int socket)>; // 服务器提供的服务方法类型void(int),可变class TcpServer;class ThreadData{public:ThreadData(int sock, TcpServer *server): _sock(sock), _server(server){}~ThreadData() {}public:int _sock;TcpServer *_server; // 因为静态成员函数呀};class TcpServer{// 不关心bind的ip和port,因为用不到啊,保留一个listen_sock用于accept就够了。private:int _listen_sock;Sock _sock;std::vector<func_t> _funcs; // 服务器提供的服务private:static void *threadRoutine(void *args){pthread_detach(pthread_self());    // 线程分离(避免类似于僵尸进程状态)ThreadData *td = (ThreadData *)args;td->_server->excute(td->_sock); // 提供服务close(td->_sock);   // 保证四次挥手正常结束delete td;return nullptr;}public:TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0"){// 创建监听套接字,bind,listen_listen_sock = _sock.Socket();_sock.Bind(_listen_sock, port, ip);_sock.Listen(_listen_sock);}void start(){for (;;){// 开始accept,然后执行任务std::string ip;uint16_t port;     // 这两个东西,也并没有传给线程。int sock = _sock.Accept(_listen_sock, &port, &ip); // 后面是输出型参数if (sock == -1)continue; // 本次accept失败,循环再次accept。目前来看几乎不会// 连接客户端成功,ip port已有。但是这里没用...pthread_t tid;ThreadData *td = new ThreadData(sock, this);pthread_create(&tid, nullptr, threadRoutine, (void *)td); // 新线程去提供service,主线程继续accept}}void bindService(func_t service)   // 暴露出去的接口,用于设置该服务器的服务方法{_funcs.push_back(service);}void excute(int sock){for (auto &func : _funcs){func(sock);}}~TcpServer(){if (_listen_sock >= 0)close(_listen_sock);}};
}
#endif

Protocol.hpp

#ifndef _PROTOCOL_HPP_
#define _PROTOCOL_HPP_#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <jsoncpp/json/json.h>// important and new
namespace ns_protocol
{// #define MYSELF 1 // 自己实现序列化反序列化还是使用json库#define SPACE " "
#define SPACE_LENGTH strlen(SPACE)
#define SEP "\r\n"
#define SEP_LENGTH strlen(SEP)// 请求和回复,都需要序列化和反序列化的成员函数// 序列化和反序列化双方都不同。但是添加报头和去报头是相同的,"Length\r\nxxxxx\r\n";// 客户端生成请求,序列化之后发送给服务端class Request{public:Request() = default;Request(int x, int y, char op): _x(x), _y(y), _op(op){}~Request() {}public:int _x;int _y;char _op;public:std::string serialize(){// 序列化为"_x _op _y"  (注意,序列化和添加报头是分开的,反序列化和去掉报头是分开的
#ifdef MYSELFstd::string s = std::to_string(_x);s += SPACE;s += _op;s += SPACE;s += std::to_string(_y);return s;
#elseJson::Value root;root["x"] = _x;root["y"] = _y;root["op"] = _op;Json::FastWriter writer;return writer.write(root);
#endif}bool deserialize(const std::string &s){
#ifdef MYSELF// "_x _op _y"std::size_t left = s.find(SPACE);if (left == std::string::npos)return false;std::size_t right = s.rfind(SPACE);if (right == left)return false;_x = atoi(s.substr(0, left).c_str());_op = s[left + SPACE_LENGTH];_y = atoi(s.substr(right + SPACE_LENGTH).c_str());
#elseJson::Value root;Json::Reader reader;reader.parse(s, root);_x = root["x"].asInt();_y = root["y"].asInt();_op = root["op"].asInt();
#endifreturn true;}};// 服务端收到请求,反序列化,业务处理生成response,序列化后发送给客户端class Response{public:Response(int result = 0, int code = 0): _result(result), _code(code){}~Response() {}public:std::string serialize(){// 序列化为"_code _result"  (注意,序列化和添加报头是分开的,反序列化和去掉报头是分开的
#ifdef MYSELFstd::string s = std::to_string(_code);s += SPACE;s += std::to_string(_result);return s;
#elseJson::Value root;root["code"] = _code;root["result"] = _result;Json::FastWriter writer;return writer.write(root);
#endif}bool deserialize(const std::string &s){
#ifdef MYSELF// "_code _result"std::size_t pos = s.find(SPACE);if (pos == std::string::npos)return false;_code = atoi(s.substr(0, pos).c_str());_result = atoi(s.substr(pos + SPACE_LENGTH).c_str());
#elseJson::Value root;Json::Reader reader;reader.parse(s, root);_result = root["result"].asInt();_code = root["code"].asInt();
#endifreturn true;}public:int _result;int _code; // 状态码, 防止除零,模零,和其他错误(比如非法运算符运算符)。code == 0时,result有效。};// 进行去报头,报文完整则去报头,并返回有效载荷,不完整则代表失败返回空字符串。std::string deCode(std::string &s) // 输入型输出型参数{// "Length\r\nx op y\r\n"   成功返回有效载荷,失败返回空串std::size_t left = s.find(SEP);if (left == std::string::npos)return "";std::size_t right = s.rfind(SEP);if (right == left)return "";int length = atoi(s.substr(0, left).c_str());if (length > s.size() - left - 2 * SEP_LENGTH)return ""; // 有效载荷长度不足,不是一个完整报文,其实经过上面两次的if判断已经够了可能。// 是一个完整报文,进行提取std::string ret;s.erase(0, left + SEP_LENGTH);ret = s.substr(0, length);s.erase(0, length + SEP_LENGTH);return ret;}std::string enCode(const std::string &s){// "Length\r\n1+1\r\n"std::string retStr = std::to_string(s.size());retStr += SEP;retStr += s;retStr += SEP;return retStr;}// 我真的很想用引用,但是好像传统规则是输出型参数用指针...// 其实这个Recv就是一个单纯的读数据的函数,将接收缓冲区数据读到应用层缓冲区中,也就是*s中。存储的是对端发来的应用层报文。bool Recv(int sock, std::string *s){// 仅仅读取数据到*s中char buff[1024];ssize_t sz = recv(sock, buff, sizeof buff, 0);if (sz > 0){buff[sz] = '\0';*s += buff;    return true;}else if (sz == 0){std::cout << "peer quit" << std::endl;return false;}else{std::cout << "recv error" << std::endl;return false;}}bool Send(int sock, const std::string &s){ssize_t sz = send(sock, s.c_str(), s.size(), 0);if (sz > 0){return true;}else{std::cout << "send error!" << std::endl;return false;}}
}#endif

CalServer.cc

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include <memory>using namespace ns_tcpserver;
using namespace ns_protocol;Response calculatorHelp(const Request &req)
{// "1+1"???Response resp;int x = req._x;int y = req._y;switch (req._op){case '+':resp._result = x + y;break;case '-':resp._result = x - y;break;case '*':resp._result = x * y;break;case '/':if (y == 0)resp._code = 1;elseresp._result = x / y;break;case '%':if (y == 0)resp._code = 2;elseresp._result = x % y;break;default:resp._code = 3;break;}return resp;
}void calculator(int sock)
{std::string s;for (;;){if (Recv(sock, &s) <= 0) // 输出型参数break;  // 大概率对端退出,则服务结束。一般不会读取失败recv errorstd::string package = deCode(s);if (package.empty())continue; // 不是一个完整报文,继续读取(因为TCP面向字节流!!!)// 读取到一个完整报文,且已经去了应用层报头,有效载荷在package中。如"1 + 2"Request req;req.deserialize(package);Response resp = calculatorHelp(req);std::string backStr = resp.serialize();backStr = enCode(backStr);if (!Send(sock, backStr)) // 发送失败就退出break;}
}// ./cal_server port
int main(int argc, char **argv)
{// std::cout << "test remake" << std::endl;  // successif (argc != 2){std::cout << "\nUsage: " << argv[0] << " port\n"<< std::endl;exit(1);}std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));server->bindService(calculator); // 给服务器设置服务方法,将网络服务和业务逻辑进行解耦server->start();                 // 服务器开始进行accept,连接一个client之后就提供上方bind的服务return 0;
}

CalClient.cc

#include "Protocol.hpp"
#include "Sock.hpp"
#include <memory>using namespace ns_protocol;// ./client serverIp serverPort
int main(int argc, char **argv)
{if (argc != 3){std::cout << "\nUsage: " << argv[0] << " serverIp serverPort\n"<< std::endl;exit(1);}Sock sock;int sockfd = sock.Socket();// 客户端不需要显式bind, 老生常谈了。if (sock.Connect(sockfd, argv[1], atoi(argv[2])) == -1){std::cout << "connect error" << std::endl;exit(3);}std::string backStr;  // bool quit = false;while (!quit){Request req;std::cout << "Please enter# ";std::cin >> req._x >> req._op >> req._y;std::string reqStr = req.serialize();reqStr = enCode(reqStr); // 添加应用层报头,此处添加报头(制定协议)是为了解决TCP粘包问题,因为TCP是面向字节流的。if (!Send(sockfd, reqStr))break;while (true){if (!Recv(sockfd, &backStr)){quit = true;break;}std::string package = deCode(backStr);if(package.empty())continue;    // 这次不是一个完整的应用层报文,继续读取// 读取到一个完整的应用层报文,且已经去报头,获取有效载荷成功,在package中。(这个有效载荷是server发来的,计算结果)Response resp;resp.deserialize(package);switch (resp._code){case 1:std::cout << "除零错误" << std::endl;break;case 2:std::cout << "模零错误" << std::endl;break;case 3:std::cout << "其他错误" << std::endl;break;default:std::cout << req._x << " " << req._op << " " << req._y << " = " << resp._result << std::endl;break;}break;  // 退出防止TCP粘包问题的循环。}// 进行下一次获取用户输入,进行计算。}close(sockfd);return 0;
}

分析

可以分为两个模块:网络通信模块,应用层模块(包括应用层协议,以及应用层计算器逻辑)。

网络模块中,Sock.hpp就是一个简单的对于系统调用的封装,TcpServer.hpp的设计很优雅,内部有一个std::vector<func_t> _funcs;即这个server提供的服务。对外提供一个BindServer的方法,可以指定这个服务器提供的服务。Start,为服务器开始方法,先accept获取与客户端建立好的连接,然后创建新线程给客户端提供服务,服务就是BindServer绑定的方法,类型为void(int)。

应用层协议:一个Request,一个Response。分别是客户端的请求(x,y,运算符)和服务端的响应(计算结果)。这两个类,都有序列化和反序列化的方法,便于网络传输。还有一个Encode添加报头和Decode去报头的方法,这个其实就是应用层协议的报头,大体格式为 Length\r\nxxxx\r\n。目的就是解决TCP面向字节流所引起的粘包问题。
Recv内部就是一个recv调用,将读取的网络数据添加到一个输出型参数string*指向string的结尾。因为TCP粘包问题,所以可能读取的不是一个完整报文(半个?),故,在Decode方法内部,也就是去报头时,会检测此时是否有至少一个完整应用层报文。若有,则去报头,获取有效载荷。若没有则返回一个空串。上层可以通过判断是否为空串。判断是否读到了一个完整应用层报文,若没有,则再次Recv,直到读到一个完整应用层报文为止。所以,server和client在读取网络数据并去报头时,都是在while循环内部进行的。

相关文章:

Linux/C++:基于TCP协议实现网络版本计算器(自定义应用层协议)

目录 Sock.hpp TcpServer.hpp Protocol.hpp CalServer.cc CalClient.cc 分析 因为&#xff0c;TCP面向字节流&#xff0c;所以TCP有粘包问题&#xff0c;故我们需要应用层协议来区分每一个数据包。防止读取到半个&#xff0c;一个半数据包的情况。 Sock.hpp #pragma on…...

并发之阻塞队列

阻塞队列 使用背景作用从阻塞队列中获取元素常用的三个方法往阻塞队列中存放元素的三种方式 使用背景 想要在多个线程之间传递数据&#xff0c;用一般的对象是不行的&#xff0c;比如我们常用的ArrayList和HashMap都不适合由多个线程同时操作&#xff0c;可能会造成数据丢失或…...

nodejs+vue 智能餐厅菜品厨位分配管理系统

系统功能主要介绍以下几点&#xff1a; 本智能餐厅管理系统主要包括三大功能模块&#xff0c;即用户功能模块和管理员功能模块、厨房功能模块。 &#xff08;1&#xff09;管理员模块&#xff1a;系统中的核心用户是管理员&#xff0c;管理员登录后&#xff0c;通过管理员功能来…...

MySQL NULL 值

NULL 值是遗漏的未知数据&#xff0c;默认地&#xff0c;表的列可以存放 NULL 值。 本章讲解 IS NULL 和 IS NOT NULL 操作符。 如果表中的某个列是可选的&#xff0c;那么我们可以在不向该列添加值的情况下插入新记录或更新已有的记录。这意味着该字段将以 NULL 值保存。 N…...

Python 机器人学习手册:1~5

原文&#xff1a;ILearning Robotics using Python 协议&#xff1a;CC BY-NC-SA 4.0 译者&#xff1a;飞龙 本文来自【ApacheCN 计算机视觉 译文集】&#xff0c;采用译后编辑&#xff08;MTPE&#xff09;流程来尽可能提升效率。 当别人说你没有底线的时候&#xff0c;你最好…...

OpenCV(14)-OpenCV4.0中文文档学习2(补充)

相机校准和3D重建 相机校准 标定 findChessboardCorners() 它返回角点和阈值&#xff0c;如果成功找到所有角点&#xff0c;则返回 True。这些角落将按顺序放置&#xff08;从左到右&#xff0c;从上到下&#xff09;cornerSubPix()用以寻找图案&#xff0c;找到角点后也可以…...

八、express框架解析

文章目录 前言一、express 路由简介1、定义2、基础使用 二、express 获取参数1、获取请求报文参数2、获取路由参数 三、express 响应设置1、一般响应设置2、其他响应设置 四、express 防盗链五、express 路由模块化1、模块中代码如下&#xff1a;2、主文件中代码如下&#xff1…...

SpringBoot整合接口管理工具Swagger

Swagger Swagger简介 Springboot整合swagger Swagger 常用注解 一、Swagger简介 ​ Swagger 是一系列 RESTful API 的工具&#xff0c;通过 Swagger 可以获得项目的⼀种交互式文档&#xff0c;客户端 SDK 的自动生成等功能。 ​ Swagger 的目标是为 REST APIs 定义一个标…...

50+常用工具函数之xijs更新指南(v1.2.3)

xijs 是一款开箱即用的 js 业务工具库, 聚集于解决业务中遇到的常用的js函数问题, 帮助开发者更高效的进行业务开发. 目前已聚合了50常用工具函数, 接下来就和大家一起分享一下v1.2.3 版本的更新内容. 1. 添加将树结构转换成扁平数组方法 该模块主要由 EasyRo 贡献, 添加内容如…...

【DAY42】vue学习

const routes [ { path: ‘/foo’, component: Foo }, { path: ‘/bar’, component: Bar } ]定义路由的作用是什么 const routes 定义路由的作用是将每一个 URL 请求映射到一个组件&#xff0c;其中 path 表示请求的 URL&#xff0c;component 表示对应的组件。 通过 const…...

JavaScript小记——事件

HTML 事件是发生在 HTML 元素上的事情。 当在 HTML 页面中使用 JavaScript 时&#xff0c; JavaScript 可以触发这些事件。 Html事件 HTML 事件可以是浏览器行为&#xff0c;也可以是用户行为。 以下是 HTML 事件的实例&#xff1a; HTML 页面完成加载HTML input 字段改变…...

Windows逆向安全(一)之基础知识(八)

if else嵌套 这次来研究if else嵌套在汇编中的表现形式&#xff0c;本次以获取三个数中最大的数这个函数为例子&#xff0c;分析if else的汇编形式 求三个数中的最大值 首先贴上代码&#xff1a; #include "stdafx.h"int result0; int getMax(int i,int j,int k)…...

PyCharm+PyQt5+pyinstaller打包labelImg.exe

0 开头 labelImg是一款标注软件&#xff0c;作为一个开源项目&#xff0c;它的源码可以在github上找到。官方仓库地址为&#xff1a; https://github.com/heartexlabs/labelImg 小白安装时的最新版本编译出来的界面长这样&#xff1a; 之前在小白的博客里&#xff0c;也教过…...

JavaScript里实现继承的几种方式

JavaScript 中的继承可以通过以下几种方式来实现&#xff1a; 1、原型链继承&#xff1a;通过将子类的原型对象指向父类的实例来实现继承。这种方式的优点是实现简单&#xff0c;缺点是父类的私有属性和方法子类是不能访问的。 function Parent() {this.name parent;this.ag…...

前端:运用HTML+CSS+JavaScript实现迷宫游戏

最近感到挺无聊的,于是想到了大学期间关于栈的应用知识,于是就写了这篇博客! 运用HTML+CSS+JavaScript实现迷宫游戏 1. 运行结果2. 实现思路3. 参考代码1. 运行结果 前端:做个迷宫玩玩,不会迷路吧! 2. 实现思路 如果有一个迷宫,有入口,也有出口,那么怎样找到从入口到出…...

NoSQL数据库简介

NoSQL代表“不仅是SQL”&#xff0c;指的是一种数据库管理系统&#xff0c;旨在处理大量非结构化和半结构化数据。与使用具有预定义架构的表格格式的传统SQL数据库不同&#xff0c;NoSQL数据库是无模式的&#xff0c;并且允许灵活和动态的数据结构。 NoSQL数据库是必需的&…...

面试马铭泽

为什么报考这个岗位 首先&#xff0c;我对军人从小有崇敬之情&#xff0c;梦想着穿着庄严的军装&#xff0c;更对祖国有强烈的热爱之心。我的大舅是一名现役军人&#xff0c;老舅也曾服过兵役&#xff0c;从他们的谈吐以及教育中&#xff0c;让我对部队一直充满向往之情&#…...

查看AWS S3的目录

要查看AWS S3存储桶&#xff08;Bucket&#xff09;的目录&#xff0c;您可以通过AWS管理控制台或AWS CLI&#xff08;命令行界面&#xff09;来实现。 在AWS管理控制台中查看&#xff1a; 登录AWS管理控制台。选择S3服务。在S3存储桶列表中选择要查看的存储桶。在对象列表中…...

分布式系统概念和设计-操作系统中的支持和设计

分布式系统概念和设计 操作系统支持 中间件和底层操作系统的关系&#xff0c;操作系统如何满足中间件需求。 中间件需求:访问物理资源的效率和健壮性&#xff0c;多种资源管理策略的灵活性。 任何一个操作系统的目标都是提供一个在物理层&#xff08;处理器&#xff0c;内存&a…...

【redis】bitmap、hyperloglog、GEO案例

【redis】bitmap、hyperloglog、GEO案例 文章目录 【redis】bitmap、hyperloglog、GEO案例前言一、面试题二、统计的类型聚合统计排序统计问题&#xff1a;思路 二值统计 0和1基数统计 三、hyperloglog1、名词理解UV 独立访客PV 页面浏览量DAU 日活跃用户MAU 月活跃度 2、看需求…...

第二章:集合与区间

1.集合 1.内容概述 1.了解集合的意义2.了解常见集合符号的含义3.云用常见的集合符号来表示集合之间的关系、元素与集合之间的关系2.基本概念 1.集合:把一些确定的对象看成一个整体就形成了一个集合。集合一般使用大写字母A、B、C…来表示2.元素:集合中每一个对象叫做这个集合…...

Mysql8.0版本安装

一,使用yum方式安装 1,配置mysql安装源: sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-3.noarch.rpm2,安装mysql8.0: sudo yum --enablerepo=mysql80-community inst...

开放式耳机真的比封闭式强很多吗?推荐几款主流的开放式耳机

​开放式耳机&#xff0c;顾名思义&#xff0c;就是通过骨头振动来传导声音的耳机。相比于传统耳机&#xff0c;它的声音传输更加开放&#xff0c;不会对耳膜造成压迫感&#xff0c;也不会对耳膜旁的内毛细胞造成损害。因此开放式耳机既是运动蓝牙耳机&#xff0c;又是音乐蓝牙…...

Doris(7):数据导入(Load)之Routine Load

例行导入功能为用户提供了义中自动从指定数据源进行数据导入的功能 1 适用场景 当前仅支持kafka系统进行例行导入。 2 使用限制 支持无认证的 Kafka 访问&#xff0c;以及通过 SSL 方式认证的 Kafka 集群。支持的消息格式为 csv 文本格式。每一个 message 为一行&#xff0c;…...

linux 安装php8.1 ZipArchive和libzip最新版扩展安装

1、概述 安装前咱们先看下我本地环境 [rootelk php8]# cat /etc/redhat-release Red Hat Enterprise Linux Server release 7.9 (Maipo) [rootelk php8]# [rootelk php8]# ./bin/php -v PHP 8.1.18 (cli) (built: Apr 17 2023 13:15:17) (NTS) Copyright (c) The PHP Group Z…...

大数据 | 实验一:大数据系统基本实验 | 熟悉常用的HBase操作

文章目录 &#x1f4da;HBase安装&#x1f407;安装HBase&#x1f407;伪分布式模式配置&#x1f407;测试运行HBase&#x1f407;HBase java API编程环境配置 &#x1f4da;实验目的&#x1f4da;实验平台&#x1f4da;实验内容&#x1f407;HBase Shell 编程命令实现以下指定…...

Linux command(split)

原理 在split.c中&#xff0c;首先处理传递给split命令的参数&#xff0c;包括需要拆分的文件、拆分大小/行数等选项。然后&#xff0c;通过调用open()函数打开需要拆分的文件&#xff0c;并获取文件信息。接着根据选项计算每个拆分文件的大小/行数&#xff0c;并根据需要创建输…...

开放式耳机好用吗,盘点几款口碑不错的开放式耳机

​开放式耳机作为一种全新的耳机形态&#xff0c;已经成为了当前市场上非常火爆的一款产品。由于无需入耳佩戴&#xff0c;可以很好的避免了耳膜受到损伤&#xff0c;而且也能够让我们在佩戴眼镜时也能够正常使用。加上开放式耳机的音质和舒适度都要优于其他类型的耳机&#xf…...

法规标准-ISO 16787标准解读

ISO 16787是做什么的&#xff1f; ISO 16787全称为智能运输系统-辅助泊车系统&#xff08;APS&#xff09;-性能要求和测试程序&#xff0c;其中主要描述了对APS系统的功能要求及测试规范 APS类型 根据目标停车位类型将APS系统分为两类&#xff1a; 1&#xff09;APS类型I&a…...

脑力劳动-英文单词

标题 前言必学场景词汇及用法会议简报电话出差市场调研广告与媒介电脑情境常用单词会议简报电话市场调研广告与媒介电脑前言 加油 必学场景词汇及用法 会议 1meeting [ˈmitɪŋ] n.会议hold / have / call off a meeting举办/取消会议be in a meeting在开会The meeting w…...