【XR806开发板试用】+移植rosserial到XR806

1 XR806简介

板子来源于极术社区的试用,XR806的在线网址
c.png
其主要参数:

主控XR806AF2L
DDRSIP 288KB SRAM
存储SIP 160KB Code ROM. SIP 16Mbit Flash.
天线板载WiFi/BT双天线,可共存
按键reboot按键 1,功能按键 1
红色电源指示灯 1,蓝色可调节LED 1
供电Type-C 5V
引脚插针引脚 *9
调试方式Type-C(已板载串口转USB芯片)
晶振外接40MHz晶振

2 rosserial简介

官网

rosserial是用于非ROS设备与ROS设备进行通信的一种协议。它为非ROS设备的应用程序提供了ROS节点和服务的发布/订阅功能,使在非ROS环境中运行的应用能够通过串口或网络能够轻松地与ROS应用进行数据交互。

rosserial分为客户端服务器两部分。rosserial客户端运行在运行在没有安装ROS的环境的应用中,通过串口或网络与运行在ROS环境中的rosserial服务器连接,并通过服务器节点在ROS中发布/订阅话题。

3 移植目标

通过rosserial使XR806能通过串口和TCP两种方式和ROS进行通信。

4 移植前准备

4.1 源码获取

[官方源码]( https://github.com/ros-drivers/rosserial.git
该仓库中的代码需要编译才能获取源码,为了直接获取源码,使用以下仓库的源码做为基础。

使用源码

该代码时属于RT-thread软件包,有较高的可信度。
clone下来的代码放在/ohosdemo/rosserial中,文件结构:
tree -L 1

tree -L 1

.
├── BUILD.gn
├── inc
├── port
└── src

  • BUILD.gn :配置文件
  • inc :rosserial源文件
  • port:移植文件(为了和XR806适配的代码放在该文件下)

4.2 XR806 C++支持

rosserial为C++代码,需要XR806支持C++编译。

  1. 修改/device/xradio/xr806/liteos_m/config.gni文件,添加以下内容:
board_cxx_flags = []

board_cxx_flags += SDK_cflags
board_cxx_flags += [
    "-includelog/log.h",
    "-DVIRTUAL_HCI",
    "-DCONFIG_ARM",
    #"-DNULL=((void*)0)",
    #"-std=c++17",
    "-lstdc++",
    "-fno-rtti",
    "-fno-exceptions"
   
]

大部分和board_cflags的配置一样,添加的编译项 "-lstdc++",-fno-rtti,-fno-exception是为了解决以下错误:

 undefined reference to `vtable for __cxxabiv1::__si_class_type_info'

该错误的原因是C++在链接时会有相关库链接不上
2. 在/ohosdemo/rosserial/BUILD.gn中添加以下代码:

cflags_cc = board_cxx_flags

表示支持c++编译

5 ROSserial移植核心

根据官网的移植介绍,只需要填写完以下模板即可:

class Hardware
{

  Hardware();

  // any initialization code necessary to use the serial port
  void init(); 

  // read a byte from the serial port. -1 = failure
  int read()

  // write data to the connection to ROS
  void write(uint8_t* data, int length);

  // returns milliseconds since start of program
  unsigned long time();

};
  • init():提供初始化函数,初始化串口或者TCP网络
  • read():读取一个字节
  • write(uint8_t* data, int length):写字符
  • time():提供时间基准

6 串口通信

和串口相关的代码放在rosserial/port/UartHaedware.h
关键代码:

6.1 串口初始化:init()函数

void init()
    {
    //    HAL_Status status = HAL_ERROR;
      UART_InitParam param;
      param.baudRate = this->baudRate;

      param.dataBits = UART_DATA_BITS_8;
	    param.stopBits = UART_STOP_BITS_1;
	    param.parity = UART_PARITY_NONE;
	    param.isAutoHwFlowCtrl = 0;

       HAL_UART_Init(UARTID, &param);
    }

6.2 串口读取一个字节:read()函数

int read()
  {
      uint8_t rx_data;
      int32_t len=0;
      len = HAL_UART_Receive_IT(UARTID,&rx_data,1,1000);
      if(len>0)
      {
          return rx_data;
      }
      else return -1;
  }

6.3 串口写字节:write(uint8_t* data, int length)

// write data to the connection to ROS
  void write(uint8_t* data, int length)
  {
      HAL_UART_Transmit_IT(UARTID, data, length);
  }

6.4 时间基准:time()函数

  // returns milliseconds since start of program
  unsigned long time()
  {
    unsigned long temp = (unsigned long)OS_GetTime() * 1000;
      return temp;
  }

6.5 代码风格统一

为了保持ROS代码的编写风格一致,添加``rosserial/port/ros.h`
关键代码:


#define ROS_USE_TCP 0
#define ROS_USE_UART 1

namespace ros
{
  #if ROS_USE_TCP == 1
    typedef NodeHandle_<TCPHardware> NodeHandle;
  #endif

  #if ROS_USE_UART == 1
    typedef NodeHandle_<Hardware> NodeHandle;
  #endif
}
#endif

可以通过宏定义选择使用串口还是TCP

7 TCP通信

7.1 wifi连接

wifi连接使用官方在ohosdemo/wlan_demo中的代码。具体使用void wifi_device_event_test()函数和wifi连接成功后的回调函数:void Connected_deal(int state, WifiLinkedInfo *info)

大致思路是在wifi连接线程中增加一个信号量,初始化信号量为0,rosserial线程会一直等待信号量有效后才会触发rosserial的相关函数,利用信号量作为两个线程间的同步,保证wifi顺利连接成功后才会进行rosserial的TCP通信。另外每次wifi重新连接后都会保证XR806会自动连接ROS。

信号量的三个关键函数:

  • OS_Status OS_SemaphoreCreate(OS_Semaphore_t *sem, uint32_t initCount, uint32_t maxCount)初始化信号量,定义计数的初始值和最大值

  • OS_Status OS_SemaphoreWait(OS_Semaphore_t *sem, OS_Time_t waitMS) 等待信号量有效,及信号量的计数不为0;等待时间可以使无限长,该参数为OS_WAIT_FOREVER

  • OS_Status OS_SemaphoreRelease(OS_Semaphore_t *sem)释放信号量,信号量计数加1;

    主要代码:

    
    #define WIFI_DEVICE_CONNECT_AP_SSID "myyy"
    #define WIFI_DEVICE_CONNECT_AP_PSK "123456789"
    
    WifiEvent sta_event;
    
    void Connected_deal(int state, WifiLinkedInfo *info)
    {
    	if (state == WIFI_STATE_AVALIABLE) {
    		//释放信号量,计数加1
        	OS_SemaphoreRelease(&ros_sem);
    		// OS_Sleep(5);
            printf("\r\n======== Callback: connected========\r\n");
            
    	} 
    	else if (state == WIFI_STATE_NOT_AVALIABLE) {
    		printf("======== Callback: disconnected\n");
    	}
    }
    
    void wifi_device_event_test()
    {
    	const char ssid_want_connect[] = WIFI_DEVICE_CONNECT_AP_SSID;
    	const char psk[] = WIFI_DEVICE_CONNECT_AP_PSK;
    
        //创建信号量:初始值为0,最大值为2
        if(OS_SemaphoreCreate(&ros_sem, 0, 2) != OS_OK)
    	{
    		printf("\r\n sem creat fail!\r\n");
    		return ;
    	}
    
        sta_event.OnWifiConnectionChanged = Connected_deal;
        if (WIFI_SUCCESS != RegisterWifiEvent(&sta_event)) {
    		printf("Error: RegisterWifiEvent fail\n");
    		return;
    	}
    
    	printf("\n=========== Connect Test Start ===========\n");
    
    	if (WIFI_SUCCESS != EnableWifi()) {
    		printf("Error: EnableWifi fail.\n");
    		return;
    	}
    	printf("EnableWifi Success.\n");
    
    	if (WIFI_STA_ACTIVE == IsWifiActive())
    		printf("Wifi is active.\n");
    
    	OS_Sleep(1);
    	/*
    	...................
    	...................
    	...................
    	省略的代码 参见官方demo
    	
    	*/
    	
    	if (WIFI_SUCCESS != GetDeviceMacAddress(get_mac_res)) {
    		printf("Error: GetDeviceMacAddress Fail\n");
    		return;
    	}
    	printf("GetDeviceMacAddress Success.\n");
    	for (int j = 0; j < WIFI_MAC_LEN - 1; j++) {
    		printf("%02X:", get_mac_res[j]);
    	}
    	printf("%02X\n", get_mac_res[WIFI_MAC_LEN - 1]);
    
    }
    

7.2 TCP客户端初始化

为了不和串口的Hardware类重名,类名为TCPHardware

 void init()
     {
         sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
 
         // address info!
         struct sockaddr_in server_addr;
         memset(&server_addr, 0, sizeof(server_addr));
 
         server_addr.sin_family = AF_INET;
         server_addr.sin_port = htons(serverPort_);
         inet_pton(AF_INET, server_, &server_addr.sin_addr);
 
         // connect!
         if (connect(sock_fd, (sockaddr *)&server_addr, sizeof(server_addr)) < 0)
         {
             printf("connect tcp_server failed! \r\n");
             return;
         }
         printf("connect tcp_server successfuly! \r\n");
     }

7.3 TCP读取字节

int read()
      {
          char ch[2];
          int bytes_received = recv(sock_fd, ch, 1, 0);
          if (bytes_received > 0)
          {
              return ch[0];
          }
          else
          {
              return -1;
          }
      }
  

7.4 TCP写字节

  void write(const uint8_t *data, int length)
      {
          send(sock_fd, data, length, 0);
      }

时间基准和6.4一样

8 测试

8.1 XR806 端

文件目录:

.
├── BUILD.gn
├── led_demo
│   ├── BUILD.gn
│   └── src
├── rosserial
│   ├── BUILD.gn
│   ├── inc
│   ├── port
│   └── src
└── wlan_demo
    ├── BUILD.gn
    ├── main.c
    ├── test_case.c
    └── test_case.h

其中:

  • led_demo:led灯,指示作用
  • rosserial:rosserialb包
  • wlan_demo:负载wifi连接
8.1.1 配置文件BUILD.gn
# 必须,config中定义了头文件路径和关键宏定义
import("//device/xradio/xr806/liteos_m/config.gni")

# 必须,所有应用工程必须是app_打头
static_library("app_rosserial")
{
    configs = []
    sources = [
        "src/ros_helloworld.cpp",

        "inc/duration.cpp",
        "inc/time.cpp",
    ]
     #必须,board_cflags是在config.gni中定义的关键宏定义
     cflags = board_cflags
     # c++
     cflags_cc = board_cxx_flags

    #必须,board_include_dirs是在config.gni中定义的文件路径
    include_dirs = board_include_dirs

    # 根据实际情况添加头文件路径
   include_dirs += [
      "//kernel/liteos_m/kernel/arch/include",
      "./../wlan_demo/",
      "inc",
      "port" ,
      "//base/iot_hardware/peripheral/interfaces/kits",
      "//foundation/communication/wifi_lite/interfaces/wifiservice",
   ]
}
8.1.2 测试程序
  • 编写一个发布话题:XR806_to_ROS

发布的内容为“hello world!”,时间间隔为1s

  • 编写一个接收话题:ROS_to_XR860

接收的内容通过串口显示出来

#include <ros.h>
#include <std_msgs/String.h>

#include <stdio.h>
#include "ohos_init.h"
#include <stdlib.h>

//信号量的声明
extern OS_Semaphore_t ros_sem;

static OS_Thread_t g_main_thread;

static ros::NodeHandle  nh;
static std_msgs::String str_msg;
static ros::Publisher chatter("XR806_to_ROS", &str_msg);
static char hello_msg[25] = "hello world!";

// 回调函数
static void message_callback(const std_msgs::String& msgs)
{
    
    printf("\r\nresive:%s\r\n", msgs.data);
  
}

static ros::Subscriber<std_msgs::String> sub("ROS_to_XR860", &message_callback);


static void ROSThread(void *arg)
{   
    //等待信号量有效
     if (OS_SemaphoreWait(&ros_sem, OS_WAIT_FOREVER) == OS_OK)
     {
        printf("\r\n--------- star ROS----------------\r\n");
        nh.initNode();
        nh.advertise(chatter);
        nh.subscribe(sub);

        while (1)
        {
            if (nh.connected())
            {
                str_msg.data = hello_msg;
                chatter.publish(&str_msg); 
            }
            nh.spinOnce();
            OS_MSleep(1000);
        } 
     }   
}
void ROSMain(void)
{
    printf("\r\nROSserial Start\r\n");
    if (OS_ThreadCreate(&g_main_thread, "ROSThread", ROSThread, NULL,
			    OS_THREAD_PRIO_APP, 4 * 1024) != OS_OK) {
		printf("[ERR] Create MainThread Failed\n");
	}
}

SYS_RUN(ROSMain);
8.1.3 编译程序
 hb build -f

可能会遇到下面问题:
在这里插入图片描述

这个flash分配的分配有问题:

cd 到 device/xradio/xr806/xr_skylark/project/demo/audio_demo/image/xr806

用文件image_auto_cal.cfg中的内容覆盖image_wlan_ble.cfg中的内容。

8.2 PC ROS端

Ubuntu版本:20版(18版也可以使用)

8.2.1 创建ros空间
mkdir -p rosworkspace/src
cd  rosworkspace
catkin_make
8.2.2 编写发布节点
import rospy
from std_msgs.msg import String

if __name__ == "__main__":
    #2.初始化 ROS 节点:命名(唯一)
    rospy.init_node("talker_p")
    #3.实例化 发布者 对象
    pub = rospy.Publisher("ROS_to_XR860",String,queue_size=10)
    #4.组织被发布的数据,并编写逻辑发布数据
    msg = String()  #创建 msg 对象
    msg_front = "hello XR806    "
    count = 0  #计数器 
    # 设置循环频率
    rate = rospy.Rate(1)
    while not rospy.is_shutdown():

        #拼接字符串
        msg.data = msg_front + str(count)

        pub.publish(msg)
        rate.sleep()
        #rospy.loginfo("写出的数据:%s",msg.data)
        count += 1

修改CMakeLists.txt

catkin_install_python(PROGRAMS
  scripts/test_pub.py
  DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
8.2.3 启动节点
  1. 启动主节点:
roscore
  1. 起动test_pub节点
source ./devel/setup.bash

rosrun ros_test test_pub.py 
  1. 启用serial_node节点:

串口:

rosrun rosserial_python serial_node.py _port:=/dev/ttyUSB0 _baud:=115200

如果串口连接成功,终端显示:

INFO] [1640087588.757505]: ROS Serial Python Node
[INFO] [1640087588.762633]: Connecting to /dev/ttyUSB0 at 115200 baud
[INFO] [1640087591.079099]: Requesting topics...
[INFO] [1640087591.131236]: Note: publish buffer size is 512 bytes
[INFO] [1640087591.134777]: Setup publisher on XR806_to_ROS [std_msgs/String]
[INFO] [1640087591.142147]: Note: subscribe buffer size is 512 bytes
[INFO] [1640087591.144610]: Setup subscriber on ROS_to_XR860 [std_msgs/String]

注意查看串口的权限,如果权限不足,先开启权限:

# 查看权限
ll /dev/ttyUSB*
#开启权限
sudo chmod 777 /dev/ttyUSB*

TCP
默认节点11411

rosrun rosserial_python serial_node.py tcp

如果TCP连接成功,终端显示:

[INFO] [1640232707.758952]: ROS Serial Python Node
[INFO] [1640232707.763597]: Fork_server is: False
[INFO] [1640232707.764688]: Waiting for socket connections on port 11411
[INFO] [1640232707.765650]: Waiting for socket connection
[INFO] [1640232724.857086]: Established a socket connection from 192.168.43.49 on port 60872
[INFO] [1640232724.859268]: calling startSerialClient
[INFO] [1640232726.966374]: Requesting topics...
[INFO] [1640232727.289246]: Note: publish buffer size is 512 bytes
[INFO] [1640232727.290573]: Setup publisher on XR806_to_ROS [std_msgs/String]
[INFO] [1640232727.295084]: Note: subscribe buffer size is 512 bytes
[INFO] [1640232727.296294]: Setup subscriber on ROS_to_XR860 [std_msgs/String]

8.3 结果查询

  1. ROS上查看话题:
 rostopic list 

/ROS_to_XR860
/XR806_to_ROS
/diagnostics
/rosout
/rosout_agg

可见 /ROS_to_XR860和/XR806_to_ROS两个话题

  1. 查看/XR806_to_ROS
rostopic echo /ROS_to_XR860

data: "hello world!"
---
data: "hello world!"
---
data: "hello world!"
---
data: "hello world!"
---
data: "hello world!"
---
  1. 查看XR806接收的 /ROS_to_XR860话题消息

串口连接

-------- star ROS----------------
resive:hello XR806    1
resive:hello XR806    2
resive:hello XR806    3
resive:hello XR806    4
resive:hello XR806    5
resive:hello XR806    6
resive:hello XR806    7
resive:hello XR806    8
resive:hello XR806    9
resive:hello XR806    10
resive:hello XR806    11
resive:hello XR806    12

TCP连接

--------- star ROS----------------
connect tcp_server successfuly! 
resive:hello XR806    1
resive:hello XR806    2
resive:hello XR806    3
resive:hello XR806    4
resive:hello XR806    5
resive:hello XR806    6
resive:hello XR806    7
resive:hello XR806    8

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

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

相关文章

如何在wxPython应用程序中使用Panda3D

我们知道wxPython提供了丰富的工具和部件来构建用户界面&#xff0c;如果当我们整合wxPython和Panda3D可以创建出功能丰富且交互性强的应用程序&#xff0c;可以创建出强大而丰富的用户界面和3D场景。这样做的主要挑战在于将两个库整合到一个应用程序中&#xff0c;同时确保它们…

【.NET Core】深入理解async 和 await 理解

【.NET Core】深入理解async 和 await 理解 文章目录 【.NET Core】深入理解async 和 await 理解一、概述二、async异步执行机制理解三、async与await应用3.1 async与await简单应用3.2 带有返回值async与await应用 四、async和await中常见问题总结4.1 当方法用async标识时&…

(每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理第10章 项目进度管理(六)

博主2023年11月通过了信息系统项目管理的考试&#xff0c;考试过程中发现考试的内容全部是教材中的内容&#xff0c;非常符合我学习的思路&#xff0c;因此博主想通过该平台把自己学习过程中的经验和教材博主认为重要的知识点分享给大家&#xff0c;希望更多的人能够通过考试&a…

跨境云手机如何简化tiktok运营流程

如今&#xff0c;tiktok已经成为世界范围内都非常流行的社交媒体平台。然而在大多数情况下&#xff0c;由于网络原因&#xff0c;tiktok无法在国内使用&#xff0c;但依然有越来越多的人注册tiktok号码、建立tiktok矩阵。原因是tiktok仍然有大量的流量可供商业使用&#xff0c;…

day04-股票K线功能实现

股票K线功能实现 今日目标 1.理解股票T和T-1概念&#xff0c;实现成交量对比功能; 2.理解个股涨跌幅度统计功能; 2.1 分析业务&#xff0c;SQL落地; 2.2 完善不存在数据的区间默认回显功能; 3.理解个股分时线业务&#xff0c;并实现功能; 4.理解个股日K线业务&#xff0c;并实…

使用傅里叶实现100倍的压缩效果(附Python源码)

傅里叶变换&#xff08;Fourier Transform&#xff09;是一种将一个函数&#xff08;在时间或空间域&#xff09;转换为另一个函数&#xff08;在频率域&#xff09;的数学变换方法。它在信号处理、图像处理、通信等领域有广泛应用。 实现过程 将傅里叶系数核心的1%保留&…

从零开始手写mmo游戏从框架到爆炸(十五)— 命令行客户端改造

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 到现在&#xff0c;我们切实需要一个客户端来完整的进行英雄选择&#xff0c;选择地图&#xff0c;打怪等等功能。所以我们需要把之前极为简陋的客户端改造一下。 首先…

0成本部署github前端项目流程

0成本部署github纯前端项目流程 对业内来说应该是一个比较常规的操作&#xff0c;对于新手来说进行过一次应该就很难忘记了&#xff0c;但很多人仍然是不会的&#xff0c;认为部署项目很难&#xff0c;很专业&#xff0c;其实现在由于这些厂商的努力&#xff0c;大众&#xff…

js设计模式:装饰者模式

作用: 可以给原有对象的身上添加新的属性方法 可以让对象或者组件进行扩展 示例: class Person{constructor(name,selfSkill){this.name namethis.selfSkill selfSkill}run 会走路}//所有人类都有的共同特性和技能let wjt new Person(王惊涛,写代码)let mashi new Pers…

Python实现KDJ指标计算:股票技术分析的利器系列(3)

Python实现KDJ指标计算&#xff1a;股票技术分析的利器系列&#xff08;3&#xff09; 介绍算法解释 代码rolling函数介绍计算LLV&#xff08;最低价最小值&#xff09;和HHV&#xff08;最高价最大值&#xff09;计算RSV计算SMA&#xff08;简单移动平均&#xff09; 完整代码…

micro-app以UMD js链接方式引入使用

npm 下载好micro-zoe/micro-app后&#xff0c;找到index.umd.js&#xff1a; 新建一个测试html&#xff0c;引入并使用&#xff1a; 参考&#xff1a; 微组件实践 - 掘金

八、计算机视觉-边界填充

文章目录 前言一、原理二、具体的实现 前言 在Python中使用OpenCV进行边界填充&#xff08;也称为zero padding&#xff09;是一种常见的图像处理操作&#xff0c;通常用于在图像周围添加额外的像素以便进行卷积或其他操作。下面是使用OpenCV进行边界填充的基本原理和方法 一…

答题抽奖活动怎么做_一场智慧与幸运的碰撞

答题抽奖&#xff0c;知识变现&#xff0c;一场智慧与幸运的碰撞&#xff01; 在这个信息爆炸的时代&#xff0c;如何吸引人们的注意力&#xff0c;成为每个营销者都需要面对的挑战。而答题抽奖活动&#xff0c;以其独特的魅力&#xff0c;正成为越来越多品牌吸引用户、提升用…

智能无人仓|加快步伐 河北沃克HEGERLS将突破与创新“常态化”

物流的发展涉及工业、商业各个领域&#xff0c;涵盖原材料&#xff0c;生产成品从起点到终点的全过程&#xff0c;在室内物流操作上涵盖了收、发、存、拣等作业。近年来&#xff0c;由于人工成本的提高&#xff0c;基础劳动力取得的难度不断地加大&#xff0c;自动化和智能化逐…

pytest 框架自动化测试

随笔记录 目录 1. 安装 2. 安装pytest 相关插件 2.1 准备阶段 2.2 安装 2.3 验证安装成功 3. pytest测试用例的运行方式 3.1 主函数模式 3.1.1 主函数执行指定文件 3.1.2 主函数执行指定模块 3.1.3 主函数执行某个文件中的某个类、方法、函数 3.1.4 主函数执行生…

『运维备忘录』之 SSH 命令详解

运维人员不仅要熟悉操作系统、服务器、网络等知识&#xff0c;甚至对于开发相关的也要有所了解。很多运维工作者可能一时半会记不住那么多命令、代码、方法、原理或者用法等等。这里我将结合自身工作&#xff0c;持续给大家更新运维工作所需要接触到的知识点&#xff0c;希望大…

C++Qt:noteBookPro_01

一、创建项目 选择Qt Widgets 常用的是QWidgets和MainWindow。两者的区别&#xff1a; QWidgets用于简单的窗口&#xff0c;没有内置的菜单栏、工具栏和状态栏。适用于简单专用的应用程序&#xff0c;不需要复杂的界面组件。 MainWindow是包含完整的菜单栏、工具栏和状态栏的主…

入门级10寸加固行业平板—EM-I10J

亿道信息以其坚固耐用的智能终端设备而闻名&#xff0c;近日发布了一款理想入门级 10 英寸加固平板电脑—I10J。 EM-I10J​​ 这是一款 10 英寸的平板电脑&#xff0c;主要运行 Windows 10操作系统&#xff0c;带有硬化塑料外壳&#xff0c;具有 IP65 防水防尘功能和 MIL-STD 8…

unity学习(19)——客户端与服务器合力完成注册功能(1)入门准备

逆向服务器用了三天的时间&#xff0c;但此时觉得一切都值&#xff0c;又可以继续学习了。 服务器中登录请求和注册请求由command变量进行区分&#xff0c;上一层的type变量都是login。 public void process(Session session, SocketModel model) {switch (model.Command){ca…

nvm安装配置环境

前言 对于前端开发人员来说&#xff0c;多个项目可能用的不同的node版本&#xff0c;如何方便快速的转换版本&#xff0c;nvm版本管理工具的出现&#xff0c;解决这个问题。 实战 1. 搜索nvm版本&#xff0c;我用的1.1.2&#xff0c;下载后直接安装。 2.在d盘建立nvm空文件…