正点原子I。MX6U嵌入式Qt开发指南第十一章网络编程3
今日头条/西瓜视频/抖音短视频 同名:正点原子
原子哥今日头条/西瓜视频/抖音短视频账号:正点原子原子哥
感谢各位的关注和支持,你们的关注和支持是正点原子无限前进的动力。
第十一章《网络编程》
由于本章内容较多,所以第十一章《网络编程》将会分为几个部分进行内容的发布,更多文章内容请持续关注今日头条正点原子官方账号。11.3 UDP通信11.3.1 UDP简介
UDP(User Datagram Protocol即用户数据报协议)是一个 轻量级的,不可靠的,面向数据报的无连接协议 。我们日常生活中使用的QQ,其聊天时的文字内容是使用UDP协议进行消息发送的。因为QQ有很多用户,发送的大部分都是短消息,要求能及时响应,并且对安全性要求不是很高的情况下使用UDP协议。但是QQ也并不是完全使用UDP协议,比如我们在传输文件时就会选择TCP协议,保证文件正确传输。像QQ语音和QQ视频通话,UDP的优势就很突出了。在选择使用协议的时候,选择UDP必须要谨慎。在网络质量令人十分不满意的环境下,UDP协议数据包丢失会比较严重。但是由于UDP的特性:它 不属于连接型协议 ,因而具有资源消耗小,处理速度快的优点,所以通常音频、视频和普通数据在传送时使用UDP较多,因为它们即使偶尔丢失一两个数据包,也不会对接收结果产生太大影响。
QUdpSocket类提供了一个UDP套接字。QUdpSocket是QAbstractSocket的子类,允许发送和接收UDP数据报。使用该类最常见的方法是使用bind()绑定到一个地址和端口,然后调用writeDatagram()和readDatagram() / receiveDatagram()来传输数据。注意发送数据一般少于512字节。如果发送多于512字节的数据,即使我们发送成功了,也会在IP层被分片(分成小片段)。
如果您想使用标准的QIODevice函数read()、readLine()、write()等,您必须首先通过调用connectToHost()将套接字直接连接到对等体。每次将数据报写入网络时,套接字都会发出bytesWritten()信号。
如果您只是想发送数据报,您不需要调用bind()。readyRead()信号在数据报到达时发出。在这种情况下,hasPendingDatagrams()返回true。调用pendingDatagramSize()来获取第一个待处理数据报的大小,并调用readDatagram()或receiveDatagram()来读取它。注意:当您接收到readyRead()信号时,一个传入的数据报应该被读取,否则这个信号将不会被发送到下一个数据报。
UDP通信示意图如下。重点是QUdpSocket类,已经为我们提供了UDP通信的基础。
UDP消息传送有三种模式,分别是单播、广播和组播三种模式。
单播(unicast):单播用于两个主机之间的端对端通信,需要知道对方的IP地址与端口。
广播(broadcast):广播UDP与单播UDP的区别就是IP地址不同,广播一般使用广播地址255.255.255.255,将消息发送到在同一广播(也就是局域网内同一网段)网络上的每个主机。值得强调的是:本地广播信息是不会被路由器转发。当然这是十分容易理解的,因为如果路由器转发了广播信息,那么势必会引起网络瘫痪。这也是为什么IP协议的设计者故意没有定义互联网范围的广播机制。广播地址通常用于在网络游戏中处于同一本地网络的玩家之间交流状态信息等。其实广播顾名思义,就是想局域网内所有的人说话,但是广播还是要指明接收者的端口号的,因为不可能接受者的所有端口都来收听广播。
组播(multicast):组播(多点广播),也称为"多播",将网络中同一业务类型主机进行了逻辑上的分组,进行数据收发的时候其数据仅仅在同一分组中进行,其他的主机没有加入此分组不能收发对应的数据。在广域网上广播的时候,其中的交换机和路由器只向需要获取数据的主机复制并转发数据。主机可以向路由器请求加入或退出某个组,网络中的路由器和交换机有选择地复制并传输数据,将数据仅仅传输给组内的主机。多播的这种功能,可以一次将数据发送到多个主机,又能保证不影响其他不需要(未加入组)的主机的其他通信。
注意:单播一样和多播是允许在广域网即Internet上进行传输的,而广播仅仅在同一局域网上才能进行。 11.3.2 UDP单播与广播
广播UDP与单播UDP的区别就是IP地址不同,所以我们的实例可以写成一个。我们可以这么理解,单播实际上是通信上对应一对一,广播则是一对多(多,这里指广播地址内的所有主机)。 11.3.2.1应用实例
本例目的:了解QUdpSocket单播和广播使用。
例10_udp_unicast_broadcast,UDP单播与广播应用(难度:一般)。项目路径为 Qt/2/10_udp_unicast_broadcast 。本例大体流程首先获取本地IP地址。创建一个udpSocket套接字,然后绑定本地主机的端口(也就是监听端口)。我们可以使用QUdpSocket类提供的读写函数readDatagram和writeDatagram,知道目标IP地址和端口,即可完成消息的接收与发送。
项目文件10_udp_unicast_broadcast.pro文件第一行添加的代码部分如下。 10_udp_unicast_broadcast.pro编程后的代码 1 QT += core gui network 2 3 greaterThan(QT_MAJOR_VERSION, 4): QT += widgets 4 5 CONFIG += c++11 6 7 # The following define makes your compiler emit warnings if you use 8 # any Qt feature that has been marked deprecated (the exact warnings 9 # depend on your compiler). Please consult the documentation of the 10 # deprecated API in order to know how to port your code away from it. 11 DEFINES += QT_DEPRECATED_WARNINGS 12 13 # You can also make your code fail to compile if it uses deprecated APIs. 14 # In order to do so, uncomment the following line. 15 # You can also select to disable deprecated APIs only up to a certain version of Qt. 16 #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 17 18 SOURCES += 19 main.cpp 20 mainwindow.cpp 21 22 HEADERS += 23 mainwindow.h 24 25 # Default rules for deployment. 26 qnx: target.path = /tmp/${TARGET}/bin 27 else: unix:!android: target.path = /opt/${TARGET}/bin 28 !isEmpty(target.path): INSTALLS += target
在头文件"mainwindow.h"具体代码如下。 mainwindow.h编程后的代码 /****************************************************************** Copyright Deng Zhimao Co., Ltd. 1990-2021. All rights reserved. * @projectName 10_udp_unicast_broadcast * @brief mainwindow.h * @author Deng Zhimao * @email 1252699831@qq.com * @net www.openedv.com * @date 2021-04-14 *******************************************************************/ 1 #ifndef MAINWINDOW_H 2 #define MAINWINDOW_H 3 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 #include 16 #include 17 18 class MainWindow : public QMainWindow 19 { 20 Q_OBJECT 21 22 public: 23 MainWindow(QWidget *parent = nullptr); 24 ~MainWindow(); 25 26 private: 27 /* Udp通信套接字 */ 28 QUdpSocket *udpSocket; 29 30 /* 按钮 */ 31 QPushButton *pushButton[5]; 32 33 /* 标签文本 */ 34 QLabel *label[3]; 35 36 /* 水平容器 */ 37 QWidget *hWidget[3]; 38 39 /* 水平布局 */ 40 QHBoxLayout *hBoxLayout[3]; 41 42 /* 垂直容器 */ 43 QWidget *vWidget; 44 45 /* 垂直布局 */ 46 QVBoxLayout *vBoxLayout; 47 48 /* 文本浏览框 */ 49 QTextBrowser *textBrowser; 50 51 /* 用于显示本地ip */ 52 QComboBox *comboBox; 53 54 /* 用于选择端口 */ 55 QSpinBox *spinBox[2]; 56 57 /* 文本输入框 */ 58 QLineEdit *lineEdit; 59 60 /* 存储本地的ip列表地址 */ 61 QList IPlist; 62 63 /* 获取本地的所有ip */ 64 void getLocalHostIP(); 65 66 private slots: 67 /* 绑定端口 */ 68 void bindPort(); 69 70 /* 解绑端口 */ 71 void unbindPort(); 72 73 /* 清除文本框时的内容 */ 74 void clearTextBrowser(); 75 76 /* 接收到消息 */ 77 void receiveMessages(); 78 79 /* 发送消息 */ 80 void sendMessages(); 81 82 /* 广播消息 */ 83 void sendBroadcastMessages(); 84 85 /* 连接状态改变槽函数 */ 86 void socketStateChange(QAbstractSocket::SocketState); 87 }; 88 #endif // MAINWINDOW_H
头文件里主要是声明界面用的元素,及一些槽函数。重点是声明udpSocket。
在源文件"mainwindow.cpp"具体代码如下。 mainwindow.cpp编程后的代码 /****************************************************************** Copyright Deng Zhimao Co., Ltd. 1990-2021. All rights reserved. * @projectName 10_udp_unicast_broadcast * @brief mainwindow.cpp * @author Deng Zhimao * @email 1252699831@qq.com * @net www.openedv.com * @date 2021-04-14 *******************************************************************/ 1 #include "mainwindow.h" 2 3 MainWindow::MainWindow(QWidget *parent) 4 : QMainWindow(parent) 5 { 6 /* 设置主窗体的位置与大小 */ 7 this->setGeometry(0, 0, 800, 480); 8 9 /* udp套接字 */ 10 udpSocket = new QUdpSocket(this); 11 12 /* 绑定端口按钮 */ 13 pushButton[0] = new QPushButton(); 14 /* 解绑端口按钮 */ 15 pushButton[1] = new QPushButton(); 16 /* 清空聊天文本按钮 */ 17 pushButton[2] = new QPushButton(); 18 /* 发送消息按钮 */ 19 pushButton[3] = new QPushButton(); 20 /* 广播消息按钮 */ 21 pushButton[4] = new QPushButton(); 22 23 /* 水平布局一 */ 24 hBoxLayout[0] = new QHBoxLayout(); 25 /* 水平布局二 */ 26 hBoxLayout[1] = new QHBoxLayout(); 27 /* 水平布局三 */ 28 hBoxLayout[2] = new QHBoxLayout(); 29 /* 水平布局四 */ 30 hBoxLayout[3] = new QHBoxLayout(); 31 32 /* 水平容器一 */ 33 hWidget[0] = new QWidget(); 34 /* 水平容器二 */ 35 hWidget[1] = new QWidget(); 36 /* 水平容器三 */ 37 hWidget[2] = new QWidget(); 38 39 40 vWidget = new QWidget(); 41 vBoxLayout = new QVBoxLayout(); 42 43 /* 标签实例化 */ 44 label[0] = new QLabel(); 45 label[1] = new QLabel(); 46 label[2] = new QLabel(); 47 48 lineEdit = new QLineEdit(); 49 comboBox = new QComboBox(); 50 spinBox[0] = new QSpinBox(); 51 spinBox[1] = new QSpinBox(); 52 textBrowser = new QTextBrowser(); 53 54 label[0]->setText("目标IP地址:"); 55 label[1]->setText("目标端口:"); 56 label[2]->setText("绑定端口:"); 57 58 /* 设置标签根据文本文字大小自适应大小 */ 59 label[0]->setSizePolicy(QSizePolicy::Fixed, 60 QSizePolicy::Fixed); 61 label[1]->setSizePolicy(QSizePolicy::Fixed, 62 QSizePolicy::Fixed); 63 label[2]->setSizePolicy(QSizePolicy::Fixed, 64 QSizePolicy::Fixed); 65 66 /* 设置端口号的范围,注意不要与主机的已使用的端口号冲突 */ 67 spinBox[0]->setRange(10000, 99999); 68 spinBox[1]->setRange(10000, 99999); 69 70 pushButton[0]->setText("绑定端口"); 71 pushButton[1]->setText("解除绑定"); 72 pushButton[2]->setText("清空文本"); 73 pushButton[3]->setText("发送消息"); 74 pushButton[4]->setText("广播消息"); 75 76 /* 设置停止监听状态不可用 */ 77 pushButton[1]->setEnabled(false); 78 79 /* 设置输入框默认的文本 */ 80 lineEdit->setText("您好!"); 81 82 /* 水平布局一添加内容 */ 83 hBoxLayout[0]->addWidget(pushButton[0]); 84 hBoxLayout[0]->addWidget(pushButton[1]); 85 hBoxLayout[0]->addWidget(pushButton[2]); 86 87 /* 设置水平容器的布局为水平布局一 */ 88 hWidget[0]->setLayout(hBoxLayout[0]); 89 90 hBoxLayout[1]->addWidget(label[0]); 91 hBoxLayout[1]->addWidget(comboBox); 92 hBoxLayout[1]->addWidget(label[1]); 93 hBoxLayout[1]->addWidget(spinBox[0]); 94 hBoxLayout[1]->addWidget(label[2]); 95 hBoxLayout[1]->addWidget(spinBox[1]); 96 97 /* 设置水平容器的布局为水平布局二 */ 98 hWidget[1]->setLayout(hBoxLayout[1]); 99 100 /* 水平布局三添加内容 */ 101 hBoxLayout[2]->addWidget(lineEdit); 102 hBoxLayout[2]->addWidget(pushButton[3]); 103 hBoxLayout[2]->addWidget(pushButton[4]); 104 105 /* 设置水平容器三的布局为水平布局一 */ 106 hWidget[2]->setLayout(hBoxLayout[2]); 107 108 /* 垂直布局添加内容 */ 109 vBoxLayout->addWidget(textBrowser); 110 vBoxLayout->addWidget(hWidget[1]); 111 vBoxLayout->addWidget(hWidget[0]); 112 vBoxLayout->addWidget(hWidget[2]); 113 114 /* 设置垂直容器的布局为垂直布局 */ 115 vWidget->setLayout(vBoxLayout); 116 117 /* 居中显示 */ 118 setCentralWidget(vWidget); 119 120 /* 获取本地ip */ 121 getLocalHostIP(); 122 123 /* 信号槽连接 */ 124 connect(pushButton[0], SIGNAL(clicked()), 125 this, SLOT(bindPort())); 126 connect(pushButton[1], SIGNAL(clicked()), 127 this, SLOT(unbindPort())); 128 connect(pushButton[2], SIGNAL(clicked()), 129 this, SLOT(clearTextBrowser())); 130 connect(pushButton[3], SIGNAL(clicked()), 131 this, SLOT(sendMessages())); 132 connect(pushButton[4], SIGNAL(clicked()), 133 this, SLOT(sendBroadcastMessages())); 134 connect(udpSocket, SIGNAL(readyRead()), 135 this, SLOT(receiveMessages())); 136 connect(udpSocket, 137 SIGNAL(stateChanged(QAbstractSocket::SocketState)), 138 this, 139 SLOT(socketStateChange(QAbstractSocket::SocketState))); 140 } 141 142 MainWindow::~MainWindow() 143 { 144 } 145 146 void MainWindow::bindPort() 147 { 148 quint16 port = spinBox[0]->value(); 149 150 /* 绑定端口需要在socket的状态为UnconnectedState */ 151 if (udpSocket->state() != QAbstractSocket::UnconnectedState) 152 udpSocket->close(); 153 154 if (udpSocket->bind(port)) { 155 textBrowser->append("已经成功绑定端口:" 156 + QString::number(port)); 157 158 /* 设置界面中的元素的可用状态 */ 159 pushButton[0]->setEnabled(false); 160 pushButton[1]->setEnabled(true); 161 spinBox[1]->setEnabled(false); 162 } 163 } 164 165 void MainWindow::unbindPort() 166 { 167 /* 解绑,不再监听 */ 168 udpSocket->abort(); 169 170 /* 设置界面中的元素的可用状态 */ 171 pushButton[0]->setEnabled(true); 172 pushButton[1]->setEnabled(false); 173 spinBox[1]->setEnabled(true); 174 } 175 176 /* 获取本地IP */ 177 void MainWindow::getLocalHostIP() 178 { 179 // /* 获取主机的名称 */ 180 // QString hostName = QHostInfo::localHostName(); 181 182 // /* 主机的信息 */ 183 // QHostInfo hostInfo = QHostInfo::fromName(hostName); 184 185 // /* ip列表,addresses返回ip地址列表,注意主机应能从路由器获取到 186 // * IP,否则可能返回空的列表(ubuntu用此方法只能获取到环回IP) */ 187 // IPlist = hostInfo.addresses(); 188 // qDebug()<addItem(ip.toString()); 194 // } 195 196 /* 获取所有的网络接口, 197 * QNetworkInterface类提供主机的IP地址和网络接口的列表 */ 198 QList list 199 = QNetworkInterface::allInterfaces(); 200 201 /* 遍历list */ 202 foreach (QNetworkInterface interface, list) { 203 204 /* QNetworkAddressEntry类存储IP地址子网掩码和广播地址 */ 205 QList entryList 206 = interface.addressEntries(); 207 208 /* 遍历entryList */ 209 foreach (QNetworkAddressEntry entry, entryList) { 210 /* 过滤IPv6地址,只留下IPv4 */ 211 if (entry.ip().protocol() == 212 QAbstractSocket::IPv4Protocol) { 213 comboBox->addItem(entry.ip().toString()); 214 /* 添加到IP列表中 */ 215 IPlist<clear(); 226 } 227 228 /* 客户端接收消息 */ 229 void MainWindow::receiveMessages() 230 { 231 /* 局部变量,用于获取发送者的IP和端口 */ 232 QHostAddress peerAddr; 233 quint16 peerPort; 234 235 /* 如果有数据已经准备好 */ 236 while (udpSocket->hasPendingDatagrams()) { 237 /* udpSocket发送的数据报是QByteArray类型的字节数组 */ 238 QByteArray datagram; 239 240 /* 重新定义数组的大小 */ 241 datagram.resize(udpSocket->pendingDatagramSize()); 242 243 /* 读取数据,并获取发送方的IP地址和端口 */ 244 udpSocket->readDatagram(datagram.data(), 245 datagram.size(), 246 &peerAddr, 247 &peerPort); 248 /* 转为字符串 */ 249 QString str = datagram.data(); 250 251 /* 显示信息到文本浏览框窗口 */ 252 textBrowser->append("接收来自" 253 + peerAddr.toString() 254 + ":" 255 + QString::number(peerPort) 256 + str); 257 } 258 } 259 260 /* 客户端发送消息 */ 261 void MainWindow::sendMessages() 262 { 263 /* 文本浏览框显示发送的信息 */ 264 textBrowser->append("发送:" + lineEdit->text()); 265 266 /* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */ 267 QByteArray data = lineEdit->text().toUtf8(); 268 269 /* 要发送的目标Ip地址 */ 270 QHostAddress peerAddr = IPlist[comboBox->currentIndex()]; 271 272 /* 要发送的目标端口号 */ 273 quint16 peerPort = spinBox[1]->value(); 274 275 /* 发送消息 */ 276 udpSocket->writeDatagram(data, peerAddr, peerPort); 277 } 278 279 void MainWindow::sendBroadcastMessages() 280 { 281 /* 文本浏览框显示发送的信息 */ 282 textBrowser->append("发送:" + lineEdit->text()); 283 284 /* 要发送的信息,转为QByteArray类型字节数组,数据一般少于512个字节 */ 285 QByteArray data = lineEdit->text().toUtf8(); 286 287 /* 广播地址,一般为255.255.255.255, 288 * 同一网段内监听目标端口的程序都会接收到消息 */ 289 QHostAddress peerAddr = QHostAddress::Broadcast; 290 291 /* 要发送的目标端口号 */ 292 quint16 peerPort = spinBox[1]->text().toInt(); 293 294 /* 发送消息 */ 295 udpSocket->writeDatagram(data, peerAddr, peerPort); 296 } 297 /* socket状态改变 */ 298 void MainWindow::socketStateChange(QAbstractSocket::SocketState state) 299 { 300 switch (state) { 301 case QAbstractSocket::UnconnectedState: 302 textBrowser->append("scoket状态:UnconnectedState"); 303 break; 304 case QAbstractSocket::ConnectedState: 305 textBrowser->append("scoket状态:ConnectedState"); 306 break; 307 case QAbstractSocket::ConnectingState: 308 textBrowser->append("scoket状态:ConnectingState"); 309 break; 310 case QAbstractSocket::HostLookupState: 311 textBrowser->append("scoket状态:HostLookupState"); 312 break; 313 case QAbstractSocket::ClosingState: 314 textBrowser->append("scoket状态:ClosingState"); 315 break; 316 case QAbstractSocket::ListeningState: 317 textBrowser->append("scoket状态:ListeningState"); 318 break; 319 case QAbstractSocket::BoundState: 320 textBrowser->append("scoket状态:BoundState"); 321 break; 322 default: 323 break; 324 } 325 }
第146~163行,绑定端口。使用bind方法,即可绑定一个端口。注意我们绑定的端口不能和主机已经使用的端口冲突!
第165~174行,解绑端口。使用abort方法即可解绑。
第229~258行,接收消息,注意接收消息是QByteArray字节数组。读数组使用的是readDatagram方法,在readDatagram方法里可以获取对方的套接字IP地址与端口号。
第261~277行,单播消息,需要知道目标IP与目标端口号。即可用writeDatagram方法发送消息。
第279~296行,广播消息与单播消息不同的是将目标IP地址换成了广播地址,一般广播地址为255.255.255.255。 11.3.2.2程序运行效果
本实例可以做即是发送者,也是接收者。如果在同一台主机同一个系统里运行两个本例程序。不能绑定同一个端口!否则会冲突!当您想测试在同一局域网内不同主机上运行此程序,那么绑定的端口号可以相同。
本例设置目标IP地址为127.0.0.1,此IP地址是Ubuntu/Windows上的环回IP地址,可以用于无网络时测试。绑定端口号与目标端口号相同,也就是说,此程序正在监听端口号为10000的数据,此程序也向目标IP地址127.0.0.1的10000端口号发送数据,实际上此程序就完成了自发自收。
当我们点击发送消息按钮时,文本消息窗口显示发送的数据"您好!",同时接收到由本地IP 127.0.0.1发出的数据"您好!"。其中ffff:是通信套接字的标识。呵呵!您可能会问为什么不是本主机的其它地址如(192.168.1.x)发出的呢?因为我们选择了目标的IP地址为127.0.0.1,那么要与此目标地址通信,必须使用相同网段的IP设备与之通信。注意不能用本地环回发送消息到其他主机上。因为本地环回IP只适用于本地主机上的IP通信。
当我们点击广播消息按钮时,广播发送的目标IP地址变成了广播地址255.255.255.255。那么我们将收到从本地IP地址192.168.x.x的数据。如下图,收到了从192.168.1.129发送过来的数据。因为环回IP 127.0.0.1的广播地址为255.0.0.0,所以要与255.255.255.255的网段里的IP通信数据必须是由192.168.x.x上发出的。如果其他同一网段上的其他主机正在监听目标端口,那么它们将同时收到消息。这也验证了上一小节为什么会从127.0.0.1发送数据。
本例不难,可能有点绕,大家多参考资料理解理解,知识点有点多,如果没有些通信基础的话,我们需要慢慢吃透。
未完待续....
12月1日下午消息,今天开始爱奇艺进行一轮大规模裁员12月1日下午消息,新浪科技独家获悉,今天开始爱奇艺进行一轮大规模裁员,多位被裁员工独家透露,此次裁员是爱奇艺历史上规模最大的一轮裁员,部分部门几乎全员被裁,即使是诸如内容智能硬件
携号转网,原手机卡的话费可能不好拿回来上个月,大船办理了携号转网,新手机卡到了,携号入网算是办完了,但是有一件事要到这个月办理,那就是转网前卡里的话费怎么处理。转网前大船曾经咨询过联通的客服,ta说转网后,下个月可以去
张磊的投资理念张磊创立的高瓴资本是目前亚洲地区资产管理规模最大的投资基金之一,规模达到600亿美元,他本人也成为耶鲁大学历史上第一位华人校董。高瓴资本投出了腾讯京东美团点评滴滴蔚来汽车UberA
蒋尚义离去,梁孟松辞职,中芯国际发声明朗了事情逐渐明朗!去年有记者问梁孟松,中芯的14nm可否在国际上一战,当时其回答的是还需要一段时间,但是在今年却传来,已经可以与行业相提并论。这样的进度可以说是巨大的,且之后中芯也传来
注册英国公司做跨境电商的优势1。可入驻AMAZONEBAY等电商平台英国本土公司入驻跨境电商品牌,利于产品销售,更受当地消费者信赖。2。享受低税率销售额8。5万英镑以下不需要注册缴纳VAT,超过8。5万英镑销
C程序中与内存有关的常见错误与内存有关的错误属于那种最令人惊恐的错误。在时间和空间上,经常在距离错误源一段距离之后才表现出来。将错误的数据写到错误的位置,你的程序可能在最终失败之前运行了一段时间。下面列举并分
宠物云领养小程序专业搭建开发宠物云领养小程序专业搭建开发团队宠物云领养小程序开发,宠物云领养小程序专业开发,宠物云领养小程序专业搭建开发,宠物领养软件开发,宠物领养软件搭建开发,宠物云领养软件专业开发随着移动互联网不断发展,诸多APP软件出
分销商城小程序开发及解决方案分销商城小程序的开发模式,顾名思义就是所有微信用户都能申请成为商城小程序的分销员,参与商城的推广与宣传同时在别人达成交易后还可以获得一定的奖励或者返券之类的实惠。很多商家之所以会选
小米发烧失败,苹果再度登顶销量第一,中国厂商还有未来吗?国产手机一味的跟风华为的价格,却没有一家能跟的上华为的技术!!!华为被制裁后,这导致后面的大批国产机价格和质量严重不匹配,导致国人对国产手机越来越失望!!!58K的价格,2K左右的
华为户外电源京东旗舰店开售预备购盼望着,盼望着,国货之光华为户外电源开售了有了这台移动小电站哪怕山高水远手机不怕电量低了饿了不吃压缩饼干了野地真能打王者了整个人都硬气起来了12月2日0000开售,限时直降200元
早上早点铺中午快餐店晚上烧烤摊分时共享店铺的经营模式是否合规?央广网北京12月2日消息(总台央广记者韩雪莹)作为新兴经济模式,共享经济不知不觉完成了从无到有,从有到少不了的跃迁,共享单车共享充电宝已经融入人们日常生活的方方面面。而近期,分时共