boost asio同步编程(附源码api)

news2024/11/17 8:49:41

首先注明,这里我写的都是关于tcp的通信。

通信大致流程

创建端点

创建tcp端点的api是boost::asio::ip::tcp::endpoint;

当然创建udp端点的api则是boost::asio::ip::udp::endpoint;

是一个表示 TCP/UDP 端点的类,在 Boost.Asio 库中用于网络编程。它通常用于指定要连接的远程主机和端口,或者表示在本地主机上监听的端点

其中endpoint的构造函数使用得较多是参数为网络类型的ip地址端口号。源码构造如下。

这里的网络类型的ip地址需要解释以下。平常我们说得ip地址都是点分十进制的,而我们在网络通信中实际需要的ip地址则是需要进行网络序列化的ip地址。因此我们需要将点分十进制的ip地址转化成为网络序列的ip地址。这里需要使用的转化函数api则是boost::asio::ip::address::from_string。

对于这个转化ip地址序列的函数源码如下

其中第二个参数是一个boost::system::error_code类型的变量。

介绍完这些api之后,下边提供一个完整的创建客户端和服务端endpoint的demo。

void client_EndPoint() {
	string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	boost::system::error_code ec;
	boost::asio::ip::address ip_address
		= boost::asio::ip::address::from_string(raw_ip_address, ec);
	if (ec.value()) {
		cerr << "transfered failed" << endl;
		cout << "Error code: " << ec.value() <<
			"ec Message is" << ec.message() << endl;
		return ec.value();
	}
	//生成端点。该端点由ip地址和端口号生成
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
}
/*boost::asio::ip::address_v4::any()是一个Boost.Asio库中的函数,用于创建一个表示
IPv4任意地址(通配地址)的对象。这个函数返回一个特殊的address_v4对象,它代表了网络
中的任何IPv4地址。这个函数通常用于初始化socket绑定到一个通配地址,这表示socket应该
接受进入的连接,不论这些连接来自于哪个IP地址。*/
int server_EndPoint() {
	//服务器端口号
	unsigned short port_num = 3333;
	//服务器地址
	boost::asio::ip::address ip_address = boost::asio::ip::address_v4::any();
	//生成一个tcp用来通信的端点
	boost::asio::ip::tcp::endpoint ep(ip_address, port_num);
	return 0;
}

创建套接字

asio空间中,我们首先不可避免的就是类io_service或io_context。

注意,io_context这个类是用来替代io_service的,所以建议以后都直接使用io_context即可

这个类非常重要,它相当于我们程序与系统之间I/O操作的中介,我们所有的接受或发送数据操作,都是通过将需求提交给这个类,然后这个类再交给计算机来执行的。

客户端创建socket

int create_tcp_socket() {
	//创建上下文
	boost::asio::io_context ioc;
	//创建ipv4的协议
	boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4();
	//创建socket
	boost::asio::ip::tcp::socket sock(ioc);
	//打开socket
	boost::system::error_code ec;
	sock.open(protocol, ec);

	return 0;
}

这里构造的socket并没有被开启,即只有在被open,在被连接或者接收才能发送数据。构造源码如下

这里的sock.open()函数是 Boost.Asio 库中的一个函数,用于打开一个 TCP 套接字。源码如下,

服务端创建acceptor

下边是一个服务端的创建欢迎套接字demo

int create_acceptor_socket() {
	/*旧写法*/
	创建上下文
	//boost::asio::io_context ioc;
	生成acceptor服务
	//boost::asio::ip::tcp::acceptor acceptor(ioc);
	创建ipv4的协议
	//boost::asio::ip::tcp protocol = boost::asio::ip::tcp::v4();
	打开socket
	//boost::system::error_code ec;
	//acceptor.open(protocol, ec);
	//if (ec.value()) {
	//	cerr << "open failed" << "Error Code : " << ec.value()
	//		<< "Error Message : " << ec.message() << endl;
	//}

	/*新写法*/
	//创建上下文
	boost::asio::io_context ioc;
	//生成acceptor服务,(指定地址和端口,实现一个默认绑定)
	boost::asio::ip::tcp::acceptor acceptor(ioc, boost::asio::ip
		::tcp::endpoint(boost::asio::ip::tcp::v4(), 3333));
	return 0;
}

第一种构造acceptor的源码如下。这样构造出来的acceptor并没有启动acceptor去监听新的连接。在acceptor能够监听新的连接之前,必须先调用open函数,启动acceptor去监听新的连接。

第二种构造acceptor的方式,会在构造acceptor的时候自动去启动acceptor在指定的端点监听新的连接

服务器绑定套接字

int bind_acceptor_socket() {
	//创建服务
	boost::asio::io_context ioc;
	unsigned short port_num = 3333;
	//创建端点
	boost::asio::ip::tcp::endpoint ep(boost::asio::
		ip::address_v4::any(), port_num);
	//生成accpetor,并手动绑定
	boost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
	//绑定端点,返回错误码
	boost::system::error_code ec;
	acceptor.bind(ep, ec);
	if (ec.value()) {
		cerr << "open failed" << "Error Code : " << ec.value()
			<< "Error Message : " << ec.message() << endl;
	}
	return 0;
}

boost::asio::ip::address_v4::any() 是一个Boost.Asio库中的函数,用于创建一个表示IPv4任意地址(通配地址)的address_v4对象。这个函数返回一个特殊的address_v4对象,它可以绑定到一个网络接口上,表示接受任意IPv4地址的数据包

这里构造endpoint的方法也会自动去开启open。源码如下

这段代码需要好好理解下。这里的构造的endpoint端点返回的是一个通配地址,即一个通配的绑定端口的端点,它并没有绑定任何一台主机。这里构造的acceptor仅仅是一个只能接收ipv4协议的acceptor的默认绑定(本地ip,但是并不清楚在哪个端口)。所以需要使用acceptor的bind方法绑定指定的端点(拿到里边的端口)。

这里可能会有疑惑,普通套接字和欢迎套接字的bind()和open()的先后顺序。是先bind再open。这里构造endpoint的方法也会自动去开启open。但是它还并没有去bind,所以并不会及时去开启open,而是等到bind之后再去开启,但是这里并不需要我们去操作,而是程序自动完成的。

客户端连接

客户端的连接比较简单,就先提供代码,再做解读。

int client_connect()
{
	//服务器地址
	string raw_ip_address = "192.168.1.124";
	//服务器端口
	unsigned short port_num = 3333;
	try{
		//创建上下文服务
		boost::asio::io_context ioc;
		//创建端点
		boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address
			::from_string(raw_ip_address), port_num);
		//创建socket
		boost::asio::ip::tcp::socket sock(ioc, ep.protocol());
		//
		sock.connect(ep);

	}catch(boost::system::system_error& e){
			cerr << "open failed" << "Error Code : " << e.code()
					<< "Error Message : " << e.what() << endl;
	}
	return 0;
}

首先构造出服务端的端点endpoint。再构造一个客户端的socket。这里使用构造socket的方法会将构造出来的socket自动开启,即只有被连接或者接收就能发送或接收数据。源码如下

使用客户端的socket的connect方法去连接服务器的端点即可。connet方法的源码如下

下边写一个服务端获取一个新的连接的demo

int accept_new_connection()
{
	const int BACKLOG_SIZE = 30;
	unsigned short port_num = 3333;
	//创建端点
	boost::asio::ip::tcp::endpoint ep(boost::asio::ip::address_v4::any(), port_num);
	boost::asio::io_context ioc;
	try {
		//生成一个acceptor
		boost::asio::ip::tcp::acceptor acceptor(ioc, ep.protocol());
		//绑定端口
		acceptor.bind(ep);
		//监听
		acceptor.listen(BACKLOG_SIZE);
		//再创建一个socket
		boost::asio::ip::tcp::socket sock(ioc);
		//将acceptor接收新的连接,交给新创建的sock处理
		acceptor.accept(sock);
	}
	catch (boost::system::system_error& e) {
		cerr << "open failed" << "Error Code : " << e.code()
			<< "Error Message : " << e.what() << endl;
	}

	return 0;
}

boost::asio中的buffer

任何网络库都有提供buffer的数据结构,所谓buffer就是接收和发送数据时缓存数据的结构。
boost::asio提供了asio::mutable_buffer 和 asio::const_buffer这两个结构,他们是一段连续的空间,首字节存储了后续数据的长度。
asio::mutable_buffer用于写服务,asio::const_buffer用于读服务。但是这两个结构都没有被asio的api直接使用。
对于api的buffer参数,asio提出了MutableBufferSequence和ConstBufferSequence概念,他们是由多个asio::mutable_buffer和asio::const_buffer组成的。也就是说boost::asio为了节省空间,将一部分连续的空间组合起来,作为参数交给api使用。
我们可以理解为MutableBufferSequence的数据结构为std::vector<asio::mutable_buffer>
结构如下

buffer的重要性

boost中的send接口要求参数为ConstBufferSequence类型。所以我们需要常用ConstBufferSequence类型的参数,但是怎么去构造这样类型的参数呢?下边是send函数源码

使用boost::asio中的const_buffer去构造ConstBufferSequence

void use_const_buffer()
{
	string buf("hello boost");
	//构造一个asio中的一个const_buffer对象
	boost::asio::const_buffer asio_buff(buf.c_str(), buf.size());
	vector<boost::asio::const_buffer> bufSequece;
	bufSequece.emplace_back(asio_buff);
}

可以看出这样写太麻烦了。asio中提供了buffer函数转化成为send函数参数需要的类型。

void use_buffer_str() {
    asio::const_buffers_1 output_buf = asio::buffer("hello world");
}

buffer函数该函数接收多种形式的字节流,该函数返回asio::mutable_buffers_1 或者asio::const_buffers_1结构的对象。
如果传递给buffer()的参数是一个只读类型,则函数返回asio::const_buffers_1 类型对象。
如果传递给buffer()的参数是一个可写类型,则返回asio::mutable_buffers_1 类型对象。asio::const_buffers_1和asio::mutable_buffers_1是asio::mutable_buffer和asio::const_buffer的适配器,提供了符合MutableBufferSequence和ConstBufferSequence概念的接口,所以他们可以作为boost::asio的api函数的参数使用。可以参考下边这个demo。

void use_buffer_array()
{
	const size_t BUF_SIZE_BYTES = 20;
	std::unique_ptr<char[]> buf(new char[BUF_SIZE_BYTES]);
	//static_cast <type-id>( expression )。
	//static_cast把expression转换为type-id类型
	boost::asio::mutable_buffers_1 input_buf =
		boost::asio::buffer(static_cast<void*>(buf.get()), BUF_SIZE_BYTES);

}

下边是buffer函数的源码。第一个参数是字符串的首地址,第二个参数是字符串的长度。

同步读写api

同步写

write_some

write_some可以每次向指定的空间写入固定的字节数。这个函数将会阻塞的写入,直到一个或者更多的数据字节成功写入,另或是出现错误。这是源码给出的解释,也能这样的理解,就是write_some可以每次向指定的空间写入固定的字节数,如果写缓冲区满了,就只写一部分,返回写入的字节数

demo如下

void write_some_to_socket(boost::asio::ip::tcp::socket& sock)
{
	string buf = "hello boost";
	size_t total_bytes_written = 0;
	//循环发送
	
	while (total_bytes_written != buf.size()) {
        //注意这里写的位置要加上偏移量,更符合实际
		total_bytes_written += sock.write_some(boost::asio::buffer
		(buf.c_str() + total_bytes_written,
			buf.size() - total_bytes_written));
	}

}

write_some函数源码如下

send

这个函数将会阻塞的写入,直到一个或者更多的数据字节成功写入,另或是出现错误。这也是源码给出的解释。但是send函数会一次性将buffer中的内容发送给对端,如果有部分字节因为发送缓冲区满无法发送,则阻塞等待,直到发送缓冲区可用,则继续发送完成,这样理解会更好

demo如下

int send_to_socket()
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint
			ep(boost::asio::ip::address::from_string(raw_ip_address),
				port_num);
		boost::asio::io_service ios;
		// 建立套接字
		boost::asio::ip::tcp::socket sock(ios, ep.protocol());
		sock.connect(ep);
		std::string buf = "Hello World!";
		/*下边的send_length只有三种情况。
		1、大于0,即等于buff的长度
		2、等于0,表示对端关闭
		3、小于0,表示socket出现系统级错误*/
		int send_length = sock.send(boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			cout << "send failed" << endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

send函数源码如下

write

可以一次性将所有数据发送给对端,如果发送缓冲区满了则阻塞,直到发送缓冲区可用,将数据发送完成

demo如下

int write_to_socket()
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint
			ep(boost::asio::ip::address::from_string(raw_ip_address),
				port_num);
		boost::asio::io_service ios;
		// 建立套接字
		boost::asio::ip::tcp::socket sock(ios, ep.protocol());
		sock.connect(ep);
		std::string buf = "Hello World!";
		/*下边的send_length只有三种情况。
		1、大于0,即等于buff的长度
		2、等于0,表示对端关闭
		3、小于0,表示socket出现系统级错误*/
		int send_length = boost::asio::write(sock, boost::asio::buffer(buf.c_str(), buf.length()));
		if (send_length <= 0) {
			cout << "send failed" << endl;
			return 0;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

write函数源码如下

同步读

read_some

同步读和同步写类似,提供了读取指定字节数的接口read_some

demo如下

string read_some_fromSocket(boost::asio::ip::tcp::socket& sock)
{
	const unsigned char MESSAGE_SIZE = 7;
	char buf[MESSAGE_SIZE];
	size_t total_bytes_read = 0;
	while (total_bytes_read != MESSAGE_SIZE) {
		total_bytes_read += sock.read_some(boost::asio::buffer(
			buf + total_bytes_read,
			MESSAGE_SIZE - total_bytes_read));
	}
	return string(buf, total_bytes_read);
}

read_some函数源码如下

receive

可以一次性同步接收对方发送的数据

demo如下

int receive_fromSocket(boost::asio::ip::tcp::socket& sock)
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint
			ep(boost::asio::ip::address::from_string(raw_ip_address),
				port_num);
		boost::asio::io_service ios;
		boost::asio::ip::tcp::socket sock(ios, ep.protocol());
		sock.connect(ep);
		const unsigned char BUFF_SIZE = 7;
		char buffer_receive[BUFF_SIZE];
		int receive_length = sock.receive(boost::asio::buffer(buffer_receive, BUFF_SIZE));
		if (receive_length <= 0) {
			cout << "receive failed" << endl;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}

	return 0;
}

receive函数源码如下

read

可以一次性同步读取对方发送的数据

demo如下

int read_fromSocket(boost::asio::ip::tcp::socket& sock)
{
	std::string raw_ip_address = "127.0.0.1";
	unsigned short port_num = 3333;
	try {
		boost::asio::ip::tcp::endpoint
			ep(boost::asio::ip::address::from_string(raw_ip_address),
				port_num);
		boost::asio::io_service ios;
		boost::asio::ip::tcp::socket sock(ios, ep.protocol());
		sock.connect(ep);
		const unsigned char BUFF_SIZE = 7;
		char buffer_receive[BUFF_SIZE];
		int receive_length = boost::asio::read(sock, boost::asio::buffer(buffer_receive, BUFF_SIZE));
		if (receive_length <= 0) {
			cout << "receive failed" << endl;
		}
	}
	catch (boost::system::system_error& e) {
		std::cout << "Error occured! Error code = " << e.code()
			<< ". Message: " << e.what();
		return e.code().value();
	}
	return 0;
}

read函数源码如下

下边提供一个同步读写的echo服务器和客户端,源码如下

syncSer:codes-C++: C++学习 - Gitee.com

syncCli:codes-C++: C++学习 - Gitee.com

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

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

相关文章

工业机器人应用实践之玻璃涂胶(篇三)

工业机器人 接上篇文章&#xff0c;浅谈一下实践应用&#xff0c;具体以玻璃涂胶为例&#xff1a; 了解工业机器人在玻璃涂胶领域的应用 认识工具坐标系的标定方法 掌握计时指令的应用 掌握人机交互指令的应用 掌握等待类指令用法&#xff08;WaitDI、WaitUnitl 等&#xff0…

正点原子Linux学习笔记(九)在 LCD 上显示字符

在 LCD 上显示字符 23.1 原始方式&#xff1a;取模显示字符23.2 freetype 简介23.3 freetype 移植下载 FreeType 源码交叉编译 FreeType 源码安装目录下的文件移植到开发板 23.4 freetype 库的使用初始化 FreeType 库加载 face 对象设置字体大小加载字形图像 23.5 示例代码 前面…

再谈毕业论文设计投机取巧之IVR自动语音服务系统设计(信息与通信工程A+其实不难)

目录 举个IVR例子格局打开&#xff0c;万物皆能IVR IVR系统其实可盐可甜。还能可圈可点。 戎马一生&#xff0c;归来依然IVR。 举个IVR例子 以下是IVR系统的一个例子。 当您拨打电话进入IVR系统。 首先检验是否为工作时间。 如是&#xff0c;您将被送入ivr-lang阶段&#xff0…

【Delphi 爬虫库 6】使用正则表达式提取猫眼电影排行榜top100

正则表达式库的简单介绍 正则表达式易于使用&#xff0c;功能强大&#xff0c;可用于复杂的搜索和替换以及基于模板的文本检查。这对于输入形式的用户输入验证特别有用-验证电子邮件地址等。您还可以从网页或文档中提取电话号码&#xff0c;邮政编码等&#xff0c;在日志文件中…

机器学习算法应用——CART决策树

CART决策树&#xff08;4-2&#xff09; CART&#xff08;Classification and Regression Trees&#xff09;决策树是一种常用的机器学习算法&#xff0c;它既可以用于分类问题&#xff0c;也可以用于回归问题。CART决策树的主要原理是通过递归地将数据集划分为两个子集来构建决…

在 Kubernetes 上运行 Apache Spark 进行大规模数据处理的实践

在刚刚结束的 Kubernetes Community Day 上海站&#xff0c;亚马逊云科技在云原生分论坛分享的“在 Kunernets 上运行 Apache Spark 进行大规模数据处理实践”引起了现场参与者的关注。开发者告诉我们&#xff0c;为了充分利用 Kubernetes 的高可用设计、弹性&#xff0c;在越来…

Leedcode题目:移除链表元素

题目&#xff1a; 这个题目就是要我们将我们的链表中的值是val的节点删除。 我们题目提供的接口是 传入了指向一个链表的第一个节点的指针&#xff0c;和我们要删除的元素的值val&#xff0c;不只要删除第一个&#xff0c; 思路 我们这里可以创建一个新的链表&#xff0c;…

配置Docker对象与管理守护进程

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 本章节的快速目录导航&#xff1a; 一、配置Docker对象 1.1、Docker对象的标记 1.2、格式化命令和日志的输出 二、示例&#xff1a; 2.1、管理…

HTML【常用的标签】、CSS【选择器】

day45 HTML 继day44&#xff0c;w3cschool 常用的标签 k) 表格 表格由 table 标签来定义。每个表格均有若干行&#xff08;由 tr 标签定义&#xff09;&#xff0c;每行被分割为若干单元格&#xff08;由 标签定义&#xff09;。字母 td指表格数据&#xff08;table data&…

Qt | QSpinBox 类 QDoubleSpinBox 类(微调框)

01、QSpinBox 类 1、QSpinBox类是 QAbstractSpinBox 类的直接子类和具体实现, 2、QSpinBox 类被设计用于处理整数和离散值集合,对于浮点值使用 QDoubleSpinBox 类实现。 3、QSpinBox 默认只支持整数值,但可通过其内部的成员函数进行扩展,以支持使用不同的 字符串。 02…

ppt通过修改幻灯片母版修改页脚

修改幻灯片母版 幻灯片母版就可以了&#xff0c;就可以修改页脚

c++多态机制

多态 在 C 中&#xff0c;多态&#xff08;Polymorphism&#xff09;是一种面向对象编程的重要概念&#xff0c;它允许不同类的对象对同一消息做出不同的响应。具体来说&#xff0c;多态性允许基类的指针或引用在运行时指向派生类的对象&#xff0c;并且根据对象的实际类型来调…

汇昌联信科技:做拼多多网店要押金吗?

做拼多多网店要押金吗?”这个问题&#xff0c;其实与拼多多的平台规则有关。在开店之前&#xff0c;商家需要详细了解平台的各项规定和费用构成&#xff0c;这样才能做好充足的准备。 一、明确回答问题 做拼多多网店&#xff0c;不需要支付押金。拼多多的入驻门槛相对较低&…

烽火三十六技丨网络资产安全治理平台新版本发布,一文看懂四大核心优势

云计算、移动互联网、物联网等技术飞速发展&#xff0c;网络环境愈发开放互联&#xff0c;原有的资产管理方式已难以适应当下的变化。同时&#xff0c;网络资产需求的突发性和人为疏忽&#xff0c;也时常导致资产数量不明、类型模糊、安全漏洞检查不全面等问题。因此&#xff0…

增加表空间的数据文件

Oracle从入门到总裁:​​​​​​https://blog.csdn.net/weixin_67859959/article/details/135209645 表空间创建完成后&#xff0c;后期还可以为表空间增加数据文件&#xff0c;以扩大数据的存储空间。增加表空间数据文件的基本语法结构如下所示。 ALTER TABLESPACE 表空间名…

SpringCloud:服务拆分和远程调用

程序员老茶 &#x1f648;作者简介&#xff1a;练习时长两年半的Java up主 &#x1f649;个人主页&#xff1a;程序员老茶 &#x1f64a; P   S : 点赞是免费的&#xff0c;却可以让写博客的作者开心好久好久&#x1f60e; &#x1f4da;系列专栏&#xff1a;Java全栈&#…

详解drop,delete,truncate区别

在SQL中&#xff0c;"DROP"、"DELETE"和"TRUNCATE"是用于删除数据的不同命令&#xff0c;它们之间有一些重要的区别&#xff1a; DROP&#xff1a; DROP用于删除数据库对象&#xff0c;例如删除表、视图、索引、触发器等。使用DROP删除的对象将…

如何撰写一份优秀的产品需求文档?看完真的不用加班了!

产品需求文档&#xff08;PRD&#xff09;是产品经理最主要的可视化交付物&#xff0c;刚入门的产品会疑惑产品需求文档应该如何撰写&#xff0c;产品老鸟们也会头痛产品文档如何高效更新和维护。本文会详细介绍2024年最新的极简产品文档撰写和管理方案&#xff0c;建议阅读并收…

数据库被攻击后出现1044 - access denied for user ‘root‘@‘% ‘ to database table

MySQL数据库被攻击后&#xff0c;数据库全部被删除&#xff0c;并且加一个一个勒索的数据&#xff0c;向我索要btc&#xff0c; 出现这个问题就是我的数据库密码太简单了&#xff0c;弱密码&#xff0c;被破解了&#xff0c;并且把我权限也给修改了 导致我操作数据库时&#…

【大数据·hadoop】在hdfs上运行shell基本常用命令

一、准备工作 1.1格式化并启动Hadoop服务 参见Hadoop在ubuntu虚拟机上的伪分布式部署|保姆级教程的4.7节 二、HDFS常用命令 接着&#xff0c;就愉快地在刚刚的命令行里敲命令啦 1.显示hdfs目录结构 hadoop fs -ls -R /hadoop fs: 这是Hadoop文件系统命令行的一部分&#x…