ROS通信机制之话题(Topics)的发布与订阅以及自定义消息的实现

我们知道在ROS中,由很多互不相干的节点组成了一个复杂的系统,单个的节点看起来是没起什么作用,但是节点之间进行了通信之后,相互之间能够交互信息和数据的时候,就变得很有意思了。
节点之间进行通信的一个常用方法就是使用话题(topic),话题表示的是一个定义了类型消息流,比方说,摄像机产生的数据可能就会被发送到一个叫做image的话题上,类型是Image的图片类型。

1、话题概述

话题是一种通过发布(publish)订阅(subscribe)进行通信的机制,这在分布式系统中是一种很常见的数据交换方式,节点在发送数据到话题上之前,需要先声明(advertise)话题名和消息类型,然后与roscore建立一个连接,roscore将共享它的订阅者与分享者列表,接着就可以发布数据到这个话题上了。对于想要从话题上接收消息的节点来说,需要通过向roscore发出请求来订阅这个话题,订阅之后,该话题上所有的消息都会被转发到这个请求的节点上,这样发布者跟订阅者就建立起了直接的连接。
它们之间的连接在 Ubuntu18.04版本安装ROS及出现错误的处理方法 这篇文章里面有更详细地讲解,有兴趣的可以去了解下。
需要注意的是,同一个话题上的所有消息必须是同一类型的。另外话题的名字最好是能够体现发送的是什么消息,比方说,在PR2机器人上面 /wide_stereo/right/image_color 的这个话题,可以直观的了解到,发送的右边相机的彩色图像数据

2、话题声明

2.1、准备工作

在声明话题之前,需要先有一个工作区,对于初次接触的朋友,更多初始化工作区与包的详细操作,可以查阅:ROS新建工作区(workspace)与包(package)编译的实践(C++示例)

我们创建一个test

catkin_create_pkg test rospy

接着在src目录里面新建一个topic_publisher.py文件,代码如下:

import rospy
from std_msgs.msg import Int32 # ROS标准消息包

rospy.init_node('topic_publisher') # 初始化节点
pub = rospy.Publisher('counter',Int32) # 发布前先声明话题名称与消息类型
rate = rospy.Rate(2) # 速率(Hz)
count = 0
while not rospy.is_shutdown():
    pub.publish(count)
    count += 1
    rate.sleep()

代码很好理解,初始化节点和声明话题,然后就是在rospy没有关闭前,每秒钟发布2次数据,其中rate.sleep()就是休息指定频率的间隔时间。
由于我们需要运行这个文件,所以需要能够执行的权限

chmod u+x topic_publisher.py


查看下文件,可以看到有了可执行权限(-rwxrw-r--):

ls -l topic_publisher.py

我们从from std_msgs.msg import Int32可以知道,有了一个标准消息包std_msgs,所以我们需要告诉ROS的构建系统,在package.xml文件里面添加依赖项(dependency):

cd ~/catkin_ws/src/test
gpedit package.xml
<depend package="std_msgs" />

添加了依赖项之后,来到工作区根目录进行编译: 

cd ~/catkin_ws
catkin_make

这样就将test包以及话题发布的Python文件构建好了。

2.2、发布话题

前面有介绍这个rostopic话题命令的使用,在使用命令前,先启动节点管理器roscore,直接输入roscore命令回车即可
重新开一个终端,运行这个节点

rosrun test topic_publisher.py

出现错误:

import-im6.q16: not authorized `rospy' @ error/constitute.c/WriteImage/1037.
from: can't read /var/mail/std_msgs.msg

/home/yahboom/catkin_ws/src/test/src/topic_publisher.py: line 4: syntax error near unexpected token `'topic_publisher''
/home/yahboom/catkin_ws/src/test/src/topic_publisher.py: line 4: `rospy.init_node('topic_publisher')'

看起来上面Python的代码有误,是没有被授权的意思,实质是没有指定解释器,这里需要告知ROS系统,这是一个Python文件

cd ~/catkin_ws/src/test/src
gedit topic_publisher.py

在代码最上面添加:#!/usr/bin/env python
然后再次运行命令即可

rosrun test topic_publisher.py

我们来看下rostopic在测试和维护当中会经常使用到的命令的用法。

2.2.1、rostopic list

再开一个终端,输入命令:rostopic list,可以看到多出了一个counter话题

/counter
/rosout
/rosout_agg

查看话题上面发布的消息:rostopic echo counter -n 5

data: 527
---
data: 528
---
data: 529
---
data: 530
---
data: 531

如果不加-n 5就是一直打印下去,直到按下Ctrl+C终止

2.2.2、rostopic hz

检验它是否按照我们期望的速率来发布消息: rostopic hz counter

subscribed to [/counter]
average rate: 2.003
    min: 0.499s max: 0.499s std dev: 0.00000s window: 2
average rate: 1.997
    min: 0.499s max: 0.502s std dev: 0.00098s window: 4
average rate: 2.000
    min: 0.498s max: 0.502s std dev: 0.00140s window: 6
average rate: 2.000
    min: 0.498s max: 0.502s std dev: 0.00118s window: 8

2.2.3、rostopic info

查看已声明的话题:rostopic info counter

Type: std_msgs/Int32

Publishers: 
 * /topic_publisher (http://YAB:45599/)

Subscribers: None

这表明counter话题承载的类型是std_msgs/Int32,并且由topic_publisher声明,运行在YAB主机上,并且通过TCP端口45599来通信,Subscribers: None表示当前还没有订阅者。

2.2.4、rostopic find

通过类型查询所有话题:rostopic find std_msgs/Int32

/counter

类型需要给出包名(std_msgs)/消息类型(Int32)

更多rostopic命令的用法,可以查看帮助:rostopic -h

rostopic is a command-line tool for printing information about ROS Topics.

Commands:
    rostopic bw    display bandwidth used by topic 显示按话题使用的带宽
    rostopic delay    display delay of topic from timestamp in header 显示标题中时间戳的话题延迟
    rostopic echo    print messages to screen 将消息打印到屏幕
    rostopic find    find topics by type 按类型查找话题
    rostopic hz    display publishing rate of topic 显示话题的发布频率
    rostopic info    print information about active topic 打印有关活动话题的信息
    rostopic list    list active topics 列出活动话题
    rostopic pub    publish data to topic 向话题发布数据
    rostopic type    print topic or field type 打印话题或字段类型

Type rostopic <command> -h for more detailed usage, e.g. 'rostopic echo -h'

3、订阅话题

有了发布话题之后,现在来写一个订阅的话题,topic_subscriber.py代码如下:

#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32 # ROS标准消息包

# 定义个回调函数
def callback(msg):
    print(msg.data)

rospy.init_node('topic_subscriber') # 初始化节点
pub = rospy.Subscriber('counter',Int32,callback) # 多一个回调函数
rospy.spin()

代码可以看到,大同小异,关键区别在于订阅的代码使用了回调函数,因为ROS是一个事件驱动的系统,所以在ROS中将会看到大量使用回调函数。这里的订阅,只要每次有消息来时,就会调用相应的回调函数,并使用接收到的消息作为它的参数。这里使用rospy.spin()将程序的运行交给ROS,避免使用上面发布话题中的while循环的一种捷径,ROS并不是必须要接管程序的主线程。

同样的,这个文件因为要去执行它,所以添加一个可执行权限:chmod u+x topic_subscriber.py

rosrun test topic_subscriber.py
850
851
852
853
854
855
...

可以正常的接收到发布者发布的消息。然后我们使用可视化工具:rqt_graph来看下它们之间的关系,如下图:

可以看到发布者(/topic_publisher)通过话题(/counter)将消息传给订阅者(/topic_subscriber)

也可以使用命令行进行消息发布:rostopic pub counter std_msgs/Int32 9999999
这样就插播一条值为9999999的消息,传给了订阅者。

现在我们来看下话题的状态信息:rostopic info counter

Type: std_msgs/Int32

Publishers: 
 * /topic_publisher (http://YAB:34725/)
 * /rostopic_3918_1692005474115 (http://YAB:45641/)

Subscribers: 
 * /topic_subscriber (http://YAB:42075/)

可以看到这里出现两个发布者,其中一个就是命令行的,也出现了一个订阅者,恩,没有问题!

4、锁存话题

在ROS中,消息是短暂的,意味着订阅可能存在丢失消息的问题,当然上面这个例子发送频率比较高,可能没什么影响,因为马上有新的消息被发送过来,不过对于有些场景,频繁发送消息也不是一个好主意。
比方说,一个节点通过map话题发送地图(数据类型:nav_msgs/OccupancyGrid),一般来说地图在一段时间内是不会发生改变,并且只会在节点加载完地图之后发送一次,如果另外一个节点需要这个地图就永远都无法获取到了。
因为地图通常很大,我们也不想经常发送,解决办法可以将频率调低,不过这里的频率应该设置多大,也是一个技巧性的问题。
所以这里提供了一个不错的解决方案,使用锁存,也就是将话题标记为锁存的时候,订阅者在完成订阅之后将会自动获取话题上最后一条消息。代码很简单,添加一个参数即可: 

pub = rospy.Publisher('counter',Int32,latch=True)

在低版本上的参数是latched,所以在新的版本上就会报下面这样的错误: 

 Traceback (most recent call last):
  File "/home/yahboom/catkin_ws/src/test/src/topic_one.py", line 6, in <module>
    pub = rospy.Publisher('counter',Int32,latched=True)
TypeError: __init__() got an unexpected keyword argument 'latched'

需要将latched更改成latch。 

5、自定义消息类型

ROS有丰富的内置消息类型,比如上面的std_msgs包定义了一些基本的类型,这些类型数据构成的定长或变长数组在Python中经过底层的通信反序列化处理,得到元组(tuple)并且可以设置成元组或列表(list)。

5.1、基本消息类型

ROS中C++与Python的基本消息类型,以及如何序列化,表格如下:

ROS消息类型序列化结果C++类型C++类型备注
boolUnsigned 8-bit integeruint8_tbool
int8Signed 8-bit integerint8_tint
uint8Unsigned 8-bit integeruint8_tuint8_tuint8[]在Python中是string表示
int16Signed 16-bit integerint16_tint
uint16Unsigned 16-bit integeruint16_tint
int32Signed 32-bit integerint32_tint
uint32Unsigned 32-bit integeruint32_tint
int64Signed 64-bit integerint64_tlong
uint64Unsigned 64-bit integeruint64_tlong
float3232-bit IEEE floatfloatfloat
float6464-bit IEEE floatdoublefloat
stringASCII stringstd::stringstringROS不支持Unicode,请使用UTF-8
timesecs/nsecs unsigned 32-bit intsros::Timerospy.Timeduration

可以看到C++比Python有更多的原生数据类型,需要注意的是:C++节点和Python节点之间交换数据的时候,需要注意一些取值范围的问题,比如说,C++中的Uint8表示无符号整数,当Python中的小于0或大于255的数值传过来时,就解释成一个8位无符号整数,这就会导致一些无法预测的bug,所以说在Python中使用范围受限的ROS类型时一定要小心。
有的时候,这些内置消息类型不够用,我们需要自己定义一些消息,这些自定义的数据类型在ROS同样是“一等公民”,跟内置消息数据类型并无差别对待。

5.2、定义新消息

在ROS中自定义消息,是用ROS包中的msg目录中的消息定义文件来说明,然后catkin_make编译成与语言有关的实现版本,这样就可以在代码中使用自定义类型了,先来看一个定义复数的一个例子,看下具体是怎么操作的:

5.2.1、msg文件

消息文件的定义很简单直观,类型 名称

cd ~/catkin_ws/src/test
mkdir msg
cd msg
gedit Complex.msg

定义实部和虚部,内容如下:

float32 real
float32 imaginary

5.2.2、修改package.xml

为了让ROS生成语言相关的消息代码,我们需要告知构建系统新消息的定义,所以需要对package.xmlCMakeLists.txt文件进行修改。
编辑package.xml文件

cd ~/catkin_ws/src/test
gedit package.xml

添加以下两个节点:

<build_depend>message_generation</build_depend>
<run_depend>message_runtime</run_depend>

后面编译的时候如果出错:

CMake Error at /opt/ros/melodic/share/catkin/cmake/catkin_package.cmake:224 (message):
  catkin_package() DEPENDS on the catkin package 'message_runtime' which must
  therefore be listed as a run dependency in the package.xml

属于版本问题,将节点名称<run_depend>修改为<exec_depend>即可

5.2.3、修改CMakeLists.txt

接着编辑CMakeLists.txt文件:gedit CMakeLists.txt

添加message_generation,让catkin能够找到message_generation包:

 find_package(catkin REQUIRED COMPONENTS rospy std_msgs message_generation)

运行时使用消息:

catkin_package(CATKIN_DEPENDS message_runtime)

告诉catkin我们想要编译它们:

add_message_files(FILES Complex.msg)

以及将下面这个注释去掉:

generate_messages(
   DEPENDENCIES
   std_msgs
 )

5.2.4、编译

定义好了之后,catkin就知道了如何编译了,接下来就回到工作区根目录进行编译

cd ~/catkin_ws
catkin_make

编译成功,没有问题。我们也可以来看下这个消息编译后的代码,当然文件所在路径,不同点取决于你的Python版本:

cd ~/catkin_ws/devel/lib/python2.7/dist-packages/test/msg
cat _Complex.py

代码如下:

# This Python file uses the following encoding: utf-8
"""autogenerated by genpy from test/Complex.msg. Do not edit."""
import codecs
import sys
python3 = True if sys.hexversion > 0x03000000 else False
import genpy
import struct


class Complex(genpy.Message):
  _md5sum = "54da470dccf15d60bd273ab751e1c0a1"
  _type = "test/Complex"
  _has_header = False  # flag to mark the presence of a Header object
  _full_text = """float32 real
float32 imaginary
"""
  __slots__ = ['real','imaginary']
  _slot_types = ['float32','float32']

  def __init__(self, *args, **kwds):
    """
    Constructor. Any message fields that are implicitly/explicitly
    set to None will be assigned a default value. The recommend
    use is keyword arguments as this is more robust to future message
    changes.  You cannot mix in-order arguments and keyword arguments.

    The available fields are:
       real,imaginary

    :param args: complete set of field values, in .msg order
    :param kwds: use keyword arguments corresponding to message field names
    to set specific fields.
    """
    if args or kwds:
      super(Complex, self).__init__(*args, **kwds)
      # message fields cannot be None, assign default values for those that are
      if self.real is None:
        self.real = 0.
      if self.imaginary is None:
        self.imaginary = 0.
    else:
      self.real = 0.
      self.imaginary = 0.

  def _get_types(self):
    """
    internal API method
    """
    return self._slot_types

  def serialize(self, buff):
    """
    serialize message into buffer
    :param buff: buffer, ``StringIO``
    """
    try:
      _x = self
      buff.write(_get_struct_2f().pack(_x.real, _x.imaginary))
    except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self)))))
    except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self)))))

  def deserialize(self, str):
    """
    unpack serialized message in str into this message instance
    :param str: byte array of serialized message, ``str``
    """
    if python3:
      codecs.lookup_error("rosmsg").msg_type = self._type
    try:
      end = 0
      _x = self
      start = end
      end += 8
      (_x.real, _x.imaginary,) = _get_struct_2f().unpack(str[start:end])
      return self
    except struct.error as e:
      raise genpy.DeserializationError(e)  # most likely buffer underfill


  def serialize_numpy(self, buff, numpy):
    """
    serialize message with numpy array types into buffer
    :param buff: buffer, ``StringIO``
    :param numpy: numpy python module
    """
    try:
      _x = self
      buff.write(_get_struct_2f().pack(_x.real, _x.imaginary))
    except struct.error as se: self._check_types(struct.error("%s: '%s' when writing '%s'" % (type(se), str(se), str(locals().get('_x', self)))))
    except TypeError as te: self._check_types(ValueError("%s: '%s' when writing '%s'" % (type(te), str(te), str(locals().get('_x', self)))))

  def deserialize_numpy(self, str, numpy):
    """
    unpack serialized message in str into this message instance using numpy for array types
    :param str: byte array of serialized message, ``str``
    :param numpy: numpy python module
    """
    if python3:
      codecs.lookup_error("rosmsg").msg_type = self._type
    try:
      end = 0
      _x = self
      start = end
      end += 8
      (_x.real, _x.imaginary,) = _get_struct_2f().unpack(str[start:end])
      return self
    except struct.error as e:
      raise genpy.DeserializationError(e)  # most likely buffer underfill

_struct_I = genpy.struct_I
def _get_struct_I():
    global _struct_I
    return _struct_I
_struct_2f = None
def _get_struct_2f():
    global _struct_2f
    if _struct_2f is None:
        _struct_2f = struct.Struct("<2f")
    return _struct_2f

可以看到生成的这个消息里面的函数主要就是做序列化和反序列化操作,序列化(封包)消息到缓冲区,然后反序列化(解包)到消息实例中。其他一些相关信息也解释下:

_md5sum = "54da470dccf15d60bd273ab751e1c0a1"
MD5校验和,这个主要是ROS用它来确保消息是正确版本。每次修改消息文件,可能都需要重新catkin_make编译,因为可能存在其他文件引用了此消息类型,比如C++就是将这个MD5校验和编译进了可执行文件,所以需要再次编译,对于Python的字节码文件(.pyc)也需要再次编译,保证它们的校验和能够匹配起来。
_type = "test/Complex"
类型定义

_full_text = """float32 real
float32 imaginary
"""
__slots__ = ['real','imaginary']
_slot_types = ['float32','float32']

这里就是实部与虚部定义的类型。

6、使用自定义消息

上面的消息被定义好了之后,接下来我们来看下如何使用它。

6.1、消息发布

cd ~/catkin_ws/src/test/src
gedit message_publisher.py
#!/usr/bin/env python
import rospy
from test.msg import Complex
from random import random

rospy.init_node('message_publisher')
pub = rospy.Publisher('complex',Complex)
rate = rospy.Rate(2)
while not rospy.is_shutdown():
    msg = Complex()
    msg.real = random()
    msg.imaginary = random()
    pub.publish(msg)
    rate.sleep()

代码跟原来差不多,没什么区别,将自定义的Complex类型跟导入其他标准类型一样导入,然后就可以创建消息类的实例了,有了实例就可以给里面的字段赋值了。

6.2、消息订阅

有了消息发布的代码之后,来写一个订阅的,跟前面的方法一样,也是使用回调函数来触发:

cd ~/catkin_ws/src/test/src
gedit message_subscriber.py
#!/usr/bin/env python
import rospy
from test.msg import Complex

def callback(msg):
    print('Real:',msg.real)
    print('Imaginary:',msg.imaginary)

rospy.init_node('message_subscriber')
sub = rospy.Subscriber('complex',Complex,callback)
rospy.spin()

最后也添加执行权限:chmod u+x message_subscriber.py

然后开三个终端分别输入以下命令:

roscore
rosrun test message_publisher.py
rosrun test message_subscriber.py

输出结果如下:

('Real:', 0.7348452806472778)
('Imaginary:', 0.24286778271198273)
('Real:', 0.8839530348777771)
('Imaginary:', 0.4728539288043976)
('Real:', 0.45228102803230286)
('Imaginary:', 0.9694715142250061)
('Real:', 0.7523669004440308)
('Imaginary:', 0.17689673602581024)

6.3、rosmsg命令

先来看下这条消息有关的命令的帮助:rosmsg -h

rosmsg is a command-line tool for displaying information about ROS Message types.

Commands:
    rosmsg show    Show message description 显示消息定义
    rosmsg info    Alias for rosmsg show 是show的别名
    rosmsg list    List all messages 列出所有消息
    rosmsg md5    Display message md5sum 显示MD5校验和
    rosmsg package    List messages in a package 列出包中所有消息
    rosmsg packages    List packages that contain messages 列出包含消息的所有包

Type rosmsg <command> -h for more detailed usage

查看这个Complex消息内容:rosmsg show Complex

[test/Complex]:
float32 real
float32 imaginary

 验证MD5校验和的值: rosmsg md5 Complex

[test/Complex]: 54da470dccf15d60bd273ab751e1c0a1

恩,没有问题,可以看到输出的内容跟代码里面的是一样的。

如果一个消息包含另一个消息,也可以递归显示出来,比如:rosmsg show PointStamped

[geometry_msgs/PointStamped]:
std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
geometry_msgs/Point point
  float64 x
  float64 y
  float64 z

包含headerpoint,都是ROS类型

列举定义了消息的所有包:rosmsg packages

 actionlib
actionlib_msgs
actionlib_tutorials
bond
control_msgs
controller_manager_msgs
diagnostic_msgs
dynamic_reconfigure
gazebo_msgs
geometry_msgs
map_msgs
nav_msgs
pcl_msgs
roscpp
rosgraph_msgs
rospy_tutorials
sensor_msgs
shape_msgs
smach_msgs
std_msgs
stereo_msgs
test
tf
tf2_msgs
theora_image_transport
trajectory_msgs
turtle_actionlib
turtlesim
visualization_msgs

列举test包定义的消息:rosmsg package test 

test/Complex

列举传感器包定义的消息:rosmsg package sensor_msgs 

sensor_msgs/BatteryState
sensor_msgs/CameraInfo
sensor_msgs/ChannelFloat32
sensor_msgs/CompressedImage
sensor_msgs/FluidPressure
sensor_msgs/Illuminance
sensor_msgs/Image
sensor_msgs/Imu
sensor_msgs/JointState
sensor_msgs/Joy
sensor_msgs/JoyFeedback
sensor_msgs/JoyFeedbackArray
sensor_msgs/LaserEcho
sensor_msgs/LaserScan
sensor_msgs/MagneticField
sensor_msgs/MultiDOFJointState
sensor_msgs/MultiEchoLaserScan
sensor_msgs/NavSatFix
sensor_msgs/NavSatStatus
sensor_msgs/PointCloud
sensor_msgs/PointCloud2
sensor_msgs/PointField
sensor_msgs/Range
sensor_msgs/RegionOfInterest
sensor_msgs/RelativeHumidity
sensor_msgs/Temperature
sensor_msgs/TimeReference

查看其中相机的消息定义:rosmsg show sensor_msgs/CameraInfo

 std_msgs/Header header
  uint32 seq
  time stamp
  string frame_id
uint32 height
uint32 width
string distortion_model
float64[] D
float64[9] K
float64[9] R
float64[12] P
uint32 binning_x
uint32 binning_y
sensor_msgs/RegionOfInterest roi
  uint32 x_offset
  uint32 y_offset
  uint32 height
  uint32 width
  bool do_rectify

7、发布者与订阅者协同工作

前面是单个的发布者和订阅者,但是节点也可以同时是一个发布者和订阅者,或者拥有多个订阅和发布。实际上在ROS中,节点最常做的事情就是传递消息,并在消息上进行计算,一起来看一个例子

cd ~/catkin_ws/src/test/src
#发布者:
gedit doubler_pub.py
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32
import random

rospy.init_node('doubler_pub')
pub = rospy.Publisher('number',Int32)
rate = rospy.Rate(2)
while not rospy.is_shutdown():
    num = random.randint(1,9)
    pub.publish(num)
    rate.sleep()

添加执行权限:chmod u+x doubler_pub.py

运行:rosrun test doubler_pub.py

这里就订阅上面的发布,然后做加倍处理之后再发布,也就是同时拥有了订阅和发布的功能

cd ~/catkin_ws/src/test/src
#加倍操作:
gedit doubler.py
#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32

rospy.init_node('doubler')
def callback(msg):
    print(msg.data)
    doubled = msg.data * 2
    pub.publish(doubled)

sub = rospy.Subscriber('number',Int32,callback)
pub = rospy.Publisher('doubled',Int32)
rospy.spin()

添加执行权限:chmod u+x doubler.py

运行:rosrun test doubler.py

订阅者:gedit doubler_sub.py

#!/usr/bin/env python
import rospy
from std_msgs.msg import Int32

def callback(msg):
    print(msg.data)

rospy.init_node('doubler_sub')
sub = rospy.Subscriber('doubled',Int32,callback)
rospy.spin()

添加执行权限:chmod u+x doubler_sub.py
运行:rosrun test doubler_sub.py

如下图:

这里的加倍操作,就是在接收到新数据之后才发布,也就是说这个节点就是起到一个信息计算处理的一个作用,然后将加倍的数据发布出去。
最后做一个订阅者查看下结果,输出比对发现确实是将数据做了加倍处理。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

SpringMVC 反射型跨站点脚本攻击

解决方案&#xff1a; 服务端校验&#xff0c;添加拦截器 配置web,xml <filter><filter-name>xssFilter </filter-name><filter-class>com.fh.filter.XssFilter </filter-class></filter> XssFilter package com.fh.filter;import com…

.NET敏捷开发框架-RDIFramework.NET V6.0发布

1、RDIFramework.NET 敏捷开发框架介绍 RDIFramework.NET敏捷开发框架&#xff0c;是我司重磅推出的基于最新.NET6与.NET Framework的快速信息化系统开发、整合框架&#xff0c;为企业快速构建跨平台、企业级的应用提供了强大支持。 开发人员不需要开发系统的基础功能和公共模…

MCU和MPU你分得清楚吗?

最近有不少同学表示在学习嵌入式的过程中分不清MCU和MPU&#xff0c;这两个确实是长得很像、容易混淆的概念&#xff0c;这里我为大家仔细分辨一下。 从概念上讲&#xff0c;MCU指的是微控制器&#xff0c;优势在于“控制”&#xff0c;MPU指的是微处理器&#xff0c;优势在于“…

微服务基础知识

文章目录 微服务基础知识一、系统架构的演变1、单体应用架构2、垂直应用架构3、分布式SOA架构&#xff08;1&#xff09;什么是SOA&#xff08;2&#xff09;SOA架构 4、微服务架构5、SOA和微服务的关系&#xff08;1&#xff09;SOA&#xff08;2&#xff09;微服务架构 二、分…

idea使用tomcat

1. 建立javaweb项目 2. /WEB-INF/web.xml项目配置文件 如果javaweb项目 先建立项目&#xff0c;然后在项目上添加框架支持&#xff0c;选择javaee 3. 项目结构 4.执行测试&#xff1a;

运维高级学习--Docker(二)

1、使用mysql:5.6和 owncloud 镜像&#xff0c;构建一个个人网盘。 #拉取mysql5.6和owncloud镜像 [rootlocalhost ~]# docker pull mysql:5.6 [rootlocalhost ~]# docker pull owncloud [rootlocalhost ~]# docker images REPOSITORY TAG IMAGE ID CREATED …

【芯片设计封装与测试】芯片测试目的、方法、分类及案例

目录 1.芯片测试概述&#xff08;目的、方法&#xff09; 1.1.测试在芯片产业价值链上的位置 2.测试如何体现在设计的过程中 2.1.半导体测试定义与基本工作机制 2.2.半导体测试环节分类及对应设备 2.3.设计验证 3.测试的各种类型 3.1.抽样测试和生产全测 3.2.测试相关…

【android12-linux-5.1】【ST芯片】HAL移植后配置文件生成报错

根据ST官方源码移植HAL源码后&#xff0c;执行readme指示中的生成配置文件指令时报错ST_HAL_ANDROID_VERSION未定义之类&#xff0c;应该是编译环境参数问题。makefile文件中是自动识别配置的&#xff0c;参数不祥就会报错&#xff0c;这里最快的解决方案是查询确定自己android…

课程项目设计--spring security--认证管理功能--宿舍管理系统--springboot后端

写在前面&#xff1a; 还要实习&#xff0c;每次时间好少呀&#xff0c;进度会比较慢一点 本文主要实现是用户管理相关功能。 前文项目建立 文章目录 验证码功能验证码配置验证码生成工具类添加依赖功能测试编写controller接口启动项目 security配置拦截器配置验证码拦截器 …

Leetcode每日一题:1267. 统计参与通信的服务器

原题 这里有一幅服务器分布图&#xff0c;服务器的位置标识在 m * n 的整数矩阵网格 grid 中&#xff0c;1 表示单元格上有服务器&#xff0c;0 表示没有。 如果两台服务器位于同一行或者同一列&#xff0c;我们就认为它们之间可以进行通信。 请你统计并返回能够与至少一台其…

musl libc ldso 动态加载研究笔记:动态库的加载次序与初始化次序

前言 musl ldso 是按照什么次序加载动态链接的应用程序的共享库的&#xff1f;如果共享库之间有依赖&#xff0c; musl ldso 如何处理先初始化哪个 共享库&#xff1f; musl ldso 的代码可以在 musl 官方代码&#xff1a; ldso\dlstart.c 与 ldso\dynlink.c&#xff0c;其中动…

ETLCloud轻量级数据中台解决方案

引言 随着信息时代的到来&#xff0c;数据已经成为企业的重要资源&#xff0c;如何高效地管理、分析和应用数据变得尤为关键。然而&#xff0c;许多企业在构建数据中台时面临着高昂的成本、复杂的架构和漫长的实施周期等问题。为了解决这些挑战&#xff0c;我们推出了ETLCloud…

使用高斯滤波器进行表面开放轮廓过滤研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【官方中文文档】Mybatis-Spring #目录

目录 此页面用于在GitHub上呈现索引。 NOTE: 由于链接目标是在使用maven-site-plugin转换为html的假设下指定的&#xff0c;因此在GitHub上的呈现中有一个锚点已损坏。 简介入门SqlSessionFactoryBean事务使用 SqlSession注入映射器Spring Boot使用 MyBatis APISpring Batch示…

Linux虚拟机安装(Ubuntu 20)

最近这段时间使用VMWare安装了一下Ubuntu版本的Linux虚拟机&#xff0c;在这里记录一下安装时参考的文章以及需要注意的细节 参考链接&#xff1a; VMware虚拟机下安装Ubuntu20.04&#xff08;保姆级教程&#xff09; 一、安装VMWare 下载链接&#xff1a;VMware Workstatio…

【TI毫米波雷达笔记】SOC外设初始化配置及驱动(以IWR6843AOP为例)

【TI毫米波雷达笔记】SOC外设初始化配置及驱动&#xff08;以IWR6843AOP为例&#xff09; 最基本的工程建立好以后 需要给SOC进行初始化配置 SOC_Cfg socCfg; //SOC配置结构体Task_Params taskParams; //任务参数SOC_Handle socHandle;ESM_init(0U); …

git介绍+集成到IDEA中+使用gitee

目录 git介绍 本地工作流程 IDEA集git 添加到暂存区 添加到本地仓库 gitee使用 添加到远程仓库 git介绍 git是一个开源的分布式版本控制工具&#xff0c;效率高。可以记录历史代码&#xff0c;多人代码共享 知识小点&#xff1a; 集中式版本控制&#xff1a;使用中央存…

SpringBoot案例-文件上传

目录 简介 文件上传前端页面三要素 服务端接收文件 小结 本地储存 实现 代码优化 小结 阿里云OSS 阿里云 阿里云OSS 使用第三方服务--通用思路 准备工作 参照官方SDK代码&#xff0c;编写入门程序 集成使用 阿里云OSS-使用步骤 阿里云OSS使用步骤 参照SDK编写入…

vue2 vue中的常用指令

一、为什么要学习Vue 1.前端必备技能 2.岗位多&#xff0c;绝大互联网公司都在使用Vue 3.提高开发效率 4.高薪必备技能&#xff08;Vue2Vue3&#xff09; 二、什么是Vue 概念&#xff1a;Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套 **构建用户界面 ** 的 渐进式 …

mysql(八)事务隔离级别及加锁流程详解

目录 MySQL 锁简介什么是锁锁的作用锁的种类共享排他锁共享锁排它锁 粒度锁全局锁表级锁页级锁行级锁种类 意向锁间隙临键记录锁记录锁间隙锁 加锁的流程锁的内存结构加锁的基本流程根据主键加锁根据二级索引加锁根据非索引字段查询加锁加锁规律 锁信息查看查看锁的sql语句 数据…