📄 chap12_3.htm
字号:
<html>
<head>
<title>12.3 串行通信与重叠I/O</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<meta name="GENERATOR" content="Microsoft FrontPage 3.0">
<link rel="stylesheet" href="../../../cpcw.css"></head>
<body link="#3973DE" alink="#3973DE" background="../../bg.gif">
<div align="center"><center>
<table width="85%" border="0">
<tr bgcolor="#FFFFFF">
<td>
<div align="center">
<center>
</center>
</div>
<p align="CENTER"><b><font color="red" face="Times New Roman">12.3 </font><font color="red">串行通信与重叠</font><font color="red" face="Times New Roman">I/O</font></b></p>
<p align="JUSTIFY"> Win 32系统为串行通信提供了全新的服务。传统的OpenComm、ReadComm、WriteComm、CloseComm等函数已经过时,WM_COMMNOTIFY消息也消失了。取而代之的是文件I/O函数提供的打开和关闭通信资源句柄及读写操作的基本接口。</p>
<p align="JUSTIFY"> 新的文件I/O函数(CreateFile、ReadFile、WriteFile等)支持重叠式输入输出,这使得线程可以从费时的I/O操作中解放出来,从而极大地提高了程序的运行效率。</p>
<p align="JUSTIFY"><b></b><font color="#3973DE" face="Times New Roman">12.3.1
</font><font color="#3973DE">串行口的打开和关闭</font></p>
<p align="JUSTIFY"> Win 32系统把文件的概念进行了扩展。无论是文件、通信设备、命名管道、邮件槽、磁盘、还是控制台,都是用API函数CreateFile来打开或创建的。该函数的声明为:</p>
<p align="JUSTIFY">HANDLE CreateFile(</p>
<p align="JUSTIFY">LPCTSTR lpFileName, // 文件名 </p>
<p align="JUSTIFY">DWORD dwDesiredAccess, // 访问模式 </p>
<p align="JUSTIFY">DWORD dwShareMode, // 共享模式 </p>
<p align="JUSTIFY">LPSECURITY_ATTRIBUTES lpSecurityAttributes, // 通常为NULL</p>
<p align="JUSTIFY">DWORD dwCreationDistribution, // 创建方式 </p>
<p align="JUSTIFY">DWORD dwFlagsAndAttributes, // 文件属性和标志</p>
<p align="JUSTIFY">HANDLE hTemplateFile // 临时文件的句柄,通常为NULL </p>
<blockquote>
<p align="JUSTIFY">);</p>
</blockquote>
<p align="JUSTIFY"> 如果调用成功,那么该函数返回文件的句柄,如果调用失败,则函数返回INVALID_HANDLE_VALUE。</p>
<p align="JUSTIFY"> 如果想要用重叠I/O方式(参见12.3.3)打开COM2口,则一般应象清单12.4那样调用CreateFile函数。注意在打开一个通信端口时,应该以独占方式打开,另外要指定GENERIC_READ、GENERIC_WRITE、OPEN_EXISTING和FILE_ATTRIBUTE_NORMAL等属性。如果要打开重叠I/O,则应该指定
FILE_FLAG_OVERLAPPED属性。</p>
<p align="JUSTIFY"><b> </b></p>
<b>
<p align="JUSTIFY">清单12.4</p>
</b>
<p align="JUSTIFY">HANDLE hCom;</p>
<p align="JUSTIFY">DWORD dwError;</p>
<p align="JUSTIFY">hCom=CreateFile(“COM2”, // 文件名</p>
<p align="JUSTIFY">GENERIC_READ | GENERIC_WRITE, // 允许读和写 </p>
<p align="JUSTIFY">0, // 独占方式</p>
<p align="JUSTIFY">NULL, </p>
<p align="JUSTIFY">OPEN_EXISTING, //打开而不是创建</p>
<p align="JUSTIFY">FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //
重叠方式</p>
<p align="JUSTIFY">NULL</p>
<p align="JUSTIFY">);</p>
<p align="JUSTIFY">if(hCom = = INVALID_HANDLE_VALUE)</p>
<p align="JUSTIFY">{</p>
<p align="JUSTIFY">dwError=GetLastError( );</p>
<p align="JUSTIFY"><b>. . .</b> // 处理错误</p>
<p align="JUSTIFY">}</p>
<p align="JUSTIFY">当不再使用文件句柄时,应该调用CloseHandle函数关闭之。</p>
<p align="JUSTIFY"><b></b><font color="#3973DE" face="Times New Roman">12.3.2
</font><font color="#3973DE">串行口的初始化</font></p>
<p align="JUSTIFY"> 在打开通信设备句柄后,常常需要对串行口进行一些初始化工作。这需要通过一个DCB结构来进行。DCB结构包含了诸如波特率、每个字符的数据位数、奇偶校验和停止位数等信息。在查询或配置置串行口的属性时,都要用DCB结构来作为缓冲区。</p>
<p align="JUSTIFY"> 调用GetCommState函数可以获得串口的配置,该函数把当前配置填充到一个DCB结构中。一般在用CreateFile打开串行口后,可以调用GetCommState函数来获取串行口的初始配置。要修改串行口的配置,应该先修改DCB结构,然后再调用SetCommState函数用指定的DCB结构来设置串行口。</p>
<p align="JUSTIFY"> 除了在DCB中的设置外,程序一般还需要设置I/O缓冲区的大小和超时。Windows用I/O缓冲区来暂存串行口输入和输出的数据,如果通信的速率较高,则应该设置较大的缓冲区。调用SetupComm函数可以设置串行口的输入和输出缓冲区的大小。</p>
<p align="JUSTIFY"> 在用ReadFile和WriteFile读写串行口时,需要考虑超时问题。如果在指定的时间内没有读出或写入指定数量的字符,那么ReadFile或WriteFile的操作就会结束。要查询当前的超时设置应调用GetCommTimeouts函数,该函数会填充一个COMMTIMEOUTS结构。调用SetCommTimeouts可以用某一个COMMTIMEOUTS结构的内容来设置超时。</p>
<p align="JUSTIFY"> 有两种超时:间隔超时和总超时。间隔超时是指在接收时两个字符之间的最大时延,总超时是指读写操作总共花费的最大时间。写操作只支持总超时,而读操作两种超时均支持。用COMMTIMEOUTS结构可以规定读/写操作的超时,该结构的定义为:</p>
<blockquote>
<p align="JUSTIFY">typedef struct _COMMTIMEOUTS { </p>
<p align="JUSTIFY">DWORD ReadIntervalTimeout; // 读间隔超时</p>
<p align="JUSTIFY">DWORD ReadTotalTimeoutMultiplier; // 读时间系数</p>
<p align="JUSTIFY">DWORD ReadTotalTimeoutConstant; // 读时间常量</p>
<p align="JUSTIFY">DWORD WriteTotalTimeoutMultiplier; // 写时间系数</p>
<p align="JUSTIFY">DWORD WriteTotalTimeoutConstant; // 写时间常量</p>
<p align="JUSTIFY">} COMMTIMEOUTS,*LPCOMMTIMEOUTS;</p>
</blockquote>
<p align="JUSTIFY"> COMMTIMEOUTS结构的成员都以毫秒为单位。总超时的计算公式是:</p>
<p align="JUSTIFY">总超时=时间系数×要求读/写的字符数 + 时间常量</p>
<p align="JUSTIFY"> 例如,如果要读入10个字符,那么读操作的总超时的计算公式为:</p>
<p align="JUSTIFY">读总超时=ReadTotalTimeoutMultiplier×10 + ReadTotalTimeoutConstant</p>
<p align="JUSTIFY"> 可以看出,间隔超时和总超时的设置是不相关的,这可以方便通信程序灵活地设置各种超时。</p>
<p align="JUSTIFY"> 如果所有写超时参数均为0,那么就不使用写超时。如果ReadIntervalTimeout为0,那么就不使用读间隔超时,如果ReadTotalTimeoutMultiplier和ReadTotalTimeoutConstant都为0,则不使用读总超时。如果读间隔超时被设置成MAXDWORD并且两个读总超时为0,那么在读一次输入缓冲区中的内容后读操作就立即完成,而不管是否读入了要求的字符。</p>
<p align="JUSTIFY"> 在用重叠方式读写串行口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的。在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间。</p>
<p align="JUSTIFY">清单12.5列出了一段简单的串行口初始化代码。</p>
<p align="JUSTIFY"><b> </b></p>
<b>
<p align="JUSTIFY">清单12.5 打开并初始化串行口</p>
</b>
<p align="JUSTIFY">HANDLE hCom;</p>
<p align="JUSTIFY">DWORD dwError;</p>
<p align="JUSTIFY">DCB dcb;</p>
<p align="JUSTIFY">COMMTIMEOUTS TimeOuts;</p>
<p align="JUSTIFY">hCom=CreateFile(“COM2”, // 文件名</p>
<p align="JUSTIFY">GENERIC_READ | GENERIC_WRITE, // 允许读和写 </p>
<p align="JUSTIFY">0, // 独占方式</p>
<p align="JUSTIFY">NULL, </p>
<p align="JUSTIFY">OPEN_EXISTING, //打开而不是创建</p>
<p align="JUSTIFY">FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, //
重叠方式</p>
<p align="JUSTIFY">NULL</p>
<p align="JUSTIFY">);</p>
<p align="JUSTIFY">if(hCom = = INVALID_HANDLE_VALUE)</p>
<p align="JUSTIFY">{</p>
<p align="JUSTIFY">dwError=GetLastError( );</p>
<p align="JUSTIFY"><b>. . .</b> // 处理错误</p>
<p align="JUSTIFY">}</p>
<p align="JUSTIFY"> </p>
<p align="JUSTIFY">SetupComm( hCom, 1024, 1024 ) //缓冲区的大小为1024</p>
<p align="JUSTIFY"> </p>
<p align="JUSTIFY">TimeOuts. ReadIntervalTimeout=1000; </p>
<p align="JUSTIFY">TimeOuts.ReadTotalTimeoutMultiplier=500; </p>
<p align="JUSTIFY">TimeOuts.ReadTotalTimeoutConstant=5000; </p>
<p align="JUSTIFY">TimeOuts.WriteTotalTimeoutMultiplier=500; </p>
<p align="JUSTIFY">TimeOuts.WriteTotalTimeoutConstant=5000;</p>
<p align="JUSTIFY">SetCommTimeouts(hCom, &TimeOuts); // 设置超时</p>
<p align="JUSTIFY"> </p>
<p align="JUSTIFY">GetCommState(hCom, &dcb);</p>
<p align="JUSTIFY">dcb.BaudRate=2400; // 波特率为2400</p>
<p align="JUSTIFY">dcb.ByteSize=8; // 每个字符有8位</p>
<p align="JUSTIFY">dcb.Parity=NOPARITY; //无校验</p>
<p align="JUSTIFY">dcb.StopBits=ONESTOPBIT; //一个停止位</p>
<p align="JUSTIFY">SetCommState(hCom, &dcb);</p>
<b>
<p align="JUSTIFY"> </p>
</b>
<p align="JUSTIFY"><b></b><font color="#3973DE" face="Times New Roman">12.3.3
</font><font color="#3973DE">重叠</font><font color="#3973DE" face="Times New Roman">I/O</font></p>
<p align="JUSTIFY"> 在用ReadFile和WriteFile读写串行口时,既可以同步执行,也可以重叠(异步)执行。在同步执行时,函数直到操作完成后才返回。这意味着在同步执行时线程会被阻塞,从而导致效率下降。在重叠执行时,即使操作还未完成,调用的函数也会立即返回。费时的I/O操作在后台进行,这样线程就可以干别的事情。例如,线程可以在不同的句柄上同时执行I/O操作,甚至可以在同一句柄上同时进行读写操作。“重叠”一词的含义就在于此。</p>
<p align="JUSTIFY"> ReadFile函数只要在串行口输入缓冲区中读入指定数量的字符,就算完成操作。而WriteFile函数不但要把指定数量的字符拷入到输出缓冲中,而且要等这些字符从串行口送出去后才算完成操作。</p>
<p align="JUSTIFY"> ReadFile和WriteFile函数是否为执行重叠操作是由CreateFile函数决定的。如果在调用CreateFile创建句柄时指定了FILE_FLAG_OVERLAPPED标志,那么调用ReadFile和WriteFile对该句柄进行的读写操作就是重叠的,如果未指定重叠标志,则读写操作是同步的。</p>
<p align="JUSTIFY"> 函数ReadFile和WriteFile的参数和返回值很相似。这里仅列出ReadFile函数的声明:</p>
<p align="JUSTIFY">BOOL ReadFile(</p>
<blockquote>
<p align="JUSTIFY">HANDLE hFile, // 文件句柄</p>
<p align="JUSTIFY">LPVOID lpBuffer, // 读缓冲区</p>
<p align="JUSTIFY">DWORD nNumberOfBytesToRead, // 要求读入的字节数 </p>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -