2.2 信息、类和服务器

  • 2.2.1 自定义消息
    • (1)定义一条自定义消息
    • (2)定义一条变长的信息
  • 2.2.2 `ROS`服务
    • (1)定义服务消息
    • (2)`ROS`服务节点
    • (3)与`ROS`服务器手动交互
    • (4)`ROS`客户端
    • (4)运行服务器和客户端
  • 2.2.3 `ROS`中使用`C++`类
    • (1)在头文件中定义类
    • (2)编写一个单独的实现文件
  • 2.2.4 ROS中创建库模块

2.2.1 自定义消息

(1)定义一条自定义消息

  在~/ros_ws/src目录下利用以下命令创建包:

cs_create_pkg example_ros_msg roscpp std_msgs

​  在~/ros_ws/src/example_ros_msg目录下创建一个msg子目录,该子目录下创建一个ExampleMessage.msg文件,其内容填入:

Header header
int32 demo_int
float64 demo_double

  编辑~/ros_ws/src/example_ros_msg目录下的package.xml,将以下两行取消注释(使得通知编译器知道需要生成新的消息头):

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

  编辑~/ros_ws/src/example_ros_msg目录下的CMakeLists.txt

cmake_minimum_required(VERSION 2.8.3)
project(example_ros_msg)find_package(catkin_simple REQUIRED)catkin_simple()# Executables
# cs_add_executable(example_ros_message_publisher src/example_ros_message_publisher.cpp)cs_install()
cs_export()

  在~/ros_ws目录下运行终端,执行以下命令进行编译(生成适合的C++文件包含相应的头文件):

catkin_make

  该文件存放在~/ros_ws/devel/include/example_ros_msg/ExampleMessage.h下。如果想要使用此新消息类型的节点源代码应依赖于程序包example_ros_msg,并且使用该消息类型时,在节点的C++源代码中应该包含以下头文件:

#include <example_ros_msg/ExampleMessage.h>

  在~/ros_ws/src/example_ros_msg目录下创建一个使用自定义消息类型的发布器程序example_ros_message_publisher.cpp

// 使用自定义消息类型的发布器:example_ros_message_publisher.cpp
#include <ros/ros.h>
#include <example_ros_msg/ExampleMessage.h>    // 导入含有自定义消息类型的头文件
#include <math.h>int main(int argc, char **argv) {ros::init(argc, argv, "example_ros_message_publisher"); // 节点名ros::NodeHandle n;    // 建立网络通信// 初始化话题example_topic,将消息数据发布到话题ros::Publisher my_publisher_object = n.advertise<example_ros_msg::ExampleMessage>("example_topic", 1);// 实例化自定义消息类型example_ros_msg::ExampleMessage  my_new_message;ros::Rate naptime(1.0);   // 填充自定义消息数据的各个字段my_new_message.header.stamp = ros::Time::now();   my_new_message.header.seq=0; my_new_message.header.frame_id = "base_frame"; my_new_message.demo_int= 1;my_new_message.demo_double=100.0;double sqrt_arg;while (ros::ok()) {my_new_message.header.seq++;      // 递增序列计数器my_new_message.header.stamp = ros::Time::now();   // 更新时间戳// 具体的数据变化:不再做注释my_new_message.demo_int*=2.0;  sqrt_arg = my_new_message.demo_double;my_new_message.demo_double = sqrt(sqrt_arg);// 发布器发布数据my_publisher_object.publish(my_new_message);naptime.sleep(); }
}

  取消CMakeList.txt文件中以下注释:

cs_add_executable(example_ros_message_publisher src/example_ros_message_publisher.cpp)

  编译后运行(此时应该保证ROS系统在运行中):

catkin_make
rosrun example_ros_msg example_ros_message_publisher

  开启另外一个终端查看运行情况:

rostopic echo example_topic

显示结果如下:

header: seq: 18stamp: secs: 1658328650nsecs: 275196150frame_id: "base_frame"
demo_int: 524288
demo_double: 1.0000087837
---
header: seq: 19stamp: secs: 1658328651nsecs: 275239555frame_id: "base_frame"
demo_int: 1048576
demo_double: 1.00000439184
---

(2)定义一条变长的信息

  在~/ros_ws/src目录下利用以下命令创建包:

cs_create_pkg custom_msgs roscpp std_msgs

  在~/ros_ws/src/custom_msgs目录下创建一个msg子目录,该子目录下创建一个VecOfDoubles.msg文件,其内容填入:

float64[] dbl_vec

  编辑~/ros_ws/src/custom_msgs目录下的package.xml,将以下两行取消注释(使得通知编译器知道需要生成新的消息头):

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

  在~/ros_ws目录下运行终端,执行以下命令进行编译(生成适合的C++文件包含相应的头文件):

catkin_make

  在~/ros_ws/src/example_ros_msg目录下(注意此处目录)创建一个使用该变长消息类型的发布器程序vector_publisher.cpp

// 使用变长消息类型的发布器程序:vector_publisher.cpp
#include <ros/ros.h>
#include <custom_msgs/VecOfDoubles.h>   // 导入新消息类型头文件int main(int argc, char **argv) {ros::init(argc, argv, "vector_publisher");    // 节点名 ros::NodeHandle n;     // 建立网络通信// 初始化发布器,将会把数据发到话题vec_topic上ros::Publisher my_publisher_object = n.advertise<custom_msgs::VecOfDoubles>("vec_topic", 1);custom_msgs::VecOfDoubles vec_msg;   // 实例化新消息类型double counter=0; ros::Rate naptime(1.0); vec_msg.dbl_vec.resize(3);   // 修改消息长度// 赋值vec_msg.dbl_vec[0]=1.414;    vec_msg.dbl_vec[1]=2.71828;    vec_msg.dbl_vec[2]=3.1416;vec_msg.dbl_vec.push_back(counter);    // 插入新数据 while(ros::ok())  {counter+=1.0;vec_msg.dbl_vec.push_back(counter);    // 插入新数据 my_publisher_object.publish(vec_msg);  // 发布数据naptime.sleep(); }
}

  为了能够在example_ros_msg中编译新节点,需要对该包中的package.xmlCMakeLists.txt进行编辑:

# package.xml文件插入以下行:
<build_depend>custom_msgs</build_depend>
<run_depend>custom_msgs</run_depend>
# CMakeLists.txt文件插入以下行:
cs_add_executable(vector_publisher src/vector_publisher.cpp)

  编译节点后运行节点:

catkin_make
rosrun example_ros_msg vector_publisher

  观察结果:

rostopic echo vec_topic

显示结果如下:

dbl_vec: [1.414, 2.71828, 3.1416, 0.0, 1.0, 2.0]
---
dbl_vec: [1.414, 2.71828, 3.1416, 0.0, 1.0, 2.0, 3.0]
---
dbl_vec: [1.414, 2.71828, 3.1416, 0.0, 1.0, 2.0, 3.0, 4.0]
---

  在~/ros_ws/src/example_ros_msg目录下(注意此处目录)创建一个使用该变长消息类型的订阅器程序vector_subscriber.cpp

// 使用该变长消息类型的订阅器程序:vector_subscriber.cpp
#include<ros/ros.h>
#include <custom_msgs/VecOfDoubles.h>
void myCallback(const custom_msgs::VecOfDoubles& message_holder)  // 回调函数
{ std::vector <double> vec_of_doubles = message_holder.dbl_vec;   // 将订阅数据存放到vector容器内int nvals = vec_of_doubles.size();   // 记录容器大小for (int i=0;i<nvals;i++) { ROS_INFO("vec[%d] = %f",i,vec_of_doubles[i]); //print out all the values }ROS_INFO("\n");
} int main(int argc, char **argv)
{ ros::init(argc,argv,"vector_subscriber");   // 节点名ros::NodeHandle n;   // 建立网络通信ros::Subscriber my_subscriber_object= n.subscribe("vec_topic",1,myCallback); ros::spin(); return 0;
}

  同样,为了编译节点,将以下语句插入CMakeLists.txt文件中:

cs_add_executable(vector_subscriber src/vector_subscriber.cpp)

  利用catkin_make编译。

  打开终端,运行ROS系统:

roscore

  打开另外一个终端,运行发布器:

rosrun example_ros_msg vector_publisher

  打开另外一个终端,运行订阅器:

rosrun example_ros_msg vector_subscriber

显示结果如下:

[ INFO] [1658384328.515807550]: vec[0] = 1.414000
[ INFO] [1658384328.515938266]: vec[1] = 2.718280
[ INFO] [1658384328.515975837]: vec[2] = 3.141600
[ INFO] [1658384328.516006054]: vec[3] = 0.000000
[ INFO] [1658384328.516034508]: vec[4] = 1.000000
[ INFO] [1658384328.516060837]: vec[5] = 2.000000
[ INFO] [1658384328.516087448]: [ INFO] [1658384329.515928234]: vec[0] = 1.414000
[ INFO] [1658384329.516032330]: vec[1] = 2.718280
[ INFO] [1658384329.516074560]: vec[2] = 3.141600
[ INFO] [1658384329.516103575]: vec[3] = 0.000000
[ INFO] [1658384329.516131998]: vec[4] = 1.000000
[ INFO] [1658384329.516158618]: vec[5] = 2.000000
[ INFO] [1658384329.516184537]: vec[6] = 3.000000
[ INFO] [1658384329.516210657]:

注:使用这种消息长度可变的数据类型时需要注意以下几点:
(1)确保变长向量不会过大,否则可能会消耗所有内存和通信带宽。
(2)与数组一样,如果访问未分配的内存时,将会导致运行错误(但是编译器并不会报错)。

2.2.2 ROS服务

  目前,前面提到的通信模式中,发布器并不清楚谁是订阅器,而订阅器也不知道是哪个发布器向该话题发布数据。这种通信方式适用于重复的消息,如传感器数值的发布。传感器发布器不需要知道哪些订阅器节点订阅话题,发布器也不会接收用户的任何请求来改变自身发布的数据。

  有些情况下,我们需要建立双向、一对一、可靠的通信。客户端向服务器发送请求,服务器向客户端发送响应。

(1)定义服务消息

  定义一个自定义服务消息来描述请求和响应的数据类型和字段名称。(通过一个.srv文件实现)

  在~/ros_ws/src目录下创建一个程序包example_ros_service

cs_create_pkg example_ros_service roscpp std_msgs

  在~/ros_ws/src/example_ros_service目录下创建一个子目录srv,该子目录下创建一个ExampleServiceMsg.srv文件,编辑文件内容,文件内容如下:

string name
---
bool on_the_list
bool good_guy
int32 age
string nickname

  该srv文件中,“—”上面的行定义了请求结构,该请求有单个组件(name)组成,数据类型是一个字符串;“—”下面的行定义了响应结构,该响应有on_the_listgood_guyagenickname四个字段组成,对应的数据类型为boolboolint32string

  与发布器和订阅器自定义消息类型一样,我们需要通知编译器通过package.xml文件生成新的消息头,因此在编译前我们需要编辑修改package.xml文件:

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

  编辑CMakeList.txt文件如下:

cmake_minimum_required(VERSION 2.8.3)
project(example_ros_service)find_package(catkin_simple REQUIRED)
catkin_simple()#cs_add_executable(example_ros_service src/example_ros_service.cpp)
#cs_add_executable(example_ros_client src/example_ros_client.cpp)cs_install()
cs_export()

  在工作空间目录下,编译:

roscd
catkin_make

  编译完成后,目录~ros_ws/devel/include/example_ros_service下生成了一个ExampleServiceMsg.h头文件。同时,该目录下还生成了两个服务头文件:ExampleServiceMsgRequest.hExampleServiceMsgResponse.h

  如果想要在一个节点中使用该服务消息类型,则需要利用以下代码包含相关头文件:

#include <example_ros_service/ExampleServiceMsg.h>

注:如果在另外一个单独的包内调用该服务消息类型,则需要注意两点:
(a)同样,在C++代码中仍需要添加#include <example_ros_service/ExampleServiceMsg.h>导入相关头文件。
(b)该程序包的package.xml文件将不再依赖message_generationmessage_runtime,因为该服务消息类型只需要生成一次,已经在example_ros_service执行;其次该程序包的package.xml文件需要新增对example_ros_service的依赖,即需要加入行<build_depend>example_ros_service</build_depend>

(2)ROS服务节点

  在目录~ros_ws/src/example_ros_service下创建一个服务器程序example_ros_service.cpp,源代码如下:

// 服务器程序:example_ros_service.cpp
#include <ros/ros.h>
#include <example_ros_service/ExampleServiceMsg.h>   // 服务消息类型文件头
#include <iostream>
#include <string>
using namespace std;bool callback(example_ros_service::ExampleServiceMsgRequest& request, example_ros_service::ExampleServiceMsgResponse& response)
{ROS_INFO("callback activated");string in_name(request.name); response.on_the_list=false;// 简单的服务器响应if (in_name.compare("Bob")==0){ROS_INFO("asked about Bob");response.age = 32;response.good_guy=false;response.on_the_list=true;response.nickname="BobTheTerrible";} if (in_name.compare("Ted")==0){ROS_INFO("asked about Ted");response.age = 21;response.good_guy=true;response.on_the_list=true;response.nickname="Ted the Benevolent";}    return true;
}int main(int argc, char **argv)
{ros::init(argc, argv, "example_ros_service");  // 节点名ros::NodeHandle n;   // 建立通信// 建立一个服务器响应,等待交互ros::ServiceServer service = n.advertiseService("lookup_by_name", callback);ROS_INFO("Ready to look up names.");ros::spin();return 0;
}

(3)与ROS服务器手动交互

  编译服务器程序example_ros_service.cpp,运行该程序:

catkin_make
rosrun example_ros_service example_ros_service

  打开另外一个终端,手动与服务器交互:

rosservice call lookup_by_name 'Ted'

显示结果如下:

on_the_list: True
good_guy: True
age: 21
nickname: "Ted the Benevolent"

(4)ROS客户端

  与上面手动与服务器交互不同,一般可以采用客户端程序与服务器交互。

  在目录~ros_ws/src/example_ros_service下创建一个服务器程序example_ros_client.cpp,源代码如下:

// 客户机程序:example_ros_client.cpp
#include <ros/ros.h>
#include <example_ros_service/ExampleServiceMsg.h>  // 服务消息类型文件头
#include <iostream>
#include <string>
using namespace std;int main(int argc, char **argv) {ros::init(argc, argv, "example_ros_client");  // 节点名ros::NodeHandle n;// 实例化客户client,希望按照example_ros_service::ExampleServiceMsg中定义的方式传达请求和响应,且期望通信对象是名为lookup_by_name的服务ros::ServiceClient client = n.serviceClient<example_ros_service::ExampleServiceMsg>("lookup_by_name");// 实例化了一个服务拥有请求和响应的对象,包含一个请求字段和一个响应字段。首先将会填充请求消息,见下面代码“srv.request.name = in_name; ”example_ros_service::ExampleServiceMsg srv;bool found_on_list = false;string in_name;while (ros::ok()) {cout<<endl;cout << "enter a name (x to quit): ";cin>>in_name;if (in_name.compare("x")==0)return 0;srv.request.name = in_name; if (client.call(srv)) {   // client.call(srv)判断是否调用成功,如果调用成功,srv.response将会是服务器的响应,该响应有定义好的四个可用字段。if (srv.response.on_the_list) {cout << srv.request.name << " is known as " << srv.response.nickname << endl;cout << "He is " << srv.response.age << " years old" << endl;if (srv.response.good_guy)cout << "He is reported to be a good guy" << endl;elsecout << "Avoid him; he is not a good guy" << endl;} else {cout << srv.request.name << " is not in my database" << endl;}} else {ROS_ERROR("Failed to call service lookup_by_name");return 1;}}return 0;
}

(4)运行服务器和客户端

  保证ROS系统运行中,在另外一个终端运行服务器:

rosrun example_ros_service example_ros_service

  打开另外一个终端运行客户端:

rosrun example_ros_service example_ros_client

显示结果如下:

enter a name (x to quit): Ted
Ted is known as Ted the Benevolent
He is 21 years old
He is reported to be a good guyenter a name (x to quit): Hny
Hny is not in my databaseenter a name (x to quit): Bob
Bob is known as BobTheTerrible
He is 32 years old
Avoid him; he is not a good guy

注:在运行前,要修改CMakeLists.txt文件并且编译。后续代码均是如此,写完代码后要保证编辑CMakeLists.txt并且编译。

2.2.3 ROS中使用C++

  C++类的用法可以使用到ROS中,以提高代码效率和代码复用。

  在目录~ros_ws/src下创建包example_ros_class

cs_create_pkg example_ros_class roscpp std_msgs

(1)在头文件中定义类

  类所有成员的原型、私有和公共数据成员、类构造函数的原型。

// 类的头文件:example_ros_class.h
#ifndef EXAMPLE_ROS_CLASS_H_
#define EXAMPLE_ROS_CLASS_H_#include <math.h>
#include <stdlib.h>
#include <string>
#include <vector>#include <ros/ros.h> // 调用使用到的消息数据类型的头文件
#include <std_msgs/Bool.h>
#include <std_msgs/Float32.h>
#include <std_srvs/Trigger.h> // 定义类ExampleRosClass,包含成员函数和数据成员
class ExampleRosClass
{public:ExampleRosClass(ros::NodeHandle* nodehandle);  // 构造函数
private:  // 私有变量后带下划线_,便于阅读代码ros::NodeHandle nh_;    // 联系主函数和构造函数// 订阅器、服务器和发布器ros::Subscriber minimal_subscriber_; ros::ServiceServer minimal_service_;ros::Publisher  minimal_publisher_;double val_from_subscriber_; double val_to_remember_; void initializeSubscribers(); void initializePublishers();void initializeServices();void subscriberCallback(const std_msgs::Float32& message_holder);bool serviceCallback(std_srvs::TriggerRequest& request, std_srvs::TriggerResponse& response);
};
#endif

(2)编写一个单独的实现文件

  包含上面定义的头文件、包含已声明成员函数的工作代码、包含在构造函数中封装必要初始化的代码。

// 类的实现:example_ros_class.cpp:
#include "example_ros_class.h"  // 导入声明好的类头文件// 类的构造函数
ExampleRosClass::ExampleRosClass(ros::NodeHandle* nodehandle):nh_(*nodehandle) //直接赋值,将传入的参数nodehandle复制到私有变量nh_中
{ROS_INFO("in class constructor of ExampleRosClass");// 初始化订阅器、发布器和服务initializeSubscribers(); initializePublishers();initializeServices();// 初始化成员变量val_to_remember_=0.0;
}void ExampleRosClass::initializeSubscribers()
{ROS_INFO("Initializing Subscribers");minimal_subscriber_ = nh_.subscribe("example_class_input_topic", 1, &ExampleRosClass::subscriberCallback,this);   // this 关键字作为当前实例
}
void ExampleRosClass::initializeServices()
{ROS_INFO("Initializing Services");minimal_service_ = nh_.advertiseService("example_minimal_service",&ExampleRosClass::serviceCallback,this);
}
void ExampleRosClass::initializePublishers()
{ROS_INFO("Initializing Publishers");minimal_publisher_ = nh_.advertise<std_msgs::Float32>("example_class_output_topic", 1, true);
}void ExampleRosClass::subscriberCallback(const std_msgs::Float32& message_holder) {val_from_subscriber_ = message_holder.data;   // 保存数据到私有变量上ROS_INFO("myCallback activated: received value %f",val_from_subscriber_);std_msgs::Float32 output_msg;val_to_remember_ += val_from_subscriber_; output_msg.data= val_to_remember_;minimal_publisher_.publish(output_msg); // 发布数据
}bool ExampleRosClass::serviceCallback(std_srvs::TriggerRequest& request, std_srvs::TriggerResponse& response) {ROS_INFO("service callback activated");response.success = true; response.message = "here is a response string";return true;
}int main(int argc, char** argv)
{ros::init(argc, argv, "exampleRosClass");   // 节点名ros::NodeHandle nh; ROS_INFO("main: instantiating an object of type ExampleRosClass");ExampleRosClass exampleRosClass(&nh);  // 通过nh联系ROS_INFO("main: going into spin; let the callbacks do all the work");ros::spin();return 0;
}

  在订阅器的回调函数中,当前的订阅数据会保留在类中的私有变量val_from_subscriber_中,而val_to_remember_则会保存订阅话题的每一次数据,这些变量在类中相当于全局变量。

  在构造函数中,执行订阅器和服务器时,在提供回调函数的同时需要提供一个指针。该关键字this(指针)告诉编译器正在引用此类的当前示例。

  在与服务相关的回调函数使用了一个已经定义好的服务消息:

bool ExampleRosClass::serviceCallback(std_srvs::TriggerRequest& request, std_srvs::TriggerResponse& response)

  该消息类型Triggerstd_srvs中定义。因此,在编译前,需要在package.xml文件中增加对std_srvs的依赖。

  保持ROS系统运行,运行example_ros_class

rosrun example_ros_class example_ros_class

运行结果如下:

[ INFO] [1658474040.360780286]: main: instantiating an object of type ExampleRosClass
[ INFO] [1658474040.360880034]: in class constructor of ExampleRosClass
[ INFO] [1658474040.360908378]: Initializing Subscribers
[ INFO] [1658474040.362983130]: Initializing Publishers
[ INFO] [1658474040.363427029]: Initializing Services
[ INFO] [1658474040.363910102]: main: going into spin; let the callbacks do all the work

  打开另外一个终端,检查服务对应话题状态:

rosservice call example_minimal_service

该终端显示结果:

success: True
message: "here is a response string"

运行example_ros_class的终端显示结果:

[ INFO] [1658474296.879997406]: service callback activated

  检查订阅器状态:

rostopic pub -r 2  example_class_input_topic std_msgs/Float32 10.0

运行example_ros_class的终端显示结果:

[ INFO] [1658474645.347071052]: myCallback activated: received value 10.000000
[ INFO] [1658474645.847343847]: myCallback activated: received value 10.000000
[ INFO] [1658474646.346482449]: myCallback activated: received value 10.000000
[ INFO] [1658474646.847432733]: myCallback activated: received value 10.000000
[ INFO] [1658474647.347156673]: myCallback activated: received value 10.000000
[ INFO] [1658474647.847809435]: myCallback activated: received value 10.000000

2.2.4 ROS中创建库模块

  源代码变长则需要我们将其分解为较小的模块。为了实现对模块的复用,可以创建一个库。

​  在目录~ros_ws/src下创建包creating_a_ros_library

cs_create_pkg creating_a_ros_library roscpp std_msgs std_srvs

  将example_ros_class.cpp文件拷贝到包creating_a_ros_library的子目录src下,并且删除main函数和修改头文件的引用:

#include <creating_a_ros_library/example_ros_class.h>  // 导入声明好的类头文件

  类原型的头文件example_ros_class.h并不是放在include目录下,而需要在include目录下新建一个与程序包(creating_a_ros_library)同名的子目录,类原型的头文件放在该目录下。

​  编辑CMakeLists.txt文件,增加以下行:

cs_add_library(example_ros_library src/example_ros_class.cpp)

  在工作空间进行编译:

roscd
catkin_make

   编译完成后,目录~ros_ws/devel/lib下出现文件libexample_ros_library.so

​  在目录~ros_ws/src/creating_a_ros_library/src下创建一个测试程序,其内容如下:

#include <creating_a_ros_library/example_ros_class.h>int main(int argc, char** argv)
{ros::init(argc, argv, "example_lib_test_main"); // 节点名ros::NodeHandle nh; ROS_INFO("main: instantiating an object of type ExampleRosClass");ExampleRosClass exampleRosClass(&nh);  ROS_INFO("main: going into spin; let the callbacks do all the work");ros::spin();return 0;
}

​  编辑CMakeLists.txt文件,插入以下行:

cs_add_executable(ros_library_test_main src/example_ros_class_test_main.cpp)
target_link_library(ros_library_test_main example_ros_library)

​  测试节点:

rosrun creating_a_ros_library ros_library_test_main

显示结果如下:

[ INFO] [1658485905.853814086]: main: instantiating an object of type ExampleRosClass
[ INFO] [1658485905.853958655]: in class constructor of ExampleRosClass
[ INFO] [1658485905.853995754]: Initializing Subscribers
[ INFO] [1658485905.856053623]: Initializing Publishers
[ INFO] [1658485905.856536291]: Initializing Services
[ INFO] [1658485905.857084721]: main: going into spin; let the callbacks do all the work

【ROS】2.2 信息、类和服务器相关推荐

  1. 电子信息类学生必看的就业形势分析(转)

    电子信息工程专业就业形势分析近几年来,IT.信息与电子类的毕业生在市场供求关系上普遍还是保持稳定的状况,社会需求量相对乐观.按照2005年毕业生就业情况统计,信息产业.IT.电子类的毕业生在各理工类中 ...

  2. 棋牌类游戏服务器架构全面总结

    一.棋牌类服务器的特点 1,棋牌类不分区不分服 一般来说,棋牌游戏都是不分区不分服的.所以棋牌类服务器要满足随着用户量的增加而扩展的需要. 2,房间模式 即在同一局游戏中就是在同一个房间中,同一个房间 ...

  3. oracle数据库中选择桌面类和服务器类的区别

    在以前的学习过程中,一直用到的都是sql server数据库,之后接触到了oracle数据库.在安装过程中,有两个选项,桌面类和服务器类,当时按照网上的安装步骤,选择了桌面类,后来公司里他们安装都是用 ...

  4. 计算机网络教学方法,信息类专业计算机网络教学方法探讨

    计算机网络是信息类专业的重要专业基础课,该课程的教学难度大.本文从提高学生的理论水平和实践能力方面做了探讨.本文引用地址:http://www.eepw.com.cn/article/199016.h ...

  5. 利用WebClient类向服务器上载文件(转孟子的)

    利用WebClient类向服务器上载文件(转孟子的) 今天看了TERRYLEE老大的一篇写上传的.里面有个WEBCLIENT我就上网查了一下,找到孟子大人的一篇文章.感觉不错,就转了过来.大家一起学习 ...

  6. fir滤波器c++程序_电气信息类专业课程之matlab系统仿真 第三章 滤波器的种类(1)...

    在讲解滤波器的种类之前,必须先讲解滤波器实现滤波的过程.怎么实现?matlab中是可以用filter函数,那总不能在单片机或者FPGA中也调用filter函数吧.可能吗? 不可能! 那怎么实现滤波过程 ...

  7. matlab中的方波信号图片_电气信息类专业课程之matlab系统仿真 第十章 DBPSK调制解调器(9)...

    继续展示程序!好东西总是慢慢的吃才有味道!当然对于本科阶段的学习而言,本章知识的难度已经不算小了. 函数化编程的概念应该刻在脑子里! 误码性能的展示!!! 工程经验:看误码率就能大概知晓程序是否有问题 ...

  8. 大数据统计分析毕业设计_基于大数据分析的电子信息类专业毕业设计成绩影响因素研究...

    基于大数据分析的电子信息类专业毕业设计成绩影响因素 研究 温芳琴 [期刊名称] <佳木斯教育学院学报> [年 ( 卷 ), 期] 2019(000)011 [摘要] 通过收集苏州科技大学天 ...

  9. java 配置信息_[Java教程]java 配置信息类 Properties 的简单使用

    [Java教程]java 配置信息类 Properties 的简单使用 0 2016-12-08 09:00:09 Properties :(配置信息类) 是一个表示持久性的集合 ,继承 Hashta ...

最新文章

  1. python详细安装教程环境配置-python环境安装详细步骤
  2. [YTU]_1985( C语言实验——保留字母)
  3. C++数字是否为质数的函数的简单有效实现(附完整源码)
  4. hdu 3367 Pseudoforest (最大生成树 最多存在一个环)
  5. JAVA正则表达式及常用类
  6. java中中文显示乱码_java中显示中文乱码解决方法
  7. 苹果cmsV10资源采集插件
  8. three.js透视投影相机PerspectiveCamera
  9. 初步设计对复杂系统的意义
  10. 【数据库学习笔记】Day03 - SQL语言基础及数据库定义功能
  11. Godot 4.0中的基于有向距离场SDF(Signed Distance Field)的实时全局光照技术
  12. 一个网课开发者复工后的一个月
  13. 线性表中的尾插法双链表的学习
  14. python中计算的平方_如何在Python中计算平方和?
  15. android应用中为按键添加声音
  16. html a标签属性 rel='nofollow'
  17. 内网渗透-代理篇(reGeorg+Proxifier代理工具)
  18. win7删除系统自带中文简体输入法中美式键盘
  19. c语言运行后电脑很卡,如何让电脑提速,电脑卡是什么原因?
  20. 从餐厅服务员到一线电商程序员(中)

热门文章

  1. 通过user-agent提取手机型号
  2. 影像科dsa为什么必须买维修保险_为什么大家对影像科了解得那么少呢?
  3. 计算机运行慢 卡是什么原因是什么原因,电脑很卡是什么原因
  4. 大势所趋话开源——中国开源现状分析
  5. CS61-仅使用正则表达式匹配罗马数字|Python一对一学员答疑贴
  6. 用ubuntu的grpb2引导Remix OS或Phoenix OS
  7. python如何取0到无穷大_python如何表示无穷大
  8. RTK差分共享猫共享后中海达不能固定解决办法
  9. ElasticSearch学习之Kibana(一)
  10. 商品表(spu)、规格表(sku)设计