RFID实践——NET IoT程序读取高频RFID卡或者标签
这篇文章是一份RFID实践的保姆级教程,将详细介绍如何用 Raspberry Pi 连接 PN5180 模块,并开发 .NET IoT 程序读写ISO14443 和 ISO15693协议的卡/标签。
设备清单
- Raspberry Pi必需套件(主板、电源、TF卡)
- PN5180
- ISO15693标签
- 杜邦线
- 面包板 ( 可选)
- GPIO扩展板 (可选 )
本文中用到的树莓派型号是 Raspberry Pi Zero 2 W,电源直接使用充电宝替代(官方电源是5.1V / 2.5A DC)。
[[RFID基础——高频RFID协议、读写模块和标签]]中介绍过 PN5180 是一款价格便宜且支持全部高频 RFID 协议的读写模块。网购 PN5180 模块时通常会送一张ICODE SLIX卡和 Mifare S50 卡。
ISO15693标签选用的是国产的复旦微电子芯片的标签。额外购买是为了测试多张标签同时在射频场中防碰撞功能。
杜邦线用于连接 Raspberry Pi Zero 2 W 和 PN5180 模块。GPIO扩展板会标注逻辑引脚,配合面包板使用,方便连接多种传感器。
树莓派连接 PN5180
在 PN5180 上,您会注意到它上有13个引脚,这些引脚中只有9个是需要连接到树莓派的GPIO引脚。对应关系如下表所示。表中特意标注出逻辑引脚和物理引脚,是因为后边程序中需要设置引脚编号。详情在后续代码部分会进行解释。
NXP5180 | 逻辑引脚 | 物理引脚 |
---|---|---|
+5V | 5V | 2 |
+3.3V | 3V3 | 1 |
RST | GPIO4(GPCLK0) | 7 |
NSS | GPIO3(SCL) | 5 |
MOSI | GPIO10(SPI0-MOSI) | 19 |
MISO | GPIO9(SPI0-MISO) | 21 |
SCK | GPIO11(SPI0-SCLK) | 23 |
BUSY | GPIO2(SDA) | 3 |
GND | GND | 9 |
GPIO | - | |
IRQ | - | |
AUX | - | |
REQ | - |
下图灰色区域中 1~40 是物理引脚编号,两侧标注的是逻辑引脚,例如物理引脚编号3的标注是 GPIO2 ,也就是对应的逻辑引脚编号为2。
到了这里,准备工作已经完成,接下来就是编码了。
编写.NET IoT程序
.NET IoT库中已经实现了 PN5180 的部分功能。比如轮询 ISO14443-A 和 ISO14443-B 类型的卡,以及对它们的读写操作,但是没有实现对 ISO15693 协议卡的支持。
PN5180通过SPI和GPIO进行工作,它以特定的方式通过GPIO使用SPI进行通信。这就需要手动管理SPI的引脚选择,Busy
引脚用于了解 PN5180 什么时候可以接收和发送信息。
首先,引用- System.Device.Gpio和 Iot.Device.Bindings两个包,然后用下面的代码创建SPI驱动程序、重置 PN5180 和创建 PN5180 实例。
var spi = SpiDevice.Create(new SpiConnectionSettings(0, 1) { ClockFrequency = Pn5180.MaximumSpiClockFrequency, Mode = Pn5180.DefaultSpiMode, DataFlow = DataFlow.MsbFirst });
// Reset the device
var gpioController = new GpioController();
gpioController.OpenPin(4, PinMode.Output);
gpioController.Write(4, PinValue.Low);
Thread.Sleep(10);
gpioController.Write(4, PinValue.High);
Thread.Sleep(10);
var pn5180 = new Pn5180(spi, 2, 3);
第1行代码创建 SpiDevice
实例,其中设置 DataFlow = DataFlow.MsbFirst
,即首先发送最高有效位。需要注意的是,这里指的是主机与 PN5180 模块之间的 SPI 总线的传输顺序,[[RFID基础——ISO15693标签存储结构及访问控制命令说明]]中协议中首先传输最低有效位指的是 VCD 与 VICC 之间的射频通信,两者是不同的数据传输过程。
第4行代码创建 GpioController
实例,GpioController 类 的无参构造函数使用逻辑引脚编号方案作为默认方案。
第5行代码开启编号4的引脚,这个编号也就是指的逻辑引脚编号 GPIO4。
第11行创建 PN5180 读写器实例。构造函数定义如下:
public Pn5180 (System.Device.Spi.SpiDevice spiDevice, int pinBusy, int pinNss, System.Device.Gpio.GpioController? gpioController = default, bool shouldDispose = true);
第一个参数是 spi 设备实例,第二个参数是 Busy
引脚编号,第三个参数是 Nss
引脚编号,这里都是指的逻辑编号。代码中的参数需和前面引脚对应表中指定的一致。
访问ISO14443协议卡
访问ISO14443协议卡比较简单,调用 ListenToCardIso14443TypeA
, ListenToCardIso14443TypeB
轮询射频场中的 PICC,然后选中卡进行操作,下边是监听 ISO14443-A 和 ISO14443-B 类型卡的示例代码:
do
{
if (pn5180.ListenToCardIso14443TypeA(TransmitterRadioFrequencyConfiguration.Iso14443A_Nfc_PI_106_106, ReceiverRadioFrequencyConfiguration.Iso14443A_Nfc_PI_106_106, out Data106kbpsTypeA? cardTypeA, 1000))
{
Console.WriteLine($"ISO 14443 Type A found:");
Console.WriteLine($" ATQA: {cardTypeA.Atqa}");
Console.WriteLine($" SAK: {cardTypeA.Sak}");
Console.WriteLine($" UID: {BitConverter.ToString(cardTypeA.NfcId)}");
}
else
{
Console.WriteLine($"{nameof(cardTypeA)} is not configured correctly.");
}
if (pn5180.ListenToCardIso14443TypeB(TransmitterRadioFrequencyConfiguration.Iso14443B_106, ReceiverRadioFrequencyConfiguration.Iso14443B_106, out Data106kbpsTypeB? card, 1000))
{
Console.WriteLine($"ISO 14443 Type B found:");
Console.WriteLine($" Target number: {card.TargetNumber}");
Console.WriteLine($" App data: {BitConverter.ToString(card.ApplicationData)}");
Console.WriteLine($" App type: {card.ApplicationType}");
Console.WriteLine($" UID: {BitConverter.ToString(card.NfcId)}");
Console.WriteLine($" Bit rates: {card.BitRates}");
Console.WriteLine($" Cid support: {card.CidSupported}");
Console.WriteLine($" Command: {card.Command}");
Console.WriteLine($" Frame timing: {card.FrameWaitingTime}");
Console.WriteLine($" Iso 14443-4 compliance: {card.ISO14443_4Compliance}");
Console.WriteLine($" Max frame size: {card.MaxFrameSize}");
Console.WriteLine($" Nad support: {card.NadSupported}");
}
else
{
Console.WriteLine($"{nameof(card)} is not configured correctly.");
}
}
while (!Console.KeyAvailable);
有关 ISO14443协议的更多操作可以查看Iot.Device.Bindings
中 PN5180 的文档iot/src/devices/Pn5180 at main · dotnet/iot。
访问ISO15693协议卡
由于Iot.Device.Bindings
中的 PN5180 并没有实现对 ISO15693协议的支持,因此需要自行实现这部分功能。
PN5180 模块的工作原理可以简单的理解为主机向 PN5180 模块发送开启、配置射频场、操作卡/标签(VICC)的命令,PN5180 模块接收到操作卡/标签(VICC)的命令时,通过射频信号与卡/标签(VICC)进行数据交互。寻卡过程的步骤如下:
- 加载ISO 15693协议到RF寄存器
- 开启射频场
- 清除中断寄存器IRQ_STATUS
- 把PN5180设置为IDLE状态
- 激活收发程序
- 向卡/标签(VICC)发送16时隙防冲突的寻卡指令
- 循环16次以下操作
- 读取RX_STATUS寄存器,判断是否有卡/标签响应
- 如果有响应,发送读卡指令然后读取卡的响应
- 在下一次射频通信中只发送EOF(帧结束)而不发送数据。
- 把PN5180设置为IDLE状态
- 激活收发程序
- 清除中断寄存器IRQ_STATUS
- 向卡/标签(VICC)发送EOF(帧结束)
- 关闭射频场
上述步骤中只有步骤6向卡/标签(VICC)发送16时隙防冲突的寻卡指令
和步骤7.7向卡/标签(VICC)发送EOF(帧结束)
是 PN5180 和卡/标签(VICC)之间的数据交互,其余的步骤都是PN5180 与主机之间通过SPI通信。
PN5180与主机通信
PN5180设计了24个主机接口命令,涉及读写寄存器、读写EEPROM、写数据到发送缓冲区,从接收缓冲区读数据,加载RF配置到寄存器,开启关闭射频场。包含44个寄存器,它们控制着PN5180处理器的行为。每个寄存器占4个字节。主机处理器可以通过4个不同的命令改变寄存器的值:write_register
、 write_register_and_mask
、 write_register_or_mask
、write_register_multiple
。
write_register
这个命令将一个32位的值写入配置寄存器。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x00 |
参数 | 1 | 寄存器地址 |
参数 | 4 | 寄存器内容 |
WRITE_REGISTER_OR_MASK
该命令使用逻辑或操作修改寄存器的内容。先读取寄存器的内容,并使用提供的掩码执行逻辑或操作,然后把修改后的内容写回寄存器。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x01 |
参数 | 1 | 寄存器地址 |
参数 | 4 | 逻辑或操作的掩码 |
WRITE_REGISTER_AND_MASK
该命令使用逻辑与操作修改寄存器的内容。先读取寄存器的内容,并使用提供的掩码执行逻辑与操作,然后把修改后的内容写回寄存器。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x02 |
参数 | 1 | 寄存器地址 |
参数 | 4 | 逻辑与操作的掩码 |
LOAD_RF_CONFIG
该命令用于将射频配置从EEPROM加载到配置寄存器中。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x11 |
参数 | 1 | 发送器配置的值 |
写入的数据 | 1 | 接收机配置的值 |
RF_ON
该命令打开内部射频场。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x16 |
参数 | 1 | 1,根据 ISO/IEC 18092 禁用冲突避免 |
RF_OFF
该命令关闭内部射频场
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x17 |
参数 | 1 | 虚字节 |
PN5180和卡/标签(VICC)数据交互
PN5180和卡/标签(VICC)数据交互本质上也是主机发送命令给 PN5180 模块,然后 PN5180 把数据写入缓冲区,接着射频传输给卡/标签(VICC),卡/标签(VICC)响应后通过射频传出给 PN5180 模块的接收缓冲区,主机发送命令读取缓冲区数据。
SEND_DATA
该命令将数据写入射频传输缓冲区,开始射频传输。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x09 |
参数 | 1 | 最后一个字节的有效位数 |
写入的数据 | 1~260 | 最大长度为260的数组 |
最后一个字节的有效位数为0表示最后一字节所有的bit都被传输,1~7表示要传输的最后一个字节内的位数。
READ_DATA
从VICC成功接收数据后,该命令从射频接收缓冲区读取数据。
负载 | 长度 | 值/描述 |
---|---|---|
命令编码 | 1 | 0x0A |
参数 | 1 | 0x00 |
读取的数据 | 1~508 | 最大长度为508的数组 |
代码实现轮询ISO15693卡
PN5180 和卡/标签(VICC)之间的数据交互都是遵循[[RFID基础——ISO15693标签存储结构及访问控制命令说明]]中的命令。只需用代码实现 PN5180 的主机接口指令以及ISO15693的访问控制命令即可。首先Fork dotnet/iot版本库,然后在 Pn5180.cs
中加入以下监听 ISO15693 协议卡的代码:
/// <summary>
/// Listen to 15693 cards with 16 slots
/// </summary>
/// <param name="transmitter">The transmitter configuration, should be compatible with 15693 card</param>
/// <param name="receiver">The receiver configuration, should be compatible with 15693 card</param>
/// <param name="cards">The 15693 cards once detected</param>
/// <param name="timeoutPollingMilliseconds">The time to poll the card in milliseconds. Card detection will stop once the detection time will be over</param>
/// <returns>True if a 15693 card has been detected</returns>
public bool ListenToCardIso15693(TransmitterRadioFrequencyConfiguration transmitter, ReceiverRadioFrequencyConfiguration receiver,
#if NET5_0_OR_GREATER
[NotNullWhen(true)]
#endif
out IList<Data26_53kbps>? cards, int timeoutPollingMilliseconds)
{
cards = new List<Data26_53kbps>();
var ret = LoadRadioFrequencyConfiguration(transmitter, receiver);
// Switch on the radio frequence field and check it
ret &= SetRadioFrequency(true);
Span<byte> inventoryResponse = stackalloc byte[10];
Span<byte> dsfid = stackalloc byte[1];
Span<byte> uid = stackalloc byte[8];
int numBytes = 0;
DateTime dtTimeout = DateTime.Now.AddMilliseconds(timeoutPollingMilliseconds);
try
{
// Clears all interrupt
SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 });
// Sets the PN5180 into IDLE state
SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF });
// Activates TRANSCEIVE routine
SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 });
// Sends an inventory command with 16 slots
ret = SendDataToCard(new byte[] { 0x06, 0x01, 0x00 });
if (dtTimeout < DateTime.Now)
{
return false;
}
for (byte slotCounter = 0; slotCounter < 16; slotCounter++)
{
(numBytes, _) = GetNumberOfBytesReceivedAndValidBits();
if (numBytes > 0)
{
ret &= ReadDataFromCard(inventoryResponse, inventoryResponse.Length);
if (ret)
{
cards.Add(new Data26_53kbps(slotCounter, 0, 0, inventoryResponse[1], inventoryResponse.Slice(2, 8).ToArray()));
}
}
// Send only EOF (End of Frame) without data at the next RF communication
SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.TX_CONFIG, new byte[] { 0x3F, 0xFB, 0xFF, 0xFF });
// Sets the PN5180 into IDLE state
SpiWriteRegister(Command.WRITE_REGISTER_AND_MASK, Register.SYSTEM_CONFIG, new byte[] { 0xF8, 0xFF, 0xFF, 0xFF });
// Activates TRANSCEIVE routine
SpiWriteRegister(Command.WRITE_REGISTER_OR_MASK, Register.SYSTEM_CONFIG, new byte[] { 0x03, 0x00, 0x00, 0x00 });
// Clears the interrupt register IRQ_STATUS
SpiWriteRegister(Command.WRITE_REGISTER, Register.IRQ_CLEAR, new byte[] { 0xFF, 0xFF, 0x0F, 0x00 });
// Send EOF
SendDataToCard(new Span<byte> { });
}
if (cards.Count > 0)
{
return true;
}
else
{
return false;
}
}
catch (TimeoutException)
{
return false;
}
}
需要注意的是,寻卡指令SendDataToCard(new byte[] { 0x06, 0x01, 0x00 })
发送的数据只有请求标志、命令、掩码长度,并没有CRC校验码,我推测是 PN5180 m模块内部进行了CRC校验,目前并没有找到相关资料证实这个猜测。同样,用 PN5180 读写标签数据块以及其他访问控制指令也不需要CRC校验码。
读写ISO15693协议卡
由于支持 ISO15693 协议的读写器不只是 PN5180 ,因此把对 ISO15693 协议卡的具体读写操作放在 PN5180 的实现类中不太合适。这里定义了一个 IcodeCard
的类型,该类实现了 ISO15693 协议中常用的命令,并在构造函数中注入 RFID 读写器。执行指定操作时,调用 RFID 读写器的 Transceive
方法传输请求指令并接收响应进行处理。以下是主要代码:
public class IcodeCard
{
public IcodeCard(CardTransceiver rfid, byte target)
{
_rfid = rfid;
Target = target;
_logger = this.GetCurrentClassLogger();
}
/// <summary>
/// Run the last setup command. In case of reading bytes, they are automatically pushed into the Data property
/// </summary>
/// <returns>-1 if the process fails otherwise the number of bytes read</returns>
private int RunIcodeCardCommand()
{
byte[] requestData = Serialize();
byte[] dataOut = new byte[_responseSize];
var ret = _rfid.Transceive(Target, requestData, dataOut.AsSpan(), NfcProtocol.Iso15693);
_logger.LogDebug($"{nameof(RunIcodeCardCommand)}: {_command}, Target: {Target}, Data: {BitConverter.ToString(requestData)}, Success: {ret}, Dataout: {BitConverter.ToString(dataOut)}");
if (ret > 0)
{
Data = dataOut;
}
return ret;
}
/// <summary>
/// Serialize request data according to the protocol
/// Request format: SOF, Flags, Command code, Parameters (opt.), Data (opt.), CRC16, EOF
/// </summary>
/// <returns>The serialized bits</returns>
private byte[] Serialize()
{
byte[]? ser = null;
switch (_command)
{
case IcodeCardCommand.ReadSingleBlock:
// Flags(1 byte), Command code(1 byte), UID(8 byte), BlockNumber(1 byte)
ser = new byte[2 + 8 + 1];
ser[0] = 0x22;
ser[1] = (byte)_command;
ser[10] = BlockNumber;
Uid?.CopyTo(ser, 2);
_responseSize = 5;
return ser;
// 略去代码....
default:
return new byte[0];
}
}
/// <summary>
/// Perform a read and place the result into the 4 bytes Data property on a specific block
/// </summary>
/// <param name="block">The block number to read</param>
/// <returns>True if success. This only means whether the communication between VCD and VICC is successful or not </returns>
public bool ReadSingleBlock(byte block)
{
BlockNumber = block;
_command = IcodeCardCommand.ReadSingleBlock;
var ret = RunIcodeCardCommand();
return ret >= 0;
}
}
只需以下代码就可以监听射频场中的 ISO15693 类型的卡并进行读写操作:
if (pn5180.ListenToCardIso15693(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26, out IList<Data26_53kbps>? cards, 20000))
{
pn5180.ResetPN5180Configuration(TransmitterRadioFrequencyConfiguration.Iso15693_ASK100_26, ReceiverRadioFrequencyConfiguration.Iso15693_26);
foreach (Data26_53kbps card in cards)
{
Console.WriteLine($"Target number: {card.TargetNumber}");
Console.WriteLine($"UID: {BitConverter.ToString(card.NfcId)}");
Console.WriteLine($"DSFID: {card.Dsfid}");
if (card.NfcId[6] == 0x04)
{
IcodeCard icodeCard = new IcodeCard(pn5180, card.TargetNumber)
{
Afi = 1,
Dsfid= 1,
Uid = card.NfcId,
Capacity = IcodeCardCapacity.IcodeSlix,
};
for (byte i = 0; i < 28; i++)
{
if (icodeCard.ReadSingleBlock(i))
{
Console.WriteLine($"Block {i} data is :{BitConverter.ToString(icodeCard.Data)}");
}
else
{
icodeCard.Data = new byte[] { };
}
}
}
else
{
Console.WriteLine("Only Icode cards are supported");
}
}
}
最后,就是把程序部署到 Raspberry pi 上,具体操作可以参照 Raspberry pi 上部署调试.Net的IoT程序。
RFID实践——NET IoT程序读取高频RFID卡或者标签的更多相关文章
- 【ALB学习笔记】基于.NET环境的高频RFID卡读写设备的基本操作案例
基于.NET环境的高频RFID卡读写设备的基本操作案例 广东职业技术学院 欧浩源 1.引言 RFID高频卡在我们的日常生活中随处可见,是物联网应用中不可或缺的一个重要部分,也是全国职业院校技能大赛& ...
- [渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序读取相关数据
这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第七篇:为ASP.NET MVC应用程序 ...
- 几种C#程序读取MAC地址的方法
原文:几种C#程序读取MAC地址的方法 以下是收集的几种C#程序读取MAC地址的方法,示例中是读取所有网卡的MAC地址,如果仅需要读取其中一个,稍作修改即可. 1 通过IPConfig命令读取MAC地 ...
- ASP.NET程序读取二代身份证(附源码)
原文:ASP.NET程序读取二代身份证(附源码) 一般来说winform应用程序解决这个问题起来时很容易的,web应用程序就麻烦一点了. 这里我说说我的解决思路: 一.你必要有联机型居民身份证阅读器一 ...
- 为ASP.NET MVC应用程序读取相关数据
为ASP.NET MVC应用程序读取相关数据 2014-05-08 18:24 by Bce, 299 阅读, 0 评论, 收藏, 编辑 这是微软官方教程Getting Started with En ...
- winform程序读取和改写配置文件App.config元素的值
winform程序读取和改写配置文件App.config元素的值 2016-05-16 17:49 by newbirth, 2412 阅读, 0 评论, 收藏, 编辑 1 2 3 4 5 6 7 & ...
- UTF-8格式的文本文件程序读取异常
最近在windows服务器上直接创建并手打输入配置参数,比如设置概率0.6,然后用java程序打开读取该参数,在本地linux环境下测试完全正常,但是一放到服务器上,就报NotNumber错误,查看了 ...
- 18个Java8日期处理的实践,对于程序员太有用了!
18个Java8日期处理的实践,对于程序员太有用了! Java 8 推出了全新的日期时间API,在教程中我们将通过一些简单的实例来学习如何使用新API. Java处理日期.日历和时间的方式一直为社区所 ...
- 基于.NET的程序读取Excel文件的解决方案
目录 0. 前言 1. 使用NPOI库读取Excel文件 2. 使用OleDbConnection 3. 相关参考 shanzm-2020年12月8日 23:48:11 0. 前言 以前基于 .NET ...
- Android 读取手机SD卡根目录下某个txt文件的文件内容
1.先看activity_main.xml文件: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/and ...
随机推荐
- 解析HTML字符串成AST树
1. 如何将一个字符传转换成一个AST树结构. 直接上代码: const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:" ...
- 怎么实时更新echarts图标数据?
function getData(){ var request . nem XPHLHttpRequest () ; request . open("get",'http://lo ...
- [AGC029D] Grid game题解
这题很显然可以用贪心来解. 由于先手不动一定会让局数更少,所以先手要能动就动. 而后手一定是希望他的石子可以撞到一个障碍物上,这样先手就无法移动了,后手就可以让局数更少. 因为先手一定会能动就动,所以 ...
- 【Python基础练习】实验3:列表、字典、集合
实验3:列表.字典.集合 姓名:萌狼蓝天 时间:2023年11月6日 Python:3.12 博客:https://wwww.mllt.cc 实验目的 (1)了解列表.元组.字典和集合的概念 (2)学 ...
- Python+Selenium自动搜索基金业协会指定企业名单,爬虫抓取指定信息并保存到数据库
Python+Selenium自动搜索基金业协会指定企业名单,抓取指定信息并保存到数据库.网址https://gs.amac.org.cn/amac-infodisc/res/pof/manager/ ...
- Qt Creator 5.0 发布
我们很高兴地宣布 Qt Creator 5.0 的发布! 正如4.15 发布博文中所宣布的,我们将切换到语义版本控制方案,因此这是 Qt Creator 很长一段时间以来的第一次主要版本更新!不过不要 ...
- vue.js中vue.config.js的配置说明
如果你的项目没有vue.config.js,请在根目录新建一个. vue.config.js里面的代码如下: module.exports = { /** 区分打包环境与开发环境 * process. ...
- DVWA靶场Insecure CAPTCHA(不安全验证)漏洞所有级别通关教程及源码审计
Insecure CAPTCHA(不安全验证) Insecure CAPTCHA(不安全验证)漏洞指的是在实现 CAPTCHA(完全自动化公共图灵测试区分计算机和人类)机制时,未能有效保护用户输入的验 ...
- 零基础Windows Server搭建部署Word Press 博客系列教程(1):从萌新到菜鸡之云主机配置与备案
不知道这个教程能帮助到多少想要长期建站的新手朋友. 下面进入正题.如果你想搭建一个基于wordpress的个人博客或者网站,但是不懂Linux,也没有命令行的操作经验,更不懂复杂的代码,那么找这篇文章 ...
- python SQLAlchemy ORM——从零开始学习 01 安装库
01基础库 1-1安装 依赖库:sqlalchemy pip install sqlalchemy #直接安装即可 1-2导入使用 这里讲解思路[个人的理解],具体写其实就是这个框架: 导入必要的接口 ...