再次是一篇入门文,各路神仙退散。

直接进入主题,又不是历史课,关于RS232那些前世今生的故事就不摆了。

硬件链接

首先以9针小口为例(大口应当只能去博物馆看了吧)看一下管脚排布,其实RS232本身没进博物馆都已经够让我惊讶了。



(图片来自互联网)

通常使用的接线图:



(图片来自互联网)

硬件接口部分的重点:

  • 绝大多数情况下,我们只需要接2号、3号、5号,RXD/TXD/SG三根线就能正常工作。(顺便多说一句,古老的大串口是2、3、7号)
  • 直连模式一般用于延长线或者大小口的转换线。
  • 交叉线是用于连接电脑之间、电脑与设备之间,是最主要的应用方式。
  • 通常三根线就能工作,但并不表示其它信号就没用,甚至像某些书上说的都没有定义。

硬件支持

当前我们常用的电脑,在台式机上一般都会有串口,可以直接使用。

绝大多数的笔记本电脑都已经没有了串口,想使用串口通常都是使用USB接口的适配器。顺便说一句,USB实际也是另外一种串口,SATA也是,只是未成文的约定俗称上,串口特指了RS232接口或者485接口。

USB适配器通常也分两种,一种是内置于外置设备中的适配器,比如外置GPS模块、烧录机。另外一种则是仅有串口功能的独立适配器,今天的实验中我们会使用后者。

驱动程序

本身主板已经具有的串口都已经有了良好的设备驱动,鲜见不可用者。

USB外置的串口则绝大多数都需要另外安装驱动,Windows/Linux/macOS都是如此,依据适配器的芯片不同,所使用的驱动也不一样。这个在采购的时候就需要了解好。比如我测试的这款是PL2302芯片,使用win10内置的微软2017版驱动(不不不,不是你想的那样免驱动,继续看)。

因为串口无论如何算是一个比较有历史的技术,所以在x64的系统中大多支持不好,PL2302为例,在win10x64系统中会自动识别并安装驱动,但驱动安装完成仍然会有一个叹号表示设备不能正常工作,错误代码10。

搜索互联网能找到第三方提供的补丁,原厂商已经发布通知说PL2302已经停止支持了。补丁程序安装后运行还会先下载.net的运行时间库,随后才能完成驱动的补丁工作。

此仅为举例,不同的适配器,需要的驱动、安装方式都不会一样。

实验环境准备

串口作为通讯设备,实验需要发送、接受两个端。所以最好的实验方法是一台电脑上,用两个串口,一个模拟接收,一个模拟发送。当然如果你不缺电脑、不缺空间、不缺时间,使用两台电脑看上去肯定会更高大上。

各类操作系统都支持多个USB串口适配器同时工作,并识别为不同的串口设备和串口编号。

所以你要做的是:

  1. 在不连接USB串口适配器的情况下(通常要求如此)安装正确的设备驱动。
  2. 根据驱动安装的要求,看是否需要重启系统。
  3. 在没有安装适配器的情况下,Windows到设备管理工具中,macOS则记录/dev路径下tty开头的设备。
  4. 连接USB串口适配器,再次到上述相应位置,查看是否增加了串口设备,如果没有增加,返回检查驱动程序甚至适配器硬件。如果有增加,记录下来端口号,以供后续编程使用。
  5. 使用带接线端子的杜邦线,用上图中交叉连接的方式,连接两个适配器的GND-GND/RX-TX/TX-RX。如果感觉插在电脑上不好接线,也可以先将两个适配器接好线再插入电脑USB。
  6. 要么你的两个USB口离的足够近,要么你的杜邦线足够长,总之要保证连接稳定可靠。顺便,如果USB不够多,使用USB集线器也可以正常工作。

开发工具部分,因为学校的教学限定,使用VC6。作为一个追求时尚的unix fans,被逼回到这个太祖级的编程环境我也是有够纠结:(

RS232编程之旅

通常的教程都会从底层写起,细致的构建起整个的系统。而我比较相反,首先从c语言的main主函数的代码讲起:

  1. // 为了清晰结构,代码有删减,但能正常运行
  2. //
  3. #include "serialport.h"
  4. #include<string.h>
  5. int main(int argc, char* argv[])
  6. {
  7. //定义两个句柄,用来报错打开后的两个串口相关资源信息
  8. //句柄是编程中常用的说法,通常都表示指向一堆数据的标志
  9. HANDLE h1,h2;
  10. //定义一个字符串,字符串的内容其实无所谓,用于演示串口通讯的内容
  11. char *msg="Hello, human!\n";
  12. //要传输的数据的长度
  13. int n=strlen(msg);
  14. //一个串口接受用的缓冲区,100是随意给出的,只要大于通讯对端一次传输的数据量即可
  15. char buf[100];
  16. //首先将接受缓冲区清空,在正常、确定长度的数据传输中,这一步并不必要
  17. //但在字符串传输的演示中,还是需要清空的,以保证在串味没有乱字符出现
  18. memset(buf,0,100);
  19. //给用户一个提示,表示传输测试开始了,因为至少以今天的眼光看,串口速度还是很慢的
  20. printf("Serial port test begin ...!\n");
  21. //打开并设置发送端串口,后面的串口编号是在设备管理器中查询到的
  22. //在正式的系统中,这个串口通常会由用户在参数设置中修改
  23. //Uart是英文中对串口的另外一个称呼,serial port/com也是同义
  24. SetupUart(&h1,"com7");
  25. //打开并设置接收端串口
  26. SetupUart2(&h2,"com8");
  27. //在发送端口写出数据,也就是我们准备的字符串
  28. //串口通讯可以容纳的内容范围很广,不仅是字符串,所以使用unsigned char类型
  29. WriteUart((unsigned char*)msg,n,h1);
  30. //在接受端口读取数据,注意因为接收是阻塞式的,所以读取的长度要<=发送的数据包长度,
  31. //否则会让程序阻塞在这里一直等待读取
  32. ReadUart((unsigned char*)buf,n,h2);
  33. //显示接收到的数据内容
  34. printf("Loop received:%s",buf);
  35. //关闭两个打开的串口
  36. CloseUart2();
  37. CloseUart();
  38. return 0;
  39. }

上面代码的注释非常详细,归纳串口操作的步骤为:

  1. 打开并设置串口。
  2. 写入或者读取数据。
  3. 关闭串口。

接下来看细节,也就是串口操作的部分:

  1. //以下代码原型来自MSDN官方示例,为了保持原始代码的风格,尽量不做改动
  2. //代码中有很多东西超出一般学习的范围,比如多线程的事件同步等,可以先大概了解即可
  3. //对于不熟悉的代码,初期可以抄过来用,了解对外的API即可,有时间再去下功夫了解细节
  4. #include "serialPort.h"
  5. DCB PortDCB;
  6. COMMTIMEOUTS CommTimeouts;
  7. HANDLE hPort1,hPort2;
  8. char lastError[1024];
  9. //以下是一些端口设置使用的常量,在正常项目中应当也是归集于配置系统中的
  10. //串口顾名思义是将数据串流化通讯,因此需要定义发送、接收方都完全相同的速度、位长、校验模式等
  11. //另外因为我们只用了三根数据线,其它控制位的设置我们就省略掉了
  12. //这些常量参数使用index*这样的方式是为了同传统界面上的各项设置做的对应,变量命名嘛,不用过于纠结。
  13. int index1=4,//9600
  14. index2=3,//8
  15. index3=2,//NOPARITY
  16. index4=0,//ONSTOPBIT
  17. index5=-1;
  18. //打开并且设置串口
  19. int SetupUart(HANDLE *hPort,char *port1)
  20. {
  21. //打开串行端口,也是把端口当做一个文件来对待
  22. //对于新手,为什么用这个函数之类的问题,只能先死记了
  23. hPort1 = CreateFile (TEXT(port1), // Name of the port
  24. GENERIC_READ | GENERIC_WRITE, // Access (read-write) mode
  25. 0,
  26. NULL,
  27. OPEN_EXISTING,
  28. FILE_ATTRIBUTE_NORMAL,
  29. NULL);
  30. //打开失败就报个警并退出后续操作
  31. if ( hPort1 == INVALID_HANDLE_VALUE )
  32. {
  33. MessageBox (NULL, "Port Open Failed" ,"Error", MB_OK);
  34. return 0;
  35. }
  36. *hPort = hPort1;
  37. //读取当前串口的状态
  38. PortDCB.DCBlength = sizeof (DCB);
  39. GetCommState (hPort1, &PortDCB);
  40. //在当前串口状态的基础上设置串口速率等参数
  41. configure();
  42. //读取当前超时设置
  43. GetCommTimeouts (hPort1, &CommTimeouts);
  44. //根据当前超时设置,设置自己期望的值
  45. configuretimeout();
  46. //Re-configure the port with the new DCB structure.
  47. //上面的configure只是设置了参数结构,下面函数才是真正将之设置到串口
  48. if (!SetCommState (hPort1, &PortDCB))
  49. {
  50. MessageBox (NULL, "1.Could not create the read thread.(SetCommState Failed)" ,"Error", MB_OK);
  51. CloseHandle(hPort1);
  52. return 0;
  53. }
  54. // Set the time-out parameters for all read and write operations on the port.
  55. //同样设置configuretimeout输出的结果
  56. if (!SetCommTimeouts (hPort1, &CommTimeouts))
  57. {
  58. MessageBox (NULL, "Could not create the read thread.(SetCommTimeouts Failed)" ,"Error", MB_OK);
  59. CloseHandle(hPort1);
  60. return 0;
  61. }
  62. // Clear the port of any existing data.
  63. //如果串口还有以前通讯积累的未完结数据,清理掉
  64. if(PurgeComm(hPort1, PURGE_TXCLEAR | PURGE_RXCLEAR)==0)
  65. { MessageBox (NULL, "Clearing The Port Failed" ,"Message", MB_OK);
  66. CloseHandle(hPort1);
  67. return 0;
  68. }
  69. //MessageBox (NULL, "Port1 SETUP OK." ,"Message", MB_OK);
  70. return 1;
  71. }
  72. //下面函数功能同上面的完全一样,其实设置一个函数就好,这里保持原状
  73. int SetupUart2(HANDLE *hPort,char *port2)
  74. {
  75. //int STOPBITS;
  76. hPort2 = CreateFile (TEXT(port2), // Name of the port
  77. GENERIC_READ | GENERIC_WRITE, // Access (read-write) mode
  78. 0,
  79. NULL,
  80. OPEN_EXISTING,
  81. FILE_ATTRIBUTE_NORMAL,
  82. NULL);
  83. if ( hPort2 == INVALID_HANDLE_VALUE )
  84. {
  85. MessageBox (NULL, "Port Open Failed" ,"Error", MB_OK);
  86. return 0;
  87. }
  88. *hPort = hPort2;
  89. // Initialize the DCBlength member.
  90. PortDCB.DCBlength = sizeof (DCB);
  91. // Get the default port setting information.
  92. GetCommState (hPort2, &PortDCB);
  93. configure();
  94. // Retrieve the time-out parameters for all read and write operations
  95. GetCommTimeouts (hPort2, &CommTimeouts);
  96. configuretimeout();
  97. //Re-configure the port with the new DCB structure.
  98. if (!SetCommState (hPort2, &PortDCB))
  99. {
  100. MessageBox (NULL, "1.Could not create the read thread.(SetCommState Failed)" ,"Error", MB_OK);
  101. CloseHandle(hPort2);
  102. return 0;
  103. }
  104. // Set the time-out parameters for all read and write operations on the port.
  105. if (!SetCommTimeouts (hPort2, &CommTimeouts))
  106. {
  107. MessageBox (NULL, "Could not create the read thread.(SetCommTimeouts Failed)" ,"Error", MB_OK);
  108. CloseHandle(hPort2);
  109. return 0;
  110. }
  111. // Clear the port of any existing data.
  112. if(PurgeComm(hPort2, PURGE_TXCLEAR | PURGE_RXCLEAR)==0)
  113. { MessageBox (NULL, "Clearing The Port Failed" ,"Message", MB_OK);
  114. CloseHandle(hPort2);
  115. return 0;
  116. }
  117. //MessageBox (NULL, "Port2 SETUP OK." ,"Message", MB_OK);
  118. return 1;
  119. }
  120. //PortDCB是全局变量,这里根据读取到的端口状态,设置自己希望的通讯参数
  121. int configure()
  122. {
  123. // Change the DCB structure settings
  124. PortDCB.fBinary = TRUE; // Binary mode; no EOF check
  125. PortDCB.fParity = TRUE; // Enable parity checking
  126. PortDCB.fDsrSensitivity = FALSE; // DSR sensitivity
  127. PortDCB.fErrorChar = FALSE; // Disable error replacement
  128. PortDCB.fOutxDsrFlow = FALSE; // No DSR output flow control
  129. PortDCB.fAbortOnError = FALSE; // Do not abort reads/writes on error
  130. PortDCB.fNull = FALSE; // Disable null stripping
  131. PortDCB.fTXContinueOnXoff = TRUE; // XOFF continues Tx
  132. //设置波特率
  133. switch(index1) // BAUD Rate
  134. {
  135. case 0:
  136. PortDCB.BaudRate= 115200;
  137. break;
  138. case 1:
  139. PortDCB.BaudRate = 19200;
  140. break;
  141. case 2:
  142. PortDCB.BaudRate= 38400;
  143. break;
  144. case 3:
  145. PortDCB.BaudRate = 57600;
  146. break;
  147. case 4:
  148. PortDCB.BaudRate = 9600;
  149. break;
  150. default:
  151. break;
  152. }
  153. //设置通讯字节位长
  154. switch(index2) // Number of bits/byte, 5-8
  155. {
  156. case 0:
  157. PortDCB.ByteSize = 5;
  158. break;
  159. case 1:
  160. PortDCB.ByteSize = 6;
  161. break;
  162. case 2:
  163. PortDCB.ByteSize= 7;
  164. break;
  165. case 3:
  166. PortDCB.ByteSize=8;
  167. break;
  168. default:
  169. break;
  170. }
  171. //校验方式
  172. switch(index3) // 0-4=no,odd,even,mark,space
  173. {
  174. case 0:
  175. PortDCB.Parity= EVENPARITY;
  176. break;
  177. case 1:
  178. PortDCB.Parity = MARKPARITY;
  179. break;
  180. case 2:
  181. PortDCB.Parity = NOPARITY;
  182. break;
  183. case 3:
  184. PortDCB.Parity = ODDPARITY;
  185. break;
  186. case 4:
  187. PortDCB.Parity = SPACEPARITY;
  188. break;
  189. default:
  190. break;
  191. }
  192. //停止位
  193. switch(index4)
  194. {
  195. case 0:
  196. PortDCB.StopBits = ONESTOPBIT;
  197. break;
  198. case 1:
  199. PortDCB.StopBits = TWOSTOPBITS;
  200. break;
  201. default:
  202. break;
  203. }
  204. //是否使用硬件流控制等
  205. switch(index5)
  206. {
  207. case 0:
  208. PortDCB.fOutxCtsFlow = TRUE; // CTS output flow control
  209. PortDCB.fDtrControl = DTR_CONTROL_ENABLE; // DTR flow control type
  210. PortDCB.fOutX = FALSE; // No XON/XOFF out flow control
  211. PortDCB.fInX = FALSE; // No XON/XOFF in flow control
  212. PortDCB.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control
  213. break;
  214. case 1:
  215. PortDCB.fOutxCtsFlow = FALSE; // No CTS output flow control
  216. PortDCB.fDtrControl = DTR_CONTROL_ENABLE; // DTR flow control type
  217. PortDCB.fOutX = FALSE; // No XON/XOFF out flow control
  218. PortDCB.fInX = FALSE; // No XON/XOFF in flow control
  219. PortDCB.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control
  220. break;
  221. case 2:
  222. PortDCB.fOutxCtsFlow = FALSE; // No CTS output flow control
  223. PortDCB.fDtrControl = DTR_CONTROL_ENABLE; // DTR flow control type
  224. PortDCB.fOutX = TRUE; // Enable XON/XOFF out flow control
  225. PortDCB.fInX = TRUE; // Enable XON/XOFF in flow control
  226. PortDCB.fRtsControl = RTS_CONTROL_ENABLE; // RTS flow control
  227. break;
  228. default:
  229. break;
  230. }
  231. return 1;
  232. }
  233. int configuretimeout()
  234. { //超时设置,放置读写端口时时间过长程序挂起
  235. //memset(&CommTimeouts, 0x00, sizeof(CommTimeouts));
  236. CommTimeouts.ReadIntervalTimeout = 50;
  237. CommTimeouts.ReadTotalTimeoutConstant = 50;
  238. CommTimeouts.ReadTotalTimeoutMultiplier=10;
  239. CommTimeouts.WriteTotalTimeoutMultiplier=10;
  240. CommTimeouts.WriteTotalTimeoutConstant = 50;
  241. return 1;
  242. }
  243. int WriteUart(unsigned char *buf1, int len,HANDLE hPort)
  244. {
  245. DWORD dwNumBytesWritten;
  246. //使用写文件的方式向串口输出数据
  247. //因为串口芯片及驱动程序都有缓存,所以一般小数据量的写出都不会阻塞
  248. WriteFile (hPort,buf1, len,&dwNumBytesWritten,NULL);
  249. if(dwNumBytesWritten > 0)
  250. {
  251. //MessageBox (NULL, "Transmission Success" ,"Success", MB_OK);
  252. return 1;
  253. }
  254. else
  255. {
  256. MessageBox (NULL, "Transmission Failed" ,"Error", MB_OK);
  257. return 0;
  258. }
  259. }
  260. int ReadUart(unsigned char *buf2,int len,HANDLE hPort)
  261. {
  262. //BOOL ret;
  263. DWORD dwRead;
  264. BOOL fWaitingOnRead = FALSE;
  265. OVERLAPPED osReader = {0};
  266. unsigned long retlen=0;
  267. // Create the overlapped event. Must be closed before exiting to avoid a handle leak.
  268. //读取串口的时候,如果对方尚未发送指定长度的数据,会导致读取串口阻塞
  269. //这里使用线程同步的事件响应方式,防止读取数据阻塞
  270. //所以读取串口可能返回0表示没有读取到数据
  271. //或者小于期望读取的字节表示数据尚未完全到来
  272. osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
  273. if (osReader.hEvent == NULL)
  274. MessageBox (NULL, "Error in creating Overlapped event" ,"Error", MB_OK);
  275. if (!fWaitingOnRead)
  276. {
  277. //具体的读取数据
  278. if (!ReadFile(hPort, buf2, len, &dwRead, &osReader))
  279. {
  280. FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
  281. NULL,
  282. GetLastError(),
  283. MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
  284. lastError,
  285. 1024,
  286. NULL);
  287. MessageBox (NULL, lastError ,"MESSAGE", MB_OK);
  288. }
  289. else
  290. {
  291. // MessageBox (NULL, "ReadFile Suceess" ,"Success", MB_OK);
  292. }
  293. }
  294. if(dwRead > 0)
  295. {
  296. //MessageBox (NULL, "Read DATA Success" ,"Success", MB_OK);//If we have data
  297. return (int) retlen;
  298. }
  299. //return the length
  300. else return 0; //else no data has been read
  301. }
  302. //关闭端口,同样有一个就够了
  303. int CloseUart()
  304. {
  305. CloseHandle(hPort1);
  306. return 1;
  307. }
  308. int CloseUart2()
  309. {
  310. CloseHandle(hPort2);
  311. return 1;
  312. }

在串口的编程中,打开串口、读写串口、关闭串口都是通常的文件操作,也就是把串口当做一个文件的方式进行处理。

只有串口的设置部分(本程序中是跟打开串口放在一起)是同传统文件操作不相同的。

第二个不同则是,通常的硬盘文件读写,速度都很快,不需要考虑阻塞问题。而串口是非常慢的设备,需要考虑阻塞问题的额外处理。

一般的初学者在这部分不需要太过纠结具体的过程,做到一般了解后。把良好运行的样本程序按照自己习惯封装、保存起来,用到的时候抄过来用即可。

我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2ei4o2mgj5j4o

RS232串口的Windows编程纪要的更多相关文章

  1. 自制单片机之十七……PC与单片机RS-232串口的通讯和控制

    这次我们来试着一步步的去掌握PC与单片机通过RS-232进行通讯和控制. 先说说我硬件的情况.我用的PC是个二手的IBM240小本本,十寸屏,赛扬400,机子很老了.但也有它的优点:1.串口,并口,P ...

  2. 【Windows编程】系列第六篇:创建Toolbar与Statusbar

    上一篇我们学习了解了如何使用Windows GDI画图,该应用程序都是光光的静态窗口,我们使用Windows应用程序,但凡稍微复杂一点的程序都会有工具栏和状态栏,工具栏主要用于一些快捷功能按钮.比如典 ...

  3. 【Windows编程】系列第十一篇:多文档界面框架

    前面我们所举的例子中都是单文档界面框架,也就是说这个窗口里面的客户区就是一个文档界面,可以编写程序在里面输入或者绘制文本和图形输出,但是不能有出现多个文档的情况.比如下面的UltraEdit就是一个典 ...

  4. 【Windows编程】系列第十篇:文本插入符

    大家知道,在使用微软的编程环境创建工程时会让你选择是控制台模式还是Windows应用程序.如果选择控制台的console模式,就会在运行时出现一个黑洞洞的字符模式窗口,里面就有等待输入一闪一闪的插入符 ...

  5. 【Windows编程】系列第八篇:通用对话框

    上一篇我们学习了菜单的基本编程,本篇来了解一下通用对话框的使用.Windows系统之所以是目前最流行的桌面系统,也是因为Windows有一套标准化,统一友好的交互界面,比如菜单.工具栏.状态栏以及各个 ...

  6. 【Windows编程】系列第七篇:Menubar的创建和使用

    上一篇我们学习了利用windows API创建工具栏和菜单栏,与上一篇紧密联系的就是菜单栏,菜单栏是一个大多数复杂一些的Windows应用程序不可或缺的部分.比如下图就是Windows自带的记事本的菜 ...

  7. 【Windows编程】系列第五篇:GDI图形绘制

    上两篇我们学习了文本字符输出以及Unicode编写程序,知道如何用常见Win32输出文本字符串,这一篇我们来学习Windows编程中另一个非常重要的部分GDI图形绘图.Windows的GDI函数包含数 ...

  8. 【Windows编程】系列第九篇:剪贴板使用

    上一篇我们学习了常见的通用对话框,本篇来了解剪贴板的使用,它常用于复制粘贴功能. 剪贴板是Windows最早就加入的功能,由于该功能非常实用,我们几乎每天都会使用到.通过剪贴板,我们就可以将数据从一个 ...

  9. 【Windows编程】系列第四篇:使用Unicode编程

    上一篇我们学习了Windows编程的文本及字体输出,在以上几篇的实例中也出现了一些带有“TEXT”的Windows宏定义,有朋友留言想了解一些ANSI和Unicode编程方面的内容,本章就来了解和学习 ...

随机推荐

  1. vue v-if 和 v-show 的知识点

    1.v-if 的特点: 实现方式:根据后面数据的真假判断是否重新删除或创建元素. 性能消耗:有较高的切换性能消耗. 编译过程:v-if 切换有一个局部编译/卸载的过程,切换过程中合适地销毁和重建内部的 ...

  2. Shell脚本学习 - 函数,输入输出重定向,文件

    函数 函数定义 [ function ] funname [()] { action; [return int;] } 定义时可以是function fun(),也可以直接fun(),不带参数 返回值 ...

  3. Python的使用方法

    1 安装turtle Python2安装命令: pip install turtule Python3安装命令: pip3 install turtle 因为turtle库主要是在Python2中使用 ...

  4. Java拦截器的实现原理

    对于某个类的A方法进行拦截,在A执行前插入一段代码,A执行后也插入一段代码 原理: 写个拦截器,拦截器中包含要插入前后执行的两段代码 interceptor { C();//C方法 D();//D方法 ...

  5. 使用mongodb的一些笔记

    show dbs # 从结果中发现有cmb_demo_23_hackeruse cmb_demo_23_hacker db.all_in_one.find({"_id":15480 ...

  6. 修改 Docker 的 daemon.json后启动失败

    创建Harbor要把register 换成Harbor地址 vim /etc/docker/daemon.json添加{ "insecure-registries":[" ...

  7. Vue知识点总结

    1.属性名已$开头的都是内部提供的属性 2.为什么使用事件修饰符的原因:methods 只有纯粹的数据逻辑,而不是去处理 DOM 事件细节 3.v-if 如果值为false,元素在页面中不存在:值为t ...

  8. vue.js数据可以在页面上渲染成功却总是警告提示某个字段“undefined”未定义

    最近在开发公司的一个后端管理系统,用的是比较流行的vue框架.在开发过程中,总是出现各种各样的报错问题,有警告的,有接口不通的,有自己马虎造成的低级错误的等等,这些错误在一些老司机面前分分钟解决,但今 ...

  9. [bzoj1059]矩阵游戏

    虽然是一道水难题,但是我这种蒟蒻还是要讲一讲的. Description 小Q是一个非常聪明的孩子,除了国际象棋,他还很喜欢玩一个电脑益智游戏——矩阵游戏.矩阵游戏在一个N *N黑白方阵进行(如同国际 ...

  10. Jmeter之Non HTTP response code: java.net.SocketException/Non HTTP response message: Permission denied: connect

    最近在做性能测试过程中遇到了高并发时,后台监控各项指标都很正常,但是测试结果中很多Non HTTP response code: java.net.SocketException/Non HTTP r ...