任务动机:用USB代替网络通讯,实现Cartographer上位机与下位机之间的消息传递。
任务描述:根据任务动机,研发实现用USB代替网络通讯,形成文档。
这是个漫长且无聊的过程,因为有关这方面的教程少之又少,在本人无数次踩坑下终于搞定。感兴趣的小伙伴可以打开官方教程链接体验下想找教程却没有教程然后砸电脑未遂的心情。
在RK3399 ProD里面,Linux的Kernel和rootfs(根文件系统)是分开地址存放的,rootfs是在Kernel之上运行的,所有的驱动和最基础的指令都存放在Kernel里面,各个厂商根据自己的特性在Kernel上开发自己的操作系统(例如Fedora和Ubuntu)。板子可以在不用重新编译内核的情况下更换操作系统,在本文档中,我们主要讲如何编译驱动到kernel里面,并支持多种操作系统。我用的rootfs链接(Ubuntu):百度云网盘提取码:q9my
首先,RK3399 ProD是A53+A7的芯片,你需要特殊的gcc编译器,然后再编译。
git clone https://github.com/friendlyarm/prebuilts.git -b master --depth 1
cd prebuilts/gcc-x64
cat toolchain-6.4-aarch64.tar.gz* | sudo tar xz -C /
然后再编辑你的.bashrc,把下面的东西加到bashrc的最底部。
export PATH=/opt/FriendlyARM/toolchain/6.4-aarch64/bin:$PATH
export GCC_COLORS=auto
接下来,你需要下载Linux Kernel源码,由于官方给的源码太多坑,我把我修改后的直接上传到gitee供国人下载,方便快捷。
git clone -b kernel-joybrick https://gitee.com/harryzhangabc/ros_bridge-and-catographer-setup.git
这里介绍下如何给驱动添加USB RNIDS/gagnet网络功能。
cd /path/to/your/ros_bridge-and-catographer-setup/dir/
make menuconfig
然后把下面的几个选项操作成如下方式
<M> USB Gadget Drivers
<M> USB functions configurable through configfs
<M> Ethernet Gadget (with CDC Ethernet support)
[*] RNDIS support (NEW)
接着在该目录下编译内核。
./make.sh linux prod
编译完成后会生成boot_linux.img文件,把这个文件烧入到3399pro对应的区域中烧写教程,只要烧这个文件,其他的都不用管。当然你想偷懒的话可以用我编译好的:D百度网盘提取码:pt7p
最后看下你编译好的文件,有几个驱动模块文件需要手动复制到RK3399 ProD里面手动加载
drivers/usb/gadget/function/u_ether.ko
drivers/usb/gadget/function/usb_f_ecm_subset.ko
drivers/usb/gadget/function/usb_f_ecm.ko
drivers/usb/gadget/function/usb_f_rndis.ko
drivers/usb/gadget/legacy/g_ether.ko
drivers/usb/gadget/libcomposite.ko
然后在设备上,依次加载上述模块
insmod libcomposite.ko
insmod u_ether.ko
insmod usb_f_rndis.ko
insmod usb_f_ecm.ko
insmod usb_f_ecm_subset.ko
insmod g_ether.ko
注意: 要先加载 libcomposite.ko 和 u_ether.ko,后面的模块才可以加载进去。
用数据线连接 PC 机和设备的 OTG 接口,在 PC 机中执行 lsusb 命令可以查看到 USB 以太网设备,即说明连接成功。
firefly@Desktop:~$ lsusb
Bus 002 Device 003: ID 09da:5814 A4Tech Co., Ltd.
Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 005: ID 04f2:b2ea Chicony Electronics Co., Ltd Integrated Camera [ThinkPad]
Bus 001 Device 004: ID 0a5c:21e6 Broadcom Corp. BCM20702 Bluetooth 4.0 [ThinkPad]
Bus 001 Device 003: ID 147e:1002 Upek Biometric Touchchip/Touchstrip Fingerprint Sensor
Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 004 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 003 Device 003: ID 0525:a4a2 Netchip Technology, Inc. Linux-USB Ethernet/RNDIS Gadget
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
输入执行 ifconfig -a 命令,可以查看到以下信息:
root@firefly:~# ifconfig -a
# eth0 是有线网卡
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 168.168.100.48 netmask 255.255.0.0 broadcast 168.168.255.255
inet6 fe80::1351:ae2f:442e:e436 prefixlen 64 scopeid 0x20<link>
ether 8a:4f:c3:77:94:ac txqueuelen 1000 (Ethernet)
RX packets 9759 bytes 897943 (897.9 KB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 236 bytes 35172 (35.1 KB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
device interrupt 42 base 0x8000
...
# usb0 是虚拟的 usb 网卡
usb0: flags=4098<BROADCAST,MULTICAST> mtu 1500
ether 4a:81:b1:34:d2:ad txqueuelen 1000 (Ethernet)
RX packets 0 bytes 0 (0.0 B)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 0 bytes 0 (0.0 B)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
然后给 usb0 网卡自定义一个适当的 IP,注意要设置 usb0 的 IP 和有线网卡 eth0 的 IP 不在同一网段!!
sudo ifconfig usb0 192.168.2.222 up
首先,需要在设置里设置板子的ip,由于我设置的是192.168.2.222,那么直接在系统设置里面设置ip地址为手动,填入ip就行。(系统不同,这里就不放图了!!!)
# 设置 USB 网卡的 IP 地址和设备的 usb0 的 IP 地址在同一网段
firefly@Desktop:~$ sudo ifconfig enp0s20u2i1 192.168.2.95
#设置默认网关:要设置为设备 usb0 的 ip 地址,因为后面要通过 usb0 来进行流量的转发
firefly@Desktop:~$ sudo route add default gw 192.168.2.222
设置完设备和 PC 机的 IP 后,使用USB-C/USB-A线连接到电脑,其中,电脑对板子的ip为192.168.2.222,板子对电脑的ip地址是192.168.2.95,他们是可以互相 ping 通的,PC 机也可以用 ssh 命令登录到设备。
然后,我们使用性能测试工具ipref测试下带宽。在USB3.0接口下,带宽约为250Mbit/s左右(30MByte/s),满足日常传输使用。
配置过程中要注意以下几点:
在RK3399上(从机)
首先编辑/etc/hosts
sudo vi /etc/hosts
然后在中间加入以下内容
192.168.2.95 server-HB
名字自取啥都行,IP根据你自己情况来定 然后编辑.bashrc
sudo vi ~/.bashrc
加入以下内容
export ROS_HOSTNAME=server-HB
export ROS_MASTER_URI=http://192.168.2.95:11311
export ROS_IP=192.168.2.222
在电脑上
编辑.bashrc
sudo gedit ~/.bashrc
加入下面内容
export ROS_MASTER_URI=http://192.168.2.95:11311
ROS连接大功告成!
在电脑上输入以下命令
roscore
rosrun turtlesim turtlesim_node
然后在RK3399上打开一个终端
rosrun turtlesim turtle_teleop_key
然后你就可以控制小乌龟行走了!USBC到此配置完成!到目前为止ROS传输demo均已配置完成,Cartographer也能跑通,上位机可以直接获取到Cartographer的话题信息(这里就不放图了),剩余的硬件驱动/适配上位机程序部分需要一些时间,预计下周完成。
一般来说,不同板子都有对应的特制功能从而设置了对应的特殊硬件(比如I2C、SPI设备),这些硬件需要在内核注册相关dts节点才能使用。因此,我们在购买板子的时候商家已经注册好了这些dts节点供我们使用,我们只要直接编译内核就好,如果是我们自己的板子,就需要自己去编写相对应的节点和驱动。
Linux设备树详解
对于这款芯片而言,rockchip官方已经给出了相关的基础设备树,其芯片所有的外设的功能已经预制在/kernel/arch/arm64/boot/dts/rockchip中的rk3399.dtsi和rk3399pro.dtsi中,你只需要创建一个你自己的.dtsi,然后在里面加入你想使用的外设设备,然后再写一个驱动.c文件放到/kernel/drivers里面即可。以下是自行编写并实现PWM驱动的一个示例:
//为了省时间,我直接在原厂板子上提供的.dtsi进行修改,文件位于/kernel/arch/arm64/boot/dts/rockchip/rk3399pro-toybrick.dtsi,同时,你要自己画板子的话,可以参考这个文件的写法,写一个自己的板级dtsi文件。
// #include <dt-bindings/pwm/pwm.h>
// #include <dt-bindings/gpio/gpio.h>
// #include <dt-bindings/pinctrl/rockchip.h>
// #include <dt-bindings/input/input.h>
// #include <dt-bindings/display/drm_mipi_dsi.h>
// #include <dt-bindings/sensor-dev.h>
// #include "rk3399pro.dtsi"
// #include "rk3399-opp.dtsi"
//注:如果是自己写文件的话,请手动加入以上这些库文件,然后再继续进行编写
pwm1: pwm_1 {
status = "okay";
compatible = "pwm_1";
pwm_id = <1>;
}; //添加一个pwm节点
backlight: backlight { //在这个上面添加
compatible = "pwm-backlight";
......
编写驱动文件
#include <linux/module.h>
#include <linux/pwm.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/debugfs.h>
#include <linux/seq_file.h>
#include <dt-bindings/pwm/pwm.h>
#include <linux/platform_device.h>
#include <asm/io.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/uaccess.h>
#include <linux/fs.h>
#include <linux/platform_device.h>
#include <linux/fb.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#define SYS_DEV_CONFIG 1
#define PWM_1_SET_PERIOD _IOW('A', 0x11, unsigned int)
#define PWM_1_GET_PERIOD _IOR('A', 0x12, unsigned int)
#define PWM_1_SET_DUTY _IOW('A', 0x13, unsigned int)
#define PWM_1_GET_DUTY _IOR('A', 0x14, unsigned int)
#define PWM_1_ENABLE _IO('A', 0x15)
#define PWM_1_DISABLE _IO('A', 0x16)
static int gval = 0;
struct pwm_data {
int pwm_id;
struct pwm_device *pwm;
unsigned int period;
unsigned int pwm_period_ns;
unsigned int max_period;
unsigned int min_period;
unsigned int duty_ns;
bool enabled;
};
struct pwm_data g_pdata;
#ifdef SYS_DEV_CONFIG
static ssize_t pwm_store(struct device *dev, \
struct device_attribute *attr, const char *buf,size_t count)
{
unsigned long state;
int c,ret;
ret = kstrtoul(buf, 10, &state);
if (state > g_pdata.max_period)
state = g_pdata.max_period;
else if (state < g_pdata.min_period)
state = g_pdata.min_period;
c = state;
if (ret)
return ret;
g_pdata.enabled = true;
pwm_config(g_pdata.pwm, c, g_pdata.pwm_period_ns);
pwm_enable(g_pdata.pwm);
gval = c;
return count;
}
static ssize_t pwm_show(struct device *dev, \
struct device_attribute *attr, char *buf)
{
return sprintf(buf, "%u\n", gval);
}
static struct kobject *pwm_kobj;
static DEVICE_ATTR(pwm, (S_IWUSR|S_IRUSR|S_IWGRP|S_IRGRP), pwm_show, pwm_store);
#endif
static int pwm_status_update(struct pwm_data *pdata) //控制占空比、周期
{
if (pdata->enabled)
return 0;
pdata->duty_ns = pdata->duty_ns * pdata->period / 100;
pwm_enable(pdata->pwm);
pwm_config(pdata->pwm, pdata->duty_ns, g_pdata.pwm_period_ns);
pdata->enabled = true;
gval = pdata->duty_ns;
return 0;
}
ssize_t pwm_parse_dt(struct pwm_data *pdata, struct platform_device *pdev) //寻找已经注册的参数
{
struct device_node *np = pdev->dev.of_node;
const __be32 *id, *min_period, *max_period, *duty_ns;
int len;
id = of_get_property(np, "pwm_id", &len);
if (id)
pdata->pwm_id = be32_to_cpu(*id);
min_period = of_get_property(np, "min_period", &len);
if (min_period)
pdata->min_period = be32_to_cpu(*min_period);
max_period = of_get_property(np, "max_period", &len);
if (max_period)
pdata->max_period = be32_to_cpu(*max_period);
pdata->pwm_period_ns = pdata->max_period - pdata->min_period;
duty_ns = of_get_property(np, "duty_ns", &len);
if (duty_ns)
pdata->duty_ns = be32_to_cpu(*duty_ns);
return 0;
}
static void update_parameter(struct pwm_data data)
{
struct pwm_data *pw_data = &data;
pwm_status_update(pw_data);
}
static int pwm_1_open(struct inode *inode, struct file *filp)
{
g_pdata.enabled = true;
update_parameter(g_pdata);
return 0;
}
static int pwm_1_release(struct inode *inode, struct file *filp)
{
g_pdata.enabled = false;
update_parameter(g_pdata);
return 0;
}
static ssize_t pwm_1_read(struct file *filp, char __user *buf, size_t len, loff_t *pos)
{
return 0;
}
static ssize_t pwm_1_write(struct file *filp, const char __user *buf, size_t len, loff_t *pos)
{
return 0;
}
static unsigned int pwm_1_get_period(void)
{
return (g_pdata.period / 1000000);
}
static long pwm_1_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) //驱动注册成功后,会在/dev下创建一个/pwm1的接口
{
unsigned int period;
unsigned int duty;
void __user *argp = (void __user *)arg;
switch (cmd)
{
case PWM_1_SET_PERIOD:
if (argp == NULL) {
printk("1: invalid argument.");
return -EINVAL;
}
if (copy_from_user(&period, argp, sizeof(unsigned int))) {
printk("copy_from_user failed.");
return -EFAULT;
}
g_pdata.period = period;
update_parameter(g_pdata);
break;
case PWM_1_GET_PERIOD:
period = pwm_1_get_period();
if (copy_to_user(argp, &period, sizeof(unsigned int))) {
printk("copy_to_user failed.");
return -EFAULT;
}
break;
case PWM_1_SET_DUTY :
if (argp == NULL) {
printk("1: invalid argument.");
return -EINVAL;
}
if (copy_from_user(&duty, argp, sizeof(unsigned int))) {
printk("copy_from_user failed.");
return -EFAULT;
}
if ((duty < 0) || (duty > 100)) {
printk("1: invalid argument.");
return -EINVAL;
}
g_pdata.duty_ns = duty;
update_parameter(g_pdata);
break;
case PWM_1_GET_DUTY :
if (copy_to_user(argp, &g_pdata.duty_ns, sizeof(unsigned int))) {
printk("copy_to_user failed.");
return -EFAULT;
}
break;
case PWM_1_ENABLE:
g_pdata.enabled = true;
update_parameter(g_pdata);
break;
case PWM_1_DISABLE:
g_pdata.enabled = false;
update_parameter(g_pdata);
break;
default:
printk("pwm: cmd error!\n");
return -EFAULT;
}
return 0;
}
struct file_operations pwm_1_fops = { //PWM1操作函数入口
.owner = THIS_MODULE,
.open = pwm_1_open,
.release = pwm_1_release,
.write = pwm_1_write,
.read = pwm_1_read,
.unlocked_ioctl = pwm_1_ioctl
};
struct miscdevice pwm_1_dev =
{
.minor = MISC_DYNAMIC_MINOR,
.fops = &pwm_1_fops,
.name = "pwm",
};
static int pwm_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
struct pwm_data *pdata = pdev->dev.platform_data;
int ret;
if (!np) {
dev_err(&pdev->dev, "Device Tree node missing\n");
return -EINVAL;
}
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
if (np)
ret = pwm_parse_dt(pdata, pdev);
pdata->enabled = false;
pdata->pwm = pwm_request(pdata->pwm_id, "pwm_1");
if (IS_ERR(pdata->pwm)) {
dev_err(&pdev->dev, "unable to request legacy PWM\n");
ret = PTR_ERR(pdata->pwm);
goto err;
}
if (pdata->pwm_period_ns > 0)
pwm_set_period(pdata->pwm, pdata->pwm_period_ns);
pdata->period = pwm_get_period(pdata->pwm);
g_pdata = *pdata;
pwm_status_update(pdata);
printk("%s: Toybrick PWM by harry Demo !\n", __func__);
misc_register(&pwm_1_dev);
#ifdef SYS_DEV_CONFIG
pwm_kobj = kobject_create_and_add("pwm", NULL);
if (pwm_kobj == NULL) {
printk("create kobject fail \n");
ret = -ENOMEM;
goto err;
}
ret = sysfs_create_file(pwm_kobj, &dev_attr_pwm.attr);
if (ret) {
printk("pwm sysfs_init: sysfs_create_group failed\n");
goto err;
}
#endif
return 0;
err:
#ifdef SYS_DEV_CONFIG
kobject_del(pwm_kobj);
#endif
pwm_free(pdata->pwm);
return ret;
}
static int pwm_remove(struct platform_device *pdev)
{
struct pwm_data *pdata = pdev->dev.platform_data;
#ifdef SYS_DEV_CONFIG
kobject_del(pwm_kobj);
#endif
pwm_free(pdata->pwm);
return 0;
}
static const struct of_device_id pwm_dt_ids[] = {
{ .compatible = "pwm_1"},
{ }
};
MODULE_DEVICE_TABLE(of, pwm_dt_ids);
static struct platform_driver pwm_driver = {
.driver = {
.name = "pwm",
.of_match_table = pwm_dt_ids,
},
.probe = pwm_probe,
.remove = pwm_remove,
};
module_platform_driver(pwm_driver);
MODULE_AUTHOR("Harry <[email protected]>");
MODULE_LICENSE("GPL");
然后添加到menuconfig
config PWM_RK3399_1
tristate "RK3399 PWM1 support"
default y
help
Generic PWM framework driver for RK3399.
To compile this driver as a module, choose M here: the module
will be called pwm1.
添加到makefile
obj-$(CONFIG_PWM_RK3399_1) += pwm-rk3399-1.o
最后编译
./make.sh linux prod
注意,如果是自己制作的板子的话,可以参考make.sh里面的写法,先编译.dts/.dtsi文件,然后再编译内核
这是一个基于STM32的智能小车下位机(底盘控制器),兼容ROS操作系统,和cartographer项目的上位机进行适配,上位机通过STM32虚拟串口与下位机透传,波特率自适应不丢包。
本软件基于C编写,支持一路SBUS接收机、一路GPS、一路IMU、一路编码器,支持速度闭环控制、方向控制、路径规划自动驾驶(基于GPS、测试中)、颠簸路况补偿。
支持上传GPS/IMU等信息并加入时间戳供上位机使用和参考。
http://wiki.t-firefly.com/zh_CN/Firefly-RK3399/ubuntu_manual.html#usb-yi-tai-wang USB 以太网设置
http://t.rock-chips.com/wiki.php?mod=view&id=14 如何烧写固件
http://wiki.friendlyarm.com/wiki/index.php/NanoPC-T4/zh 12.6节 编译 FriendlyCore/FriendlyDesktop/Lubuntu/EFlasher的内核源代码
文章浏览阅读175次。Redis 主从复制+哨兵+集群一.主从复制-哨兵-集群二.主从复制1.主从复制的作用2.主从复制流程3.部署Redis 主从复制三.哨兵模式1.哨兵模式的原理2.哨兵模式的作用3.哨兵结构由两部分组成,哨兵节点和数据节点部署哨兵模式四.集群模式1.集群的作用,可以归纳为两点2.Redis集群的数据分片3.以3个节点组成的集群为例4.Redis集群的主从复制模型5.Redis集群部署一.主从复制-哨兵-集群1.主从复制:主从复制是高可用Redis的基础,哨兵和集群都是在主从复制基础上实现高可用的。主从复_windows redis 主从复制部署
文章浏览阅读1.3w次,点赞181次,收藏596次。HashMap的原理与实现版本之更迭:–》JDK 1.7 : Table数组+ Entry链表;–》JDK1.8 : Table数组+ Entry链表/红黑树;(为什么要使用红黑树?)一问HashMap的实现原理你看过HashMap源码吗,知道底层的原理吗为什么使用数组+链表用LinkedList代替数组可以吗既然是可以的,为什么不用反而用数组。重要变量介绍:..._hashmap原理详解,看不懂算我输(附面试题)
文章浏览阅读959次。干货:问题:在a.com上上传图片至b.comhtml(a.com)<div class="box_03"> <iframe style="width:100%;height:100%;" frameborder="0" scrolling="no" src="/picupload/ming?imgurl=&shuiyin=&uploadUr..._php 上传图片流跨域怎么做
文章浏览阅读1.2w次,点赞4次,收藏2次。实验拓扑:实验说明:1.把SW3模拟成三台PC,配置三个VLAN对应的SVI地址。2.HSRP:SW1做vlan 10的活动设备,SW2做vlan 20的备份设备;SW1做vlan 10的备份设备,SW2做vlan 20的活动设备;3.VRRP:SW1做vlan 30的Master,SW2做vlan 30的备份设备。4.down掉环回口,检查数据包的路径。实..._hsrp 可以在svi口配置么
文章浏览阅读8k次。今天编译kafka-0.8.2.1-src源代码,发现一个问题。编译始终报错,错误如下:lizhitao@users-MacBook-Pro-2:~/mt_wp/open_source/kafka-platform/kafka-0.8.2.1-src$ gradle jar_core_2_10_4 --stacktraceTo honour the JVM settings for this bu_to honour the jvm settings for this build a new jvm will be forked. please c
文章浏览阅读600次。微信支付流程都是我自己工作中开发的,亲测可用,不喜勿喷。controller中我是这么写的,你们需要根据自己的业务需求改动。ResponseBean是我自己封装的,你们可以改成你们想要的形式。/*** 微信统一下单接口* @return*/@RequestMapping(value = "/doUnifiedOrder", method = RequestMethod.POST)public Re..._java+微信支付 dounifiedrefund
文章浏览阅读140次。I'm having a strange time dealing with selecting from a table with about 30,000 rows.It seems my script is using an outrageous amount of memory for what is a simple, forward only walk over a query res..._pdo_mysql.cache_size
文章浏览阅读1.0k次。最近在使用layui的过程中,遇到了表格合并单元格,设置不同底色的问https://www.hixiaoe.com/题。在此总结,大家一起学习。效果如下:同一组新闻的底色相同实现代码:<script> layui.config({ base: '/static/' //静态资源所在路径 }).extend({ index: 'admin/lib/index' //主入口模块 .._layui tablemerge 合并背景
文章浏览阅读3.8k次,点赞10次,收藏18次。Kali Linux系统作为白帽、黑帽最受欢迎的渗透测试系统,你如果是一个安全渗透专家或者网络安全管理员,必须要学会慎重并且合理地利用这个系统,因为对目标系统造成的实质伤害会带来法律的约束以及制裁!1、Kali Linux下载官网下载镜像:下载链接:https://www.kali.org2、安装配置我这里使用的虚拟机软件是 VMware 15,名字随便看需要,也可按默认配置建议选4G,也可以选2G内存默认,下一步默认,下一步默认,下一步默认,下一步这里建议将磁_kali2020.3安装
文章浏览阅读131次。应用场景:例如秒杀。瞬时大量写入订单到数据库,导致数据库无法及时响应。此时可以采用Redis做消息队列,把所有需要写入的数据先写入Redis消息队列中,然后同时在服务器开启php-cli进程循环读取队列中的数据,异步写入数据库。使用redis做消息队列可能会出现消息丢失的情况,因为没有消息接收的确认机制。大型程序,应该使用类似RabitMQ来做专业消息队列。1、使用publish/subscrib..._mysql 做队列好还是redis做队列
文章浏览阅读1.3k次。本文转自:https://www.cnblogs.com/wcwnina/p/8029156.htmlApache简介 Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页服务器,可以在大多数计算机操作系统中运行,由于其多平台和安全性被广泛使用,是最流行的Web服务器端软件之一。它快速、可靠并且可通过简单的API扩展,将..._no such command: nginx. please use /usr/bin/yum --help
文章浏览阅读560次。xShell:最常用的软件!远程操作linux,打开命令行终端!终端模拟软件。export LANG=“zh_CN.UTF-8” #中文export LANG=“en_US.UTF-8” #英文查看linux的ip地址:ifconfig;Linux无法使用ifconfig命令查看ip地址是因为没有安装net-tool所以执行下面的语句进行安装。_linux 粘贴之后 只做提示