当前位置: 首页 > news >正文

C#使用TCP-S7协议读写西门子PLC(三)

接上篇

C#使用TCP-S7协议读写西门子PLC(二)-CSDN博客

这里我们进行封装读写西门子PLC的S7协议命令以及连接西门子PLC并两次握手

新建部分类文件SiemensS7ProtocolUtil.ReadWrite.cs

主要方法:

连接西门子PLC并发送两次握手。两次握手成功后,才真正连接到PLC

public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000)

生成一个写入字节数据的指令

public static OperateResult<byte[]> BuildWriteByteCommand(OperateResult<byte, int, ushort> analysis, byte[] data)

生成一个读取字数据指令头的通用方法【一个字Word占用两个字节Byte】

public static OperateResult<byte[]> BuildReadCommand(OperateResult<byte, int, ushort>[] address, ushort[] length)

SiemensS7ProtocolUtil.ReadWrite.cs源程序如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;namespace PlcSiemesS7Demo
{/// <summary>/// 西门子S7协议,封装读写命令/// 关键方法:连接PLC并发送两次握手、生成写PLC命令,生成读PLC命令/// (1).public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000)/// (2).public static OperateResult&lt;byte[]&gt; BuildWriteByteCommand(OperateResult&lt;byte, int, ushort&gt; analysis, byte[] data)/// (3).BuildReadCommand(OperateResult&lt;byte, int, ushort&gt;[] address, ushort[] length)/// </summary>public partial class SiemensS7ProtocolUtil{#region 西门子S7协议握手【两次握手】命令,固定.不同型号的PLC握手命令有少许不同private byte[] plcHead1 = new byte[22]{0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,0x00,0x01,0x00,0xC0,0x01,0x0A,0xC1,0x02,0x01,0x02,0xC2,0x02,0x01,0x00};private byte[] plcHead2 = new byte[25]{0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,0x01,0x00,0x00,0x04,0x00,0x00,0x08,0x00,0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x01,0xE0};private byte[] plcHead1_200smart = new byte[22]{0x03,0x00,0x00,0x16,0x11,0xE0,0x00,0x00,0x00,0x01,0x00,0xC1,0x02,0x10,0x00,0xC2,0x02,0x03,0x00,0xC0,0x01,0x0A};private byte[] plcHead2_200smart = new byte[25]{0x03,0x00,0x00,0x19,0x02,0xF0,0x80,0x32,0x01,0x00,0x00,0xCC,0xC1,0x00,0x08,0x00,0x00,0xF0,0x00,0x00,0x01,0x00,0x01,0x03,0xC0};#endregion/// <summary>/// 连接西门子PLC并发送两次握手。两次握手成功后,才真正连接到PLC/// 使用网络终结点和PLC型号枚举进行连接/// </summary>/// <param name="siemensPlcCategory"></param>/// <param name="endPoint"></param>/// <param name="timeout"></param>/// <returns></returns>public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, IPEndPoint endPoint, int timeout = 3000){isConnected = false;this.SiemensPlcCategory = siemensPlcCategory;switch (siemensPlcCategory){case SiemensPlcCategory.S1200:case SiemensPlcCategory.S1500:plcHead1[21] = 0;break;case SiemensPlcCategory.S300:plcHead1[21] = 2;break;case SiemensPlcCategory.S400:plcHead1[21] = 3;plcHead1[17] = 0x00;break;case SiemensPlcCategory.S200Smart:plcHead1 = plcHead1_200smart;plcHead2 = plcHead2_200smart;break;default:plcHead1[18] = 0;break;}// 重新连接之前,先将旧的数据进行清空CoreSocket?.Close();OperateResult<Socket> result = ConnectPlc(endPoint, timeout);if (result.IsSuccess){// 第一次握手 -> First handshakeOperateResult<byte[]> read_first = SendDataAndWaitResult(result.Content, plcHead1);if (!read_first.IsSuccess){RecordLogEvent?.Invoke($"第一次握手出错:{read_first.Message}");return read_first;}// 第二次握手 -> Second handshakeOperateResult<byte[]> read_second = SendDataAndWaitResult(result.Content, plcHead2);if (!read_second.IsSuccess){RecordLogEvent?.Invoke($"第二次握手出错:{read_second.Message}");return read_second;}// 返回成功的信号 CoreSocket = result.Content;result.IsSuccess = true;isConnected = true;RecordLogEvent?.Invoke($"连接PLC【{endPoint}】成功并且两次握手成功");}else{result.Content?.Close();CoreSocket = null;result.IsSuccess = false;}return result;}/// <summary>/// 连接西门子PLC并发送两次握手。两次握手成功后,才真正连接到PLC/// 使用IP地址和端口【默认102】和PLC型号枚举进行连接/// </summary>/// <param name="siemensPlcCategory"></param>/// <param name="ipAddress"></param>/// <param name="port"></param>/// <param name="timeout"></param>/// <returns></returns>public OperateResult ConnectPlcAndHandshake(SiemensPlcCategory siemensPlcCategory, string ipAddress, int port = 102, int timeout = 3000){return ConnectPlcAndHandshake(siemensPlcCategory, new IPEndPoint(IPAddress.Parse(ipAddress), port), timeout);}private OperateResult<byte[]> Read(OperateResult<byte, int, ushort>[] address, ushort[] length){// 构建指令 -> Build read commandOperateResult<byte[]> command = BuildReadCommand(address, length);if (!command.IsSuccess) return command;// 核心交互 -> Core InteractionsOperateResult<byte[]> read = SendDataAndWaitResult(command.Content);if (!read.IsSuccess) return read;// 分析结果 -> Analysis resultsint receiveCount = 0;for (int i = 0; i < length.Length; i++){receiveCount += length[i];}if (read.Content.Length >= 21 && read.Content[20] == length.Length){byte[] buffer = new byte[receiveCount];int kk = 0;int ll = 0;for (int ii = 21; ii < read.Content.Length; ii++){if ((ii + 1) < read.Content.Length){if (read.Content[ii] == 0xFF && read.Content[ii + 1] == 0x04){Array.Copy(read.Content, ii + 4, buffer, ll, length[kk]);ii += length[kk] + 3;ll += length[kk];kk++;}}}return OperateResult.CreateSuccessResult(buffer);}else{return new OperateResult<byte[]>() { ErrorCode = read.ErrorCode, Message = "数据块长度校验失败,请检查是否开启put/get以及关闭db块优化" };}}private OperateResult<byte[]> ReadBitFromPLC(OperateResult<byte, int, ushort> analysis){// 指令生成 -> Build bit read commandOperateResult<byte[]> command = BuildBitReadCommand(analysis);if (!command.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(command);// 核心交互 ->  Core interactiveOperateResult<byte[]> read = SendDataAndWaitResult(command.Content);if (!read.IsSuccess) return read;// 分析结果 -> Analysis read resultint receiveCount = 1;if (read.Content.Length >= 21 && read.Content[20] == 1){byte[] buffer = new byte[receiveCount];if (22 < read.Content.Length){if (read.Content[21] == 0xFF && read.Content[22] == 0x03){buffer[0] = read.Content[25];}}return OperateResult.CreateSuccessResult(buffer);}else{return new OperateResult<byte[]>(read.ErrorCode, "数据块长度校验失败,请检查是否开启put/get以及关闭db块优化");}}/// <summary>/// 基础的写入数据的操作支持 -> Operational support for the underlying write data/// </summary>/// <param name="entireValue">完整的字节数据 -> Full byte data</param>/// <returns>是否写入成功的结果对象 -> Whether to write a successful result object</returns>private OperateResult WriteBase(byte[] entireValue){OperateResult<byte[]> write = SendDataAndWaitResult(entireValue);if (!write.IsSuccess) return write;if (write.Content[write.Content.Length - 1] != 0xFF){// 写入异常 -> WriteErrorreturn new OperateResult(write.Content[write.Content.Length - 1], "写入数据异常,代号为:" + write.Content[write.Content.Length - 1]);}else{return OperateResult.CreateSuccessResult();}}/// <summary>/// 生成一个写入字节数据的指令 -> Generate an instruction to write byte data/// </summary>/// <param name="analysis">内存区域标识,起始地址*8,DB块编号 </param>/// <param name="data">原始的字节数据 -> Raw byte data</param>/// <returns>包含结果对象的报文 -> Message containing the result object</returns>public static OperateResult<byte[]> BuildWriteByteCommand(OperateResult<byte, int, ushort> analysis, byte[] data){byte[] _PLCCommand = new byte[35 + data.Length];_PLCCommand[0] = 0x03;_PLCCommand[1] = 0x00;// 长度 -> Length_PLCCommand[2] = (byte)((35 + data.Length) / 256);_PLCCommand[3] = (byte)((35 + data.Length) % 256);// 固定 -> Fixed_PLCCommand[4] = 0x02;_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;// 命令 发 -> command to send_PLCCommand[8] = 0x01;// 标识序列号 -> Identification serial Number_PLCCommand[9] = 0x00;_PLCCommand[10] = 0x00;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;// 固定 -> Fixed_PLCCommand[13] = 0x00;_PLCCommand[14] = 0x0E;// 写入长度+4 -> Write Length +4_PLCCommand[15] = (byte)((4 + data.Length) / 256);_PLCCommand[16] = (byte)((4 + data.Length) % 256);// 读写指令 -> Read and write instructions_PLCCommand[17] = 0x05;// 写入数据块个数 -> Number of data blocks written_PLCCommand[18] = 0x01;// 固定,返回数据长度 -> Fixed, return data length_PLCCommand[19] = 0x12;_PLCCommand[20] = 0x0A;_PLCCommand[21] = 0x10;// 写入方式,1是按位,2是按字 -> Write mode, 1 is bitwise, 2 is by word_PLCCommand[22] = 0x02;// 写入数据的个数 -> Number of Write Data_PLCCommand[23] = (byte)(data.Length / 256);_PLCCommand[24] = (byte)(data.Length % 256);// DB块编号,如果访问的是DB块的话 -> DB block number, if you are accessing a DB block_PLCCommand[25] = (byte)(analysis.Content3 / 256);_PLCCommand[26] = (byte)(analysis.Content3 % 256);// 写入数据的类型 -> Types of writing data_PLCCommand[27] = analysis.Content1;// 偏移位置 -> Offset position 因65535*8 即0x07 FF F8 偏移地址占用三个字节:下面注释的三行等价于BitConverter.GetBytes(Int32) //_PLCCommand[28] = (byte)(analysis.Content2 / 256 / 256 % 256);//_PLCCommand[29] = (byte)(analysis.Content2 / 256 % 256);//_PLCCommand[30] = (byte)(analysis.Content2 % 256);byte[] offsetAddress = BitConverter.GetBytes(analysis.Content2);_PLCCommand[28] = offsetAddress[2];_PLCCommand[29] = offsetAddress[1];_PLCCommand[30] = offsetAddress[0];// 按字写入 -> Write by Word_PLCCommand[31] = 0x00;_PLCCommand[32] = 0x04;// 按位计算的长度 -> The length of the bitwise calculation_PLCCommand[33] = (byte)(data.Length * 8 / 256);_PLCCommand[34] = (byte)(data.Length * 8 % 256);data.CopyTo(_PLCCommand, 35);return OperateResult.CreateSuccessResult(_PLCCommand);}public static OperateResult<byte[]> BuildWriteBitCommand(OperateResult<byte, int, ushort> analysis, bool data){//OperateResult<byte, int, ushort> analysis = AnalysisAddress(address);if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(analysis);byte[] buffer = new byte[1];buffer[0] = data ? (byte)0x01 : (byte)0x00;byte[] _PLCCommand = new byte[35 + buffer.Length];_PLCCommand[0] = 0x03;_PLCCommand[1] = 0x00;// 长度 -> length_PLCCommand[2] = (byte)((35 + buffer.Length) / 256);_PLCCommand[3] = (byte)((35 + buffer.Length) % 256);// 固定 -> fixed_PLCCommand[4] = 0x02;_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;// 命令 发 -> command to send_PLCCommand[8] = 0x01;// 标识序列号 -> Identification serial Number_PLCCommand[9] = 0x00;_PLCCommand[10] = 0x00;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;// 固定 -> fixed_PLCCommand[13] = 0x00;_PLCCommand[14] = 0x0E;// 写入长度+4 -> Write Length +4_PLCCommand[15] = (byte)((4 + buffer.Length) / 256);_PLCCommand[16] = (byte)((4 + buffer.Length) % 256);// 命令起始符 -> Command start character_PLCCommand[17] = 0x05;// 写入数据块个数 -> Number of data blocks written_PLCCommand[18] = 0x01;_PLCCommand[19] = 0x12;_PLCCommand[20] = 0x0A;_PLCCommand[21] = 0x10;// 写入方式,1是按位,2是按字 -> Write mode, 1 is bitwise, 2 is by word_PLCCommand[22] = 0x01;// 写入数据的个数 -> Number of Write Data_PLCCommand[23] = (byte)(buffer.Length / 256);_PLCCommand[24] = (byte)(buffer.Length % 256);// DB块编号,如果访问的是DB块的话 -> DB block number, if you are accessing a DB block_PLCCommand[25] = (byte)(analysis.Content3 / 256);_PLCCommand[26] = (byte)(analysis.Content3 % 256);// 写入数据的类型 -> Types of writing data_PLCCommand[27] = analysis.Content1;// 偏移位置 -> Offset position_PLCCommand[28] = (byte)(analysis.Content2 / 256 / 256);_PLCCommand[29] = (byte)(analysis.Content2 / 256);_PLCCommand[30] = (byte)(analysis.Content2 % 256);// 按位写入 -> Bitwise Write_PLCCommand[31] = 0x00;_PLCCommand[32] = 0x03;// 按位计算的长度 -> The length of the bitwise calculation_PLCCommand[33] = (byte)(buffer.Length / 256);_PLCCommand[34] = (byte)(buffer.Length % 256);buffer.CopyTo(_PLCCommand, 35);return OperateResult.CreateSuccessResult(_PLCCommand);}/// <summary>/// 解析地址,返回元组【地址类型,起始地址,DB块编号】/// </summary>/// <param name="plcRegisterCategory">寄存器类型</param>/// <param name="offsetAddress">偏移量,偏移地址</param>/// <param name="dbNumber">DB块号</param>/// <param name="bitIndex">位索引0~7</param>/// <returns></returns>private static OperateResult<byte, int, ushort> AnalysisAddress(PlcRegisterCategory plcRegisterCategory, ushort offsetAddress, ushort dbNumber = 0, int bitIndex = 0){//地址类型,起始地址,DB块编号OperateResult<byte, int, ushort> result = new OperateResult<byte, int, ushort>();result.Content3 = 0;switch (plcRegisterCategory){case PlcRegisterCategory.DB:result.Content1 = 0x84;result.Content2 = offsetAddress * 8 + bitIndex;result.Content3 = dbNumber;break;case PlcRegisterCategory.M:result.Content1 = 0x83;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.Input:result.Content1 = 0x81;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.Quit:result.Content1 = 0x82;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.T:result.Content1 = 0x1D;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.C:result.Content1 = 0x1C;result.Content2 = offsetAddress * 8 + bitIndex;break;case PlcRegisterCategory.V:result.Content1 = 0x84;result.Content2 = offsetAddress * 8 + bitIndex;result.Content3 = 1;break;default:result.Message = "输入的类型不支持,请重新输入";result.Content1 = 0;result.Content2 = 0;result.Content3 = 0;return result;}result.IsSuccess = true;return result;}/// <summary>/// 生成一个读取字数据指令头的通用方法【一个字Word占用两个字节Byte】 ->/// A general method for generating a command header to read a Word data/// </summary>/// <param name="address">起始地址,例如M100,I0,Q0,DB2.100 ->/// Start address, such as M100,I0,Q0,DB2.100</param>/// <param name="length">读取数据长度 -> Read Data length</param>/// <returns>包含结果对象的报文 -> Message containing the result object</returns>public static OperateResult<byte[]> BuildReadCommand(OperateResult<byte, int, ushort>[] address, ushort[] length){if (address == null) throw new NullReferenceException("address");if (length == null) throw new NullReferenceException("count");if (address.Length != length.Length) throw new Exception("两个参数的个数不一致");if (length.Length > 19) throw new Exception("读取的数组数量不允许大于19");int readCount = length.Length;byte[] _PLCCommand = new byte[19 + readCount * 12];// ======================================================================================_PLCCommand[0] = 0x03;                                                // 报文头 -> Head_PLCCommand[1] = 0x00;_PLCCommand[2] = (byte)(_PLCCommand.Length / 256);                    // 长度 -> Length_PLCCommand[3] = (byte)(_PLCCommand.Length % 256);_PLCCommand[4] = 0x02;                                                // 固定 -> Fixed_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;                                                // 协议标识 -> Protocol identification_PLCCommand[8] = 0x01;                                                // 命令:发 -> Command: Send_PLCCommand[9] = 0x00;                                                // 冗余标识(保留)-> redundancy identification (reserved): 0x0000;_PLCCommand[10] = 0x00;                                               // protocol data unit reference; it’s increased by request event;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;                                               // 参数命令数据总长度 -> Parameter command Data total length_PLCCommand[13] = (byte)((_PLCCommand.Length - 17) / 256);_PLCCommand[14] = (byte)((_PLCCommand.Length - 17) % 256);_PLCCommand[15] = 0x00;                                               // 读取内部数据时为00,读取CPU型号为Data数据长度 -> Read internal data is 00, read CPU model is data length_PLCCommand[16] = 0x00;// =====================================================================================_PLCCommand[17] = 0x04;                                               // 读写指令,04读,05写 -> Read-write instruction, 04 read, 05 Write_PLCCommand[18] = (byte)readCount;                                    // 读取数据块个数 -> Number of data blocks readfor (int ii = 0; ii < readCount; ii++){//===========================================================================================// 指定有效值类型 -> Specify a valid value type_PLCCommand[19 + ii * 12] = 0x12;// 接下来本次地址访问长度 -> The next time the address access length_PLCCommand[20 + ii * 12] = 0x0A;// 语法标记,ANY -> Syntax tag, any_PLCCommand[21 + ii * 12] = 0x10;// 按字为单位 -> by word_PLCCommand[22 + ii * 12] = 0x02;// 访问数据的个数 -> Number of Access data_PLCCommand[23 + ii * 12] = (byte)(length[ii] / 256);_PLCCommand[24 + ii * 12] = (byte)(length[ii] % 256);// DB块编号,如果访问的是DB块的话 -> DB block number, if you are accessing a DB block_PLCCommand[25 + ii * 12] = (byte)(address[ii].Content3 / 256);_PLCCommand[26 + ii * 12] = (byte)(address[ii].Content3 % 256);// 访问数据类型 -> Accessing data types_PLCCommand[27 + ii * 12] = address[ii].Content1;// 偏移位置 -> Offset position_PLCCommand[28 + ii * 12] = (byte)(address[ii].Content2 / 256 / 256 % 256);_PLCCommand[29 + ii * 12] = (byte)(address[ii].Content2 / 256 % 256);_PLCCommand[30 + ii * 12] = (byte)(address[ii].Content2 % 256);}return OperateResult.CreateSuccessResult(_PLCCommand);}/// <summary>/// 生成一个位读取数据指令头的通用方法 ->/// A general method for generating a bit-read-Data instruction header/// </summary>/// <param name="address">起始地址,例如M100.0,I0.1,Q0.1,DB2.100.2 ->/// Start address, such as M100.0,I0.1,Q0.1,DB2.100.2/// </param>/// <returns>包含结果对象的报文 -> Message containing the result object</returns>public static OperateResult<byte[]> BuildBitReadCommand(OperateResult<byte, int, ushort> analysis){//OperateResult<byte, int, ushort> analysis = AnalysisAddress(address);if (!analysis.IsSuccess) return OperateResult.CreateFailedResult<byte[]>(analysis);byte[] _PLCCommand = new byte[31];_PLCCommand[0] = 0x03;_PLCCommand[1] = 0x00;// 长度 -> Length_PLCCommand[2] = (byte)(_PLCCommand.Length / 256);_PLCCommand[3] = (byte)(_PLCCommand.Length % 256);// 固定 -> Fixed_PLCCommand[4] = 0x02;_PLCCommand[5] = 0xF0;_PLCCommand[6] = 0x80;_PLCCommand[7] = 0x32;// 命令:发 -> command to send_PLCCommand[8] = 0x01;// 标识序列号_PLCCommand[9] = 0x00;_PLCCommand[10] = 0x00;_PLCCommand[11] = 0x00;_PLCCommand[12] = 0x01;// 命令数据总长度 -> Identification serial Number_PLCCommand[13] = (byte)((_PLCCommand.Length - 17) / 256);_PLCCommand[14] = (byte)((_PLCCommand.Length - 17) % 256);_PLCCommand[15] = 0x00;_PLCCommand[16] = 0x00;// 命令起始符 -> Command start character_PLCCommand[17] = 0x04;// 读取数据块个数 -> Number of data blocks read_PLCCommand[18] = 0x01;//===========================================================================================// 读取地址的前缀 -> Read the prefix of the address_PLCCommand[19] = 0x12;_PLCCommand[20] = 0x0A;_PLCCommand[21] = 0x10;// 读取的数据时位 -> Data read-time bit_PLCCommand[22] = 0x01;// 访问数据的个数 -> Number of Access data_PLCCommand[23] = 0x00;_PLCCommand[24] = 0x01;// DB块编号,如果访问的是DB块的话 -> DB block number, if you are accessing a DB block_PLCCommand[25] = (byte)(analysis.Content3 / 256);_PLCCommand[26] = (byte)(analysis.Content3 % 256);// 访问数据类型 -> Types of reading data_PLCCommand[27] = analysis.Content1;// 偏移位置 -> Offset position_PLCCommand[28] = (byte)(analysis.Content2 / 256 / 256 % 256);_PLCCommand[29] = (byte)(analysis.Content2 / 256 % 256);_PLCCommand[30] = (byte)(analysis.Content2 % 256);return OperateResult.CreateSuccessResult(_PLCCommand);}}
}

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 2024 年 8 月区块链游戏研报:用户增长与加密货币市场波动并存
  • 队列-数据结构
  • django学习入门系列之第十点《A 案例: 员工管理系统5》
  • 嵌入式音视频开发:探索多领域的融合与创新
  • 跟李沐学AI:长短期记忆网络LSTM
  • 个人用户如何有效利用固态硬盘数据恢复工具
  • 开源Devops工具-Ansible
  • 【Python】05.Python 中的列表与元组
  • Oracle(120)如何创建和管理备份策略?
  • Flutter 中的低功耗蓝牙概述
  • 【C#生态园】从正则表达式到Excel操作:全面解析这六款C#库的核心功能和应用
  • NISP 一级 | 3.4 无线局域网安全防护
  • 使用C++11的`std::future`和`std::promise`实现异步网络通信
  • 14_L3缓存友好的数据结构
  • windows服务管理插件 nssm
  • 【技术性】Search知识
  • Docker容器管理
  • ES6 ...操作符
  • Java 9 被无情抛弃,Java 8 直接升级到 Java 10!!
  • javascript 总结(常用工具类的封装)
  • Java编程基础24——递归练习
  • Java到底能干嘛?
  • k8s如何管理Pod
  • log4j2输出到kafka
  • Python_OOP
  • Redis中的lru算法实现
  • SegmentFault 2015 Top Rank
  • VuePress 静态网站生成
  • vue--为什么data属性必须是一个函数
  • Webpack入门之遇到的那些坑,系列示例Demo
  • 搞机器学习要哪些技能
  • 猴子数据域名防封接口降低小说被封的风险
  • 回顾 Swift 多平台移植进度 #2
  • 紧急通知:《观止-微软》请在经管柜购买!
  • 通过获取异步加载JS文件进度实现一个canvas环形loading图
  • 写给高年级小学生看的《Bash 指南》
  • 怎么把视频里的音乐提取出来
  • JavaScript 新语法详解:Class 的私有属性与私有方法 ...
  • 积累各种好的链接
  • ​ 轻量应用服务器:亚马逊云科技打造全球领先的云计算解决方案
  • ​无人机石油管道巡检方案新亮点:灵活准确又高效
  • ​学习一下,什么是预包装食品?​
  • #gStore-weekly | gStore最新版本1.0之三角形计数函数的使用
  • (07)Hive——窗口函数详解
  • (10)工业界推荐系统-小红书推荐场景及内部实践【排序模型的特征】
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (2)leetcode 234.回文链表 141.环形链表
  • (NSDate) 时间 (time )比较
  • (附源码)计算机毕业设计SSM保险客户管理系统
  • (附源码)计算机毕业设计SSM基于java的云顶博客系统
  • (含笔试题)深度解析数据在内存中的存储
  • (介绍与使用)物联网NodeMCUESP8266(ESP-12F)连接新版onenet mqtt协议实现上传数据(温湿度)和下发指令(控制LED灯)
  • (蓝桥杯每日一题)平方末尾及补充(常用的字符串函数功能)
  • (免费领源码)Python#MySQL图书馆管理系统071718-计算机毕业设计项目选题推荐
  • (四)c52学习之旅-流水LED灯