How to use ROS
ROS初学教程
安装与配置
跟据 Ubuntu 版本选择对应的 ROS 版本,其中 Ubuntu18.04 应安装 melodic,以下过程以 melodic 为例
按顺序执行以下命令以安装 ROS,建议选择网络良好的环境
sudo sh -c 'echo "deb http://packages.ros.org/ros/ubuntu $(lsb_release -sc) main" > /etc/apt/sources.list.d/ros-latest.list'
sudo apt-key adv --keyserver 'hkp://keyserver.ubuntu.com:80' --recv-key C1CF6E31E6BADE8868B172B4F42ED6FBAB17C654
sudo apt update
sudo apt install ros-melodic-desktop-full
配置 shell 环境
echo "source /opt/ros/melodic/setup.bash" >> ~/.bashrc
source ~/.bashrc
若 shell 为 zsh,则
echo "source /opt/ros/melodic/setup.zsh" >> ~/.zshrc
source ~/.zshrc
安装其他依赖
sudo apt install python-rosdep python-rosinstall python-rosinstall-generator python-wstool build-essential
sudo apt install python-rosdep
sudo rosdep init
rosdep update
创建工作区
mkdir -p ~/catkin_ws/src
cd ~/catkin_ws
catkin_make
source devel/setup.zsh # 以zsh为例,下同
echo $ROS_PACKAGE_PATH
检验环境已覆盖,该指令将打印所有已添加的 ros package 路径,以分号分割
系统指令
命令
-
rospack
获取与 package 相关信息,例如
rospack find roscpp
将返回
/opt/ros/melodic/share/roscpp
-
roscd
直接切换目录到 package,例如
roscd roscpp
注意该命令只对 ROS_PACKAGE_PATH 中包含的 package 有效
roscd log
将进入储存日志文件的文件夹,注意若此时尚未运行任何 ROS 程序,则该指令报错 -
rosls
直接打印 package 中的文件名,例如
rosls roscpp_tutorials
其他
ROS 命令支持 TAB 自动补全
ROS Package
目录结构
workspace_folder/ -- WORKSPACE
src/ -- SOURCE SPACE
CMakeLists.txt -- 'Toplevel' CMake file, provided by catkin
package_1/
CMakeLists.txt -- CMakeLists.txt file for package_1
package.xml -- Package manifest for package_1
...
package_n/
CMakeLists.txt -- CMakeLists.txt file for package_n
package.xml -- Package manifest for package_n
创建包
catkin_create_pkg <package_name> [depend1] [depend2]...[dependn]
例如在上文中创建的 catkin_ws/src
中执行
catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
构建包
在 catkin_ws
目录下执行
catkin_make
将工作区添加到 ROS 环境中,执行
. ./devel/setup.zsh
查看包依赖
rospack depends1 beginner_tutorials # 直接依赖
rospack depends beginner_tutorials # 间接依赖
ROS Nodes
概念简述
- 节点:节点是使用ROS与其他节点通信的可执行文件。
- 消息:订阅或发布主题时使用的ROS数据类型。
- 主题:节点可以将消息发布到主题,也可以订阅主题以接收消息。
- 主设备:ROS的名称服务(即帮助节点相互查找)
- rosout:等效于stdout / stderr的ROS
- roscore:Master + rosout +参数服务器(参数服务器将在以后介绍)
节点实际上是ROS包中的可执行文件。ROS节点使用ROS客户端库与其他节点进行通信,节点可以发布或订阅主题,也可以提供或使用服务。
ROS客户端库允许使用不同编程语言编写的节点进行通信:
- rospy = python客户端库
- roscpp = C ++客户端库
rosnode
先执行
roscore
打开新的终端,执行
rosnode list
将返回 rosout
,说明有一个节点在运行
执行
rosnode info /rosout
将返回关于该节点的更多信息
rosrun
rosrun [package_name] [node_name]
例如
rosrun turtlesim turtlesim_node
可以打开海龟界面
加入 __name:=my_turtle
参数还可以改变节点名称,可以在 rosnode list
中查看
另外还可以执行
rosnode ping my_turtle
来查看该节点是否在运行
ROS Topic
例如,分别在三个终端中执行
roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key
可以控制海龟移动
该例程中的两个节点通过同一个主题进行通讯。一个发布按键信息,一个接受信息并控制海龟移动。
rqt_gragh
在第四个终端中执行
rqt_gragh
可以打开一个图表以展示当前的 ROS 节点状态。
将鼠标移到节点上可以看到相关节点变色。
可以看出海龟例程的主题名为 /turtle1/cmd_vel
rostopic
-
echo
rostopic echo /turtle1/cmd_vel
以获取主题中的通讯信息
与此同时,rqt_gragh 中将出现新的节点 -
list
rostopic list -v
返回当前要发布和订阅的主题的详细列表
-
type
rostopic type /turtle1/cmd_vel
查看主题的通讯类型,此处返回
geometry_msgs/Twist
另外还可以执行
rosmsg show geometry_msgs/Twist
查看详细信息
或者结合使用
rostopic type /turtle1/cmd_vel | rosmsg show
-
pub
用于将信息发布到指定主题上,执行
rostopic pub [topic] [msg_type] [args]
例如
rostopic pub -1 /turtle1/cmd_vel geometry_msgs/Twist -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, 1.8]'
其中
-1
使该信息只发布一条然后退出,--
表明此后任何参数都不是选项,以防参数前的连字符(如复号)造成歧义又例如
rostopic pub /turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'
将发送稳定的命令流
与此同时可以看到rqt_gragh
中出现新的节点 -
hz
执行
rostopic hz [topic]
查看信息发布的频率
rqt_plot
执行
rqt_plot
显示有关主题发布数据的滚动时间图
ROS Services and Parameters
服务是节点间沟通的另一种方式,它允许节点发出一个请求并接受回复
rosservice
-
list
rosservice list
打印提供的服务名
-
type
rosservice type [service]
返回服务类型
-
call
rosservice call [service] [args]
例如
rosservice call /clear
将清除海龟之前爬行所留下的轨迹
执行
rosservice type /spawn | rossrv show
查看服务所需的参数,又执行
rosservice call /spawn 2 2 0.2 ""
将在指定位置创建一只新的海龟
rosparam
-
list
rosparam list
打印参数
-
set / get
rosparam set [param_name] [value] rosparam get [param_name]
用于设置或返回指定参数值。
注:设置参数后需要执行 /clear 服务以应用参数另外也可以用
rosparam get /
获得整个服务上的参数列表
-
dump / load
rosparam dump [file_name] rosparam load [file_name] [namespace]
用于保存或加载参数文件
ROS 控制台
rqt_console 和 rqt_logger_level
rosrun rqt_console rqt_console
rosrun rqt_logger_level rqt_logger_level
可以打开两个控制窗口
此时重新打开 turtlesim_node 将可以看到一条消息
-
记录器优先级
Fatal Error Warn Info Debug
roslaunch
在 ROS 包内(下例在前文中所创建的包 beginner_tutorials)新建 launch 文件夹,编写文件 Launch.launch 形如
<launch>
<group ns="turtlesim1">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<group ns="turtlesim2">
<node pkg="turtlesim" name="sim" type="turtlesim_node"/>
</group>
<node pkg="turtlesim" name="mimic" type="mimic">
<remap from="input" to="turtlesim1/turtle1"/>
<remap from="output" to="turtlesim2/turtle1"/>
</node>
</launch>
-
对以上代码的解释
建立两个组,从而启动两个节点而不出现名称冲突
将输入输出重定向,使第二只海龟与第一只海龟协同
再执行
roslaunch beginner_tutorials Launch.launch
rostopic pub /turtlesim1/turtle1/cmd_vel geometry_msgs/Twist -r 1 -- '[2.0, 0.0, 0.0]' '[0.0, 0.0, -1.8]'
可以看到,虽然命令仅向第一只海龟发出,第二只海龟依然同步运动
同时可以查看 rqt_gragh
中的情况
ROS Editor
rosed [package_name] [filename]
默认设置与 vim 并无不同,其他拓展选项可参阅官网
ROS msg and srv
关于消息和服务的简介与区别参考前文。
创建 msg
在 ROS 包中新建 msg 文件夹,编辑文件 Msg.msg 内容形如
string first_name
string last_name
uint8 age
uint32 score
接下来修改 CMakeLists.txt 中的部分内容如下
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
...
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...
)
...
add_message_files(
FILES
Num.msg
)
...
generate_messages(
DEPENDENCIES
std_msgs
)
执行
rosmsg show Msg
验证消息是否已经成功创建
创建 src
在 ROS 包中新建 srv 文件夹,执行
roscp rospy_tutorials AddTwoInts.srv srv/AddTwoInts.srv
从 ros_tutorials 中拷贝一个服务文件到 srv 文件夹中
同时再修改 CMakeLists.txt 中的部分内容如下
find_package(catkin REQUIRED COMPONENTS
roscpp
rospy
std_msgs
message_generation
)
...
catkin_package(
...
CATKIN_DEPENDS message_runtime ...
...
)
...
add_service_files( # 此处与msg不同
FILES
AddTwoInts.srv
)
...
generate_messages(
DEPENDENCIES
std_msgs
)
执行
rossrv show AddTwoInts
验证服务是否已成功创建
注:此处因未指定包名,将返回包括复制来源的 rospy 包中同名服务在内的两个服务
编译
确保在 package.xml 文件中包含如下
<build_depend>message_generation</build_depend>
<exec_depend>message_runtime</exec_depend>
在工作区下(该例中为 catkin_ws)执行 catkin_make
,将会在 devel/include/beginner_tutorials/ 目录下创建相应的头文件(C++)
Publisher and Subscriber (C++)
Publisher
在 ROS 包内的 src 文件夹中创建 talker.cpp 源代码文件如下
#include "ros/ros.h"
#include "std_msgs/String.h"
#include <sstream>
/**
* This tutorial demonstrates simple sending of messages over the ROS system.
*/
int main(int argc, char **argv)
{
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line.
* For programmatic remappings you can use a different version of init() which takes
* remappings directly, but for most command-line programs, passing argc and argv is
* the easiest way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, argv, "talker");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
ros::NodeHandle n;
/**
* The advertise() function is how you tell ROS that you want to
* publish on a given topic name. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. After this advertise() call is made, the master
* node will notify anyone who is trying to subscribe to this topic name,
* and they will in turn negotiate a peer-to-peer connection with this
* node. advertise() returns a Publisher object which allows you to
* publish messages on that topic through a call to publish(). Once
* all copies of the returned Publisher object are destroyed, the topic
* will be automatically unadvertised.
*
* The second parameter to advertise() is the size of the message queue
* used for publishing messages. If messages are published more quickly
* than we can send them, the number here specifies how many messages to
* buffer up before throwing some away.
*/
ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
ros::Rate loop_rate(10);
/**
* A count of how many messages we have sent. This is used to create
* a unique string for each message.
*/
int count = 0;
while (ros::ok())
{
/**
* This is a message object. You stuff it with data, and then publish it.
*/
std_msgs::String msg;
std::stringstream ss;
ss << "hello world " << count;
msg.data = ss.str();
ROS_INFO("%s", msg.data.c_str());
/**
* The publish() function is how you send messages. The parameter
* is the message object. The type of this object must agree with the type
* given as a template parameter to the advertise<>() call, as was done
* in the constructor above.
*/
chatter_pub.publish(msg);
ros::spinOnce();
loop_rate.sleep();
++count;
}
return 0;
}
Subscriber
在 ROS 包内的 src 文件夹中创建 listener.cpp 源代码文件如下
#include "ros/ros.h"
#include "std_msgs/String.h"
/**
* This tutorial demonstrates simple receipt of messages over the ROS system.
*/
void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
ROS_INFO("I heard: [%s]", msg->data.c_str());
}
int main(int argc, char **argv)
{
/**
* The ros::init() function needs to see argc and argv so that it can perform
* any ROS arguments and name remapping that were provided at the command line.
* For programmatic remappings you can use a different version of init() which takes
* remappings directly, but for most command-line programs, passing argc and argv is
* the easiest way to do it. The third argument to init() is the name of the node.
*
* You must call one of the versions of ros::init() before using any other
* part of the ROS system.
*/
ros::init(argc, argv, "listener");
/**
* NodeHandle is the main access point to communications with the ROS system.
* The first NodeHandle constructed will fully initialize this node, and the last
* NodeHandle destructed will close down the node.
*/
ros::NodeHandle n;
/**
* The subscribe() call is how you tell ROS that you want to receive messages
* on a given topic. This invokes a call to the ROS
* master node, which keeps a registry of who is publishing and who
* is subscribing. Messages are passed to a callback function, here
* called chatterCallback. subscribe() returns a Subscriber object that you
* must hold on to until you want to unsubscribe. When all copies of the Subscriber
* object go out of scope, this callback will automatically be unsubscribed from
* this topic.
*
* The second parameter to the subscribe() function is the size of the message
* queue. If messages are arriving faster than they are being processed, this
* is the number of messages that will be buffered up before beginning to throw
* away the oldest ones.
*/
ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
/**
* ros::spin() will enter a loop, pumping callbacks. With this version, all
* callbacks will be called from within this thread (the main one). ros::spin()
* will exit when Ctrl-C is pressed, or the node is shutdown by the master.
*/
ros::spin();
return 0;
}
编译
在 CMakelists.txt 中加入如下内容
add_executable(talker src/talker.cpp)
target_link_libraries(talker ${catkin_LIBRARIES})
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
add_executable(listener src/listener.cpp)
target_link_libraries(listener ${catkin_LIBRARIES})
add_dependencies(listener beginner_tutorials_generate_messages_cpp)
在工作区下执行 catkin_make
,将默认在 <workspace_name>devel/lib/<package_name> 目录下生成可执行文件
注意:如果编译出错原因来已执行的 cmake 编译文件,即使其他部分修改正确也无法刷新,此时可以在任意 CMakeList.txt 中添加无关空行使 catkin_make 重新创建编译文件
运行
先执行 roscore
,然后打开新的终端进入工作区
为了让 shell 能找到 ROS 包,记得执行 source devel/setup.zsh
先后执行
rosrun beginner_tutorials talker
rosrun beginner_tutorials listener
Service and Client (C++)
Service
在 ROS 包内的 src 文件夹中创建 add_two_ints_server.cpp 源代码文件如下
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
bool add(beginner_tutorials::AddTwoInts::Request &req,
beginner_tutorials::AddTwoInts::Response &res)
{
res.sum = req.a + req.b;
ROS_INFO("request: x=%ld, y=%ld", (long int)req.a, (long int)req.b);
ROS_INFO("sending back response: [%ld]", (long int)res.sum);
return true;
}
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_server");
ros::NodeHandle n;
ros::ServiceServer service = n.advertiseService("add_two_ints", add);
ROS_INFO("Ready to add two ints.");
ros::spin();
return 0;
}
Client
在 ROS 包内的 src 文件夹中创建 add_two_ints_client.cpp 源代码文件如下
#include "ros/ros.h"
#include "beginner_tutorials/AddTwoInts.h"
#include <cstdlib>
int main(int argc, char **argv)
{
ros::init(argc, argv, "add_two_ints_client");
if (argc != 3)
{
ROS_INFO("usage: add_two_ints_client X Y");
return 1;
}
ros::NodeHandle n;
ros::ServiceClient client = n.serviceClient<beginner_tutorials::AddTwoInts>("add_two_ints");
beginner_tutorials::AddTwoInts srv;
srv.request.a = atoll(argv[1]);
srv.request.b = atoll(argv[2]);
if (client.call(srv))
{
ROS_INFO("Sum: %ld", (long int)srv.response.sum);
}
else
{
ROS_ERROR("Failed to call service add_two_ints");
return 1;
}
return 0;
}
编译
在 CMakeLists.txt 中加入如下内容
add_executable(add_two_ints_server src/add_two_ints_server.cpp)
target_link_libraries(add_two_ints_server ${catkin_LIBRARIES})
add_dependencies(add_two_ints_server beginner_tutorials_gencpp)
add_executable(add_two_ints_client src/add_two_ints_client.cpp)
target_link_libraries(add_two_ints_client ${catkin_LIBRARIES})
add_dependencies(add_two_ints_client beginner_tutorials_gencpp)
在工作区下执行 catkin_make
运行
先执行 roscore
,然后打开新的终端进入工作区
为了让 shell 能找到 ROS 包,记得执行 source devel/setup.zsh
先后执行
rosrun beginner_tutorials add_two_ints_server
rosrun beginner_tutorials add_two_ints_client 1 3
此时应该在两个终端分别看到请求和结果相关的信息
Data Bagfile
record
仍然以海龟为例,在三个终端中分别执行
roscore
rosrun turtlesim turtlesim_node
rosrun turtlesim turtle_teleop_key
在工作区下新建 bagfiles 文件夹,在文件夹内执行
rosbag record -a
持续一段时间后杀死记录进程,执行
rosbag info <your_bagfile>
打印数据包信息,或执行
rosbag play <your_bagfile>
打印所有数据信息
也可以记录数据集的子集
rosbag record -O subset /turtle1/cmd_vel /turtle1/pose
子集名称可以从数据包信息中找到
read
在工作区下新建 bin 文件夹,新建脚本文件
#!/usr/bin/python2
"""
This file is part of eRCaGuy_dotfiles: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles
Author: Gabriel Staples
INSTALLATION INSTRUCTIONS:
0. Install dependencies:
sudo apt install python-rosbag
1. Create a symlink in ~/bin to this script so you can run it from anywhere:
cd /path/to/here
mkdir -p ~/bin
ln -si "${PWD}/ros_readbagfile.py" ~/bin/gs_ros_readbagfile
2. Now you can use the `gs_ros_readbagfile` command directly anywhere you like.
References:
1. http://wiki.ros.org/rosbag/Cookbook
1. https://pypi.org/project/pyrosbag/
1. Python2 `rosbag` Code API documentation: https://docs.ros.org/api/rosbag/html/python/
- `read_messages()` API documentation:
https://docs.ros.org/api/rosbag/html/python/rosbag.bag.Bag-class.html#read_messages
1. http://wiki.ros.org/rospy/Overview/Time
1. https://www.geeksforgeeks.org/python-exit-commands-quit-exit-sys-exit-and-os-_exit/
Install Dependencies:
Source: https://zoomadmin.com/HowToInstall/UbuntuPackage/python-rosbag
sudo apt install python-rosbag
Note: as far as I can tell, rosbag runs only in Python2, not Python3 :(
Pros of this script: really easy to use; requires only 1 terminal, and NO `roscore` running.
Cons: this Python implementation runs ~13x slower than "OPTION 1", as described in
"eRCaGuy_dotfiles/git & Linux cmds, help, tips & tricks - Gabriel.txt", so it might take up to
1 to 2+ minutes to process an entire bag file instead of only like 5 to 10 seconds.
TODO:
1. convert this entire Python script to a C++ program using the [C++ rosbag
API](http://wiki.ros.org/rosbag/Code%20API#cpp_api), to hopefully achieve a speed-up of ~13x
or so.
"""
import rosbag
import os
import sys
# `topics=None` means to read ALL topics; see:
# https://docs.ros.org/api/rosbag/html/python/rosbag.bag.Bag-class.html#read_messages
READ_ALL_TOPICS = None
def printHelp():
cmd_name = os.path.basename(sys.argv[0])
help = """
Usage: {} <mybagfile.bag> [topic1] [topic2] [topic3] [...topicN]
Reads and prints all messages published on the specified topics from the specified ROS bag file. If
no topics are specified, it will print ALL messages published on ALL topics found in the bag file.
For large bag files, on the order of 10 to 20 GB or so, expect the script to take on the order of
1 to 4 minutes or so assuming you have a fast Solid-State Drive (SSD).
TODO: converting this script from Python to C++ could potentially speed this up by an estimated
factor of 13x or so, decreasing the processing time from a couple minutes to perhaps a dozen
seconds.
Examples:
1. Print all messages published to the "/speed", "/vel", and "/dist" topics and stored in the
"mybagfile.bag" ROS bag file to the screen:
{} mybagfile.bag /speed /vel /dist
2. Same as above, except also stores the printed output into an output file, "output.txt" as well:
{} mybagfile.bag /speed /vel /dist | tee output.txt
3. [MY FAVORITE] Same as above, except also times how long the whole process takes:
time {} mybagfile.bag /speed /vel /dist | tee output.txt
""".format(cmd_name, cmd_name, cmd_name, cmd_name)
print(help)
class Args:
"""
Argument variables for the `main()` func
"""
bag_file_in = ""
topics_to_read = READ_ALL_TOPICS
def printArgs(args):
print("args.bag_file_in = {}".format(args.bag_file_in))
print("args.topics_to_read = {}".format(args.topics_to_read))
def parseArgs():
args = Args()
if len(sys.argv) == 1:
print("ERROR: no input args; 1 minimum required.")
printHelp()
sys.exit()
if len(sys.argv) >= 2:
if sys.argv[1] == "-h" or sys.argv[1] == "--help":
printHelp()
sys.exit()
args.bag_file_in = sys.argv[1]
if len(sys.argv) >= 3:
args.topics_to_read = sys.argv[2:]
print("Scanning ROS bag file \"{}\"".format(args.bag_file_in))
print("for the following topics:")
if not args.topics_to_read:
# list is empty
print(" ALL TOPICS in the bag file")
else:
for topic in args.topics_to_read:
print(" {}".format(topic))
print("=======================================")
return args
def printMsgsInBagFile(args):
no_msgs_found = True
# Establish counters to keep track of the message number being received under each topic name
msg_counters = {} # empty dict
total_count = 0
bag_in = rosbag.Bag(args.bag_file_in)
for topic, msg, t in bag_in.read_messages(args.topics_to_read):
total_count += 1
no_msgs_found = False
# Keep track of individual message counters for each message type
if msg_counters.has_key(topic) == False:
msg_counters[topic] = 1;
else:
msg_counters[topic] += 1;
# Print topic name and message receipt info
print("topic: " + topic)
print("msg #: %u" % msg_counters[topic])
# the comma at the end prevents the newline char being printed at the end in Python2; see:
# https://www.geeksforgeeks.org/print-without-newline-python/
print("timestamp (sec):"),
print("%.9f" % t.to_sec())
print("- - -")
# Print the message
print(msg)
print("=======================================")
if no_msgs_found:
print("NO MESSAGES FOUND IN THESE TOPICS")
print("Total messages found = {}.".format(total_count))
##### TODO: PRINT MESSAGE COUNT FOR EACH MSG TYPE TOO! ie: just print all key/value pairs in the
# `msg_counters` dict. What order to print them in? Probably just alphabetical.
print("DONE.")
# If this file is called directly, as opposed to imported, run this:
if __name__ == '__main__':
args = parseArgs()
# printArgs(args) # for debugging
printMsgsInBagFile(args)
通过执行
ros_readbagfile <mybagfile.bag> [topic1] [topic2] [topic3] [...]
可以读取信息,例如
./ros_readbagfile.py ../bagfiles/<your_bagfile> /turtle1/cmd_vel | tee topics.yaml
Debug
roswtf
将打印错误信息以供参考
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/150992.html