GDB 概述: 主要用于调试程序,帮助找出程序BUG. GDB基本调试:(https://blog.csdn.net/zdy0_2004/article/details/80102076) 1,编译程序加上-g选项 2,通过一系列指令进行设置与查看调试 指令: 1,l 显示源代码 2,b 设置断点 3,i 查看信息,如i b查看设置的断点信息 4,n 执行下条语句 5,r 运行程序 6,p 打印变量值,如p sum 打印变量sum的值 7,q 退出gdb调试 GDB嵌入式远程调试:(https://www.cnblogs.com/Dennis-mi/articles/5018745.html) 嵌入式目标机装上gdbserver,挂载PC共享目录,使用gdbserver运行程序,PC端连接目标机进行gdb调试。 GDB调试多进程: 1,单独调试子进程, 使用attach “PID”单独调试 2,使用调试器选项follow-fork-mode选项,设置调试父进程还是子进程,如set follow-fork-mode child GDB调试多线程: 1,info threads 显示当前可调试的所有线程,gdb会为每个线程分配一个ID 2,thread ID 调试指定的目标ID的线程 3,set scheduler-locking[off|on|step] 设置被调试线程的模式 1,on 表示只有当前被调试的线程会继续执行 2,off 表示不锁定任何线程,其他线程都可以继续执行 3,step 表示在单步执行的时候,只有当前线程会执行
RAM内存 和 ROM内存
RAM 内存 特点与类型: 易失性存储器,断电不保存,如SRAM,DRAM,SDRAM,DDR,LPDDR 不同类型的特质: 1,SRAM: S->static(静态),6个晶体管存储一个bit,不需要周期性补充电源来保持记忆,故为静态。存取速度快,成本高, 主要用于对容量要求低对速度要求高的存储器, 如中央处理器 内建256kb,512kb,1mb的快取存储器,一般都是使用SRAM. 2,DRAM: 1个晶体管和一个电容存储一个bit,使用时需周期性的补充电源来保持记忆的内容,故称为动态。 DRAM构造简单,存取速度慢,成本低。如个人电脑主板使用1GB以上的DDR-SDRAM也属于DRAM的一种。 3,SDRAM: CPU与主机板上的主存储器(SDRAM)存取资料时的工作时脉相同故称为同步,SDRAM的存取速度较DRAM快。 4,DDR-SDRAM:两倍速率的SDRAM。 5,LPDDR:是DDR SDRAM的一种,面向低功耗内存而制定的通信标准,以低功耗和小体积著称,专门用于移动式电子产品。 ROM存储 特点与类型: 非易失性存储器,如ROM,PROM,EPROM, EEPROM,NOR Flash, NAND Flash 各类型特质: 1,ROM: 只读存储器,一次性制造,不可修改。 2,PROM:只允许写入一次,基本也是不可修改 3,EPROM: 可擦写可编程只读存储器,擦除需紫外线照射,非常不方便。 4,EEPROM: 电可擦除可编程只读存储器,直接用电信号即可擦除,编程时间长,有效编程次数低 5,NOR Flash: 基本存储单元为并联,故可以实现位读取,读取速率快,存储密度低,适用于代码存储,不适合存储数据。 6,NAND Flash: 基本存储单元为串联,故减少了金属导线占用的面积,存储密度高,适用于需要大容量存储的场合,不可位读取, 故程序也不可运行,NAND适合存储数据。
多进程与多线程
进程:
进程是系统资源分配的最小单位
父子进程的关系:
1,关于资源,子进程除了代码段和父进程共享以外,其他的就是一个复制过来的副本,二者并不共享地址空间,两个是单独的进程,之后就没太多关系
了,子进程单独运行。
2,关于文件描述符,父子进程共享文件表项,一个进程修改,另一个进程也知道此文件被修改了。
线程与进程之间的关系:
1,一个进程的所有线程都共享该进程获得的资源。
2,各线程有属于自己的一小部分资源,就是栈空间,保存其运行状态和局部自动变量的。
3,在线程中malloc等申请的空间都是占的进程的资源,即堆资源。
多进程通信方式:
1,管道(无名管道,有名管道)
2,信号
3,共享内存
4,消息队列
管道:
1,管道是半双工的,管道允许进程间按先进先出的方式传送数据
2,无名管道只可在有亲缘关系的进程之间通信,有名管道则都可以
3,管道的实质其实是一个内核缓存区,该缓存区可看作是一个循环队列
消息队列:
1,消息队列就是一个消息链表,是一系列保存在内核中消息的列表
2,消息队列对于管道通信的优点就是可以按需要读取特定类型消息,不需要按队列次序
共享内存:
1,共享内存即允许两个或多个进程共享一个存储区,这段存储区进程可以映射至自身地址,大家共享这块存储区域
2,共享内存,效率高,可直接读写内存不需任何数据拷贝,而管道,消息队列需要拷贝4次,共享内存只需2次
信号:
1,信号是Linux系统中用于进程之间通信或操作的一种机制,信号可以在任何时候发送给某一进程
2,信号是软件层次上对中断机制的一种模拟,是一种异步通信方式
3,信号事件来源,硬件来源,软件来源
4,SIGKILL、SEGSTOP应用进程无法捕捉和忽略,这是为了使系统管理员能在任何时候中断或结束某一特定的进程。
线程:
线程是程序执行的最小单位,即CPU调度的基本单位。
多线程同步:
两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。比如 A 任务的运行依赖于 B 任务产生的数据
多线程互斥:
一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。
多进程与多线程的区别及优劣:
1,进程是系统资源分配的最小单位,线程是程序执行的最小单位,即CPU调度的基本单位。
2,进程创建开销大,线程创建开销小
3,进程间通信比较麻烦,线程间通信比较方便
4,多进程程序更加健壮,多线程出现某个线程挂掉导致整个业务挂掉,多进程有独立的地址空间,一个进程挂掉影响可能并不大。
IO多路复用:
select poll epoll
select和poll基本是差不多的,除了select有文件描述符限制(1024个),其他和poll没有太大区别。每次调用都需要将fd集合拷贝到内核态且
遍历所有的fd,开销很大。
epoll在注册新事件时就会把所有fd拷贝到内核,只需拷贝一次即可,epoll是只关心活跃的fd,通过回调机制提高效率。
大端,小端,网络字节序:
网络字节序其实就是大端字节序,为了统一通信标准,都统一转换为相同字节序。
htons,htonl,ntohs,ntohl,主机序与网络字节序的转换函数。
大端字节序:低地址存高位,高地址存地位,更直观。
小端字节序:低地址存低位,高地址存高位,更符合人类思维。
如何判断大端小端字节序:
测试大小端一般使用union的特性。union是一个联合体,所有变量公用一块内存,只是在不同的时候解释不同。
其在内存中存储是按最长的那个变量所需要的位数来开辟内存的。
union test_t {
int32_t num;
char s;
};
int main()
{
test_t test;
test.num = 0x01020304;
if (s == 0x01) {
printf("big\r\n");
} else {
printf("little\r\n");
}
}
指针方法:
int num = 1;
char tmp = *((char *)&num);
if (tmp) 小端
else 大端
优先级翻转问题:
就是高优先级任务要访问共享资源,而此时低优先级任务正在占用,此时中等优先级任务到来先执行完毕后,低优先级任务释放资源高优先级
任务才得以执行,这就发生了优先级翻转问题。
解决方式:
优先级天花板,优先级继承
优先级天花板:
当任务申请某资源时就将当前任务的优先级提高到可访问该资源任务的最高优先级。
优先级继承:
当任务申请某资源时,此资源已被占用,那么判断当前任务是否比占用资源的任务优先级高,若高则将占用资源任务的优先级
提高到当前任务的优先级,等释放资源后再恢复其本身的优先级。
大端,小端,网络字节序:
网络字节序其实就是大端字节序,为了统一通信标准,都统一转换为相同字节序。
htons,htonl,ntohs,ntohl,主机序与网络字节序的转换函数。
大端字节序:低地址存高位,高地址存地位,更直观。
小端字节序:低地址存低位,高地址存高位,更符合人类思维。
如何判断大端小端字节序:
测试大小端一般使用union的特性。union是一个联合体,所有变量公用一块内存,只是在不同的时候解释不同。
其在内存中存储是按最长的那个变量所需要的位数来开辟内存的。
union test_t {
int32_t num;
char s;
};
int main()
{
test_t test;
test.num = 0x01020304;
if (s == 0x01) {
printf("big\r\n");
} else {
printf("little\r\n");
}
}
指针方法:
int num = 1;
char tmp = *((char *)&num);
if (tmp) 小端
else 大端
优先级翻转问题:
就是高优先级任务要访问共享资源,而此时低优先级任务正在占用,此时中等优先级任务到来先执行完毕后,低优先级任务释放资源高优先级
任务才得以执行,这就发生了优先级翻转问题。
解决方式:
优先级天花板,优先级继承
优先级天花板:
当任务申请某资源时就将当前任务的优先级提高到可访问该资源任务的最高优先级。
优先级继承:
当任务申请某资源时,此资源已被占用,那么判断当前任务是否比占用资源的任务优先级高,若高则将占用资源任务的优先级
提高到当前任务的优先级,等释放资源后再恢复其本身的优先级。
HTTP协议
HTTP协议概述:
HTTP是一个基于请求与响应模式的、无状态的、应用层的协议,常基于TCP的连接方式
常用HTTP方法:
1,GET
2,POST
3,PUT
4,HEAD
5,DELETE
6,OPTIONS
GET与POST方法的区别:
1,GET重点在于从服务器获取数据,POST则是发送数据
2,GET传输数据在URL中用户可见,POST用户不可见
3,GET传输数据率小,效率高,POST传输数据量大
4,GET不安全,POST安全性较高
5,GET只可支持ASCLL字符,中文乱码,POST支持标准字符
HTTP请求与响应报文格式:
1,请求报文格式
请求行
请求首部字体
请求内容实体
2,响应报文格式
状态行
响应首部字体
响应内容实体
常见HTTP状态码:
1xx:指示信息--表示请求已接收,继续处理
2xx:成功--表示请求已被成功接收、理解、接受
3xx:重定向--要完成请求必须进行更进一步的操作
4xx:客户端错误--请求有语法错误或请求无法实现
5xx:服务器端错误--服务器未能实现合法的请求
HTTP缺点与HTTPS:
1,通信使用明文,内容可能被窃听
2,不验证通信方身份,可能遭到伪装
3,无法验证报文完整性,可能被篡改
HTTPS就是HTTP加上加密处理(一般是SSL安全通信线路)+认证+完整性保护
NBIOT窄带物联网
NBIOT模块工作模式:
1,Connect(连接态),模块注册入网后处于该状态,可以发送接收数据,无数据交互超过一段时间进入Idle模式,时间可配置
2,Idle(空闲态),可收发数据且收到下行数据会进入connect状态,无数据交互超过一段时间会进入PSM模式,时间可配置
Idle空闲态可配置两种执行模式:
DRX(不连续接收):
周期监听寻呼信息,基本上是可以随时找到设备
eDRX(扩展不连续接收):
周期监听寻呼信息,但周期时间很长20s-2.92h,省电但不能实时找到设备
3,PSM(节能模式),此模式下终端关闭收发信号机,不监听寻呼,因此虽然注册在网络但信令不可达,处于休眠态,功率很小
NBIOT特点:
1,广覆盖,相比现有GSM等网络覆盖增强了20dB,能覆盖到深层地下。
2,大连接,相比现有无线技术,同一基站增多50-100倍的接入数,每小区可以达到50k的连接数,实现万物互联的海量连接
3,低功耗,idle,psm状态功耗都很低
4,低成本,硬件可剪裁,软件按需简化,确保了NB-IoT的成本低廉
5,NBIOT频段,B5和B8,850Mhz-中国电信,900Mhz-移动联通
COAP协议
COAP概述:
COAP是一种在物联网界的类web协议,COAP协议类似http协议,不过由于智能设备资源少,COAP是以二进制格式传输,传输层是UDP,支持
可靠传输,数据重传,块传输,确保数据可靠到达。url地址也类似http地址,非长连接通信。
COAP消息模型:
COAP定义了4种类型消息来实现设备端与云端之间双向通信
1,CON 需要确认消息
2,NON 不需要确认消息
3,ACK 确认应答信息
4,RST 复位信息
COAP头部:
固定4bytes
Ver: 2bit 代表版本信息
T: 2bit 代表消息类型 CON、NON、ACK、RST
TKL: 4bit token长度
Code: 8bit 分为前三位和后五位表示,前3bit表示类型
MsgID 16bit 代表消息MID,每个消息都有一个ID,重发的消息MID不变
COAP安全:
COAP使用DTLS来做安全传输层,该层运行于UDP之上。当前考虑使用DTLS时,需要考虑设备终端资源受限情况,
有些资源有限设备无法运行DTLS安全加密算法
TCP/IP协议族
四层模型:
应用层 传输层 网络层 数据链路层
应用层:
HTTP/FTP等
传输层:
TCP/UDP协议
网络层:
IP协议 ICMP协议
ICMP协议:
ICMP协议其实也算是IP协议的一个补充,ICMP 协议是为了辅助IP 协议,交换各种各样的控制信息而被制造出来的。主要用于
传输出错报告控制信息。
IP协议:
IP协议是无状态,无连接,不可靠的,这也让其相互独立更加便于通信。
IP分片,在IP通信过程中有数据传输大小限制,最多为65535个字节,但是限于MTU限制,可能只有1500个字节,如果大于1500个
字节就要进行数据的分片处理,对于多分片,每次分片都有自己的偏移值,除最后一个外,其它都有MF标志,传到主机后,主机会
根据协议进行数据重组。
TTL(Time to live): 常见设置为64,是数据到达目的地并允许经过的路由器级数,每经过一个路由就减1,若为0就舍弃此消息,防止
路由循环。
IP寻址和路由: IP在收到传输层传过来的数据,首先查看要传输的目的地IP是不是在同一网段,若在就交给数据链路层去做,若不在
就要将数据给到路由,让路由再去按相同方式继续查找,直到最终找到结束。
IP主要作用为寻址和路由,分片和重组。
数据链路层:
ARP协议
ARP协议:
ARP协议主要作用就是通过IP地址找到对应的设备MAC地址,ARP在收到IP老大哥的命令会去ARP缓存表里查看目标地址对应的MAC
地址,若没找到就广播室广播一下,找到对应MAC地址,然后将数据打包好给对应的设备。
TCP IP ARP协议关系可以看这篇文章,通俗易懂,地址:https://www.cnblogs.com/itsad/p/8250503.html
TCP协议:
TCP传输控制协议是一种面向连接的,可靠的,基于字节流的传输层通信协议。
TCP三次握手连接:
1,客户端发送SYN到服务器
2,服务器接收到SYN,发送ACK,SYN到客户端
3,客户端接收到服务端数据,发送确认ACK到服务端,客户端进入ESTABLISHED状态,服务端接收到确认也进入ESTABLISHED状态
TCP连接为何3次不用2次:
主要是为了防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误,如客户端发送一条连接请求因网络问题滞留时间长
并未丢失,而客户端以为服务器没收到,又发了一条,连接建立成功,数据发送完毕后断开,此时那条消息到达服务器,本来是失效
的,但是两次握手的机制就会让客户端服务器再次建立连接,浪费资源。
TCP连接为何3次不用4次:
3次能做到的事,干嘛还要用4次呢,哈哈。
TCP四次挥手断开连接:
1,客户端发出连接释放报文FIN到服务端(客户端FIN_WAIT1)
2,服务端收到客户端发来的连接释放报文,发送确认包到客户端(客户端FIN_WAIT2)
3,服务端将未发送的数据都发送完毕后,发送连接释放报文FIN到客户端.
4,客户端接收到服务端的释放连接报文,发送确认包到服务端,进入TIME_WAIT状态,服务端收到应答立马关闭此连接。
TCP连接三次握手为何断开要四次挥手:
在建立连接时,SYN和ACK可以一同发送到客户端,而断开连接时,只是客户端不再发送数据了,但还可以接收数据,服务端也不一定
所有数据都已经传给对方,所以可以先把数据发完再去发送FIN报文,故就多了一次。
TIME_WAIT状态:
TCP协议规定,主动关闭连接的一方要处于TIME_ WAIT状态,等待2*MSL(maximum segment lifetime)的时间后才能回到CLOSED状态。
1,可靠的终止TCP连接
2,保证让迟来的TCP报文段有足够的时间被识别和丢弃。
等待 2*MSL的作用:
1,保证客户端发送的最后一个ACK报文到达服务器,假如此报文丢失,服务端又发来一个,等待时间内就可以再次将ACK
发送到服务端,关闭连接。
2,防止已经失效的连接请求报文段,客户端发送最后一个确认报文后在这个2MSL时间内所有产生的报文都从网络中消失
如何解决让其不进入TIME_WAIT状态:
使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1, 表示允许创建端口号相同但IP地址不同的多个socket描述符.
流量控制:
TCP支持根据接收端的处理能力, 来决定发送端的发送速度. 这个机制就叫做 流量控制
拥塞控制:(资料参考:https://www.cnblogs.com/losbyday/p/5847041.html)
四种方式:慢启动,拥塞避免,快重传,快恢复。
慢启动和拥塞避免:
在发送数据时不知道当前网络状态,先发送小部分数据试探,如果网络状态良好就指数级增加发送数据,但也不可以无限制
指数增长下去啊,设置一个慢启动阈值,超过这阈值就不指数级增长而是拥塞避免,让其线性增长。
快重传和快恢复:
快重传算法首先要求接收方每收到一个失序的报文段后就立即发出重复确认,快重传算法还规定,发送方只要一连收到三个
重复确认就应当立即重传对方尚未收到的报文段,而不用等待超时再重发。同时不进行慢启动,而是将发送量减半,进行
拥塞避免算法,可以快速恢复原来的传输速率。
UDP协议:
UDP用户数据报协议是一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
UDP协议对比TCP优势:
1,UDP无连接,不存在连接时延和维护连接的开销。
2,UDP没有拥塞控制,实时性好。
3,UDP面向报文,无需考虑粘包等问题。
UDP,广播,多播,单播。
1,字符串翻转
实现逻辑,就是将字符串从中间一分为二,互相换位置即完成了翻转的效果
void rechange_str(char *str)
{
int i, len;
char tmp;
if (NULL == str) {
return ;
}
len = strlen(str);
for (i = 0; i < len/2; i ++) {
tmp = str[i];
str[i] = str[len-i-1];
str[len-i-1] = tmp;
}
}
2,整型转字符串
实现逻辑,每个整数看其转换进制,从个位到十位百位都可以通过%操作加上/操作获得,再用一个字符数组保存0-F,用个位数对应值转为字符,
注意转换出的字符串是反向的,还要考虑传入的若是负数如何处理,再用翻转字符串完成最后整个操作
char *sky_itoa(int value, char *str, unsigned int radix)
{
char list[] = "0123456789ABCDEF";
unsigned int tmp_value;
int i, j, k;
if (NULL == str) {
return NULL;
}
if (2 != radix && 8 != radix && 10 != radix && 16 != radix) {
return NULL;
}
i = 0;
k = 0;
if (radix == 10 && value < 0) {
tmp_value = (unsigned int)(0 - value);
str[i++] = '-';
k = 1;
} else {
tmp_value = (unsigned int)value;
}
do {
str[i++] = list[tmp_value%radix];
tmp_value /= radix;
} while(tmp_value);
str[i] = '\0';
//翻转
char tmp;
for (j = k; j < (i+k)/2; j++) {
tmp = str[j];
str[j] = str[i+k-j-1];
str[i+k-j-1] = tmp;
}
return str;
}
3,字符串复制
实现逻辑,逐个赋值直到遇到'\0'停止即可
char *sky_strcpy(char *dst, const char *str)
{
if (NULL == dst || NULL == str) {
return NULL;
}
char *ret = dst;
while (*str != '\0') {
*dst ++ = *str ++;
}
return ret;
}
4,字符串比较
1)正常比较是否相同
实现逻辑,判断字符串长度是否相同,若相同逐个比较字符是否相同
int sky_strcmp(char *dst, char *str)
{
int i, len;
if (NULL == dst || NULL == str) {
return 0;
}
if (strlen(dst) != strlen(str)) {
return 0;
}
len = strlen(dst);
for (i = 0; i < len; i++) {
if (*dst++ != *str++) {
return 0;
}
}
return 1;
}
2)忽略大小写字符串比较
实现逻辑,在比较字符时可以将其统一转换为大写或小写,然后再进行比对即可,和正常对比无其他不同
#define CONVERT(c) (((c) >= 'A' && (c) <= 'Z') ? ((c) - 'A' + 'a') : (c))
int sky_strcmp(char *dst, char *str)
{
int i, len;
if (NULL == dst || NULL == str) {
return 0;
}
if (strlen(dst) != strlen(str)) {
return 0;
}
len = strlen(dst);
for (i = 0; i < len; i++) {
if (CONVERT(*dst) != CONVERT(*str)) {
return 0;
}
dst ++;
str ++;
}
return 1;
}
5,memcpy函数实现
实现逻辑,主要就是逐个赋值即可完成
1)不考虑拷贝覆盖问题
void *sky_memecpy(void *dst, const void *str, int n)
{
if (NULL == dst || NULL == str || n <= 0) {
return NULL;
}
char *pdst = (char *)dst;
char *pstr = (char *)str;
while (n --) {
*pdst ++ = *pstr ++;
}
return dst;
}
2)考虑拷贝覆盖问题
void *sky_memecpy(void *dst, const void *str, int n)
{
if (NULL == dst || NULL == str || n <= 0) {
return NULL;
}
char *pdst = (char *)dst;
char *pstr = (char *)str;
if (pdst > pstr && pdst < pstr + n) {
pdst = pdst + n - 1;
pstr = pstr + n - 1;
while (n --) {
*pdst -- = *pstr --;
}
} else {
while (n --) {
*pdst ++ = *pstr ++;
}
}
return dst;
}
IIC总线协议
概述:
I2C总线是一种串行数据总线,一根是双向的数据线SDA,一根是时钟线SCL.两条线可以挂多个设备,一般IIC设备里有个固化的地址,只有传输对应
固化地址时才会响应。基本谁控制时钟线谁就是主设备。
IIC总线初始化与起始信号,结束信号:
1,初始化:SCL SDA均为高。
2,开始信号:SCL保持高电平,SDA由高变低,SCL拉低。
3,停止信号:SCL拉高,SDA由低变高。
IIC优缺点:
优点:硬件资源节约,协议设计精巧,易用,使用广泛易移植。
缺点:传输速率较慢。
/*******************************************************************************/
SPI协议
概述:
SPI串行外围设备接口,是Mototrola公司推出的。全双工通信,通信速率高,主要应用在flash,实时时钟RTC,数模转换器ADC等设备上。
主要构造:
SPI在芯片中只占用四根管脚用来控制及数据传输,分别为时钟线SCK,片选信号线CS,数据输入线MISO,数据输出线MOSI.
特点:
1,SPI设备之间通信必须由主设备控制从设备,主设备可通过片选实现控制多个从设备,从设备不能产生或控制时钟,只可主设备提供。
2,SPI是采用同步方式传输数据的。
3,SPI设备之间数据传输又称为数据交换,不管是读数据还是写数据都需要发送数据到从设备为其提供时钟,才能正常进行,在有些设备
读取数据时可能需要先写入数据,此时一定要记得将写入时从设备响应的数据也读出来,后面才可读出对应需要的数据。
时钟极性与时钟相位:
1,时钟极性(CPOL):
表示 SPI 在空闲时, 时钟信号是高电平还是低电平. 若CPOL =1, 设备在空闲时 SCK时钟信号为高电平. CPOL=0, SCK为低电平。
2,时钟相位(CPHA):
表示SPI在采样数据时是在SCK第一个边沿还是第二个边沿,第一个边沿CPHA=0,第二个边沿CPHA=1.
3,如何判断:
SCK的空闲时候的电压,是0还是1,决定了CPOL是0还是1,数据采样时刻对应着的SCK的电平,是第一个边沿还是第二个边沿,
对应着CPHA为0还是1。
优缺点:
1,优点:
支持全双工操作,操作简单,数据传输速率较高。
2,缺点:
需要占用主机较多的口线,只支持单个主机,没有应答机制确认是否接收到数据。
/*******************************************************************************/
UART协议
概述:
通用异步收发传输器,通常称为UART,主要有RS232,RS422,RS485,TTL等。
RS232:
RS-232是为点对点(即只用一对收、发设备)通讯而设计的,适合本地设备之间的通信,全双工。
RS485:
RS485可连接多个设备,传输距离远,抗干扰能力强,半双工。
转载自:https://blog.csdn.net/Dancer__Sky/article/details/88684696