vc++ 串口重叠IO实验

一   问题的由来

        关于VC++串口重叠IO通信,一直有些细节不清楚, 刚好要做一个串口通信类,调试时遇到问题了, 在使用重叠IO方式打开串口后,使用重叠方式读取出口数据时发现read函数总是直接返回TRUE,但获得的字节数却是0,代码如下:

    int i =0;
    DWORD dwRead=0;

   char   buf[1024];

    //使用重叠IO操作, 直到超时返回
    OVERLAPPED Overlapped;
    memset(&Overlapped, 0, sizeof(Overlapped));
    Overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);         //初始化无信号状态
    BOOL bRet =  ReadFile(m_hComm, buf, sizeof(buf), &dwRead, &Overlapped);
    DWORD Err = ::GetLastError();

   调试断点状态结果 : 

                bRet  : TRUE;

                dwRead : 0;

                 Err :0;

  // 一切都很正常, 好像没什么不对

==================================

疑问:  我使用的USB转232, TX和RX是使用跳线短接的, 在read之前, 先执行了write函数,读到的数据长度为0,这没有道理啊?

于是在Read之前加了几行代码, 查询串口缓冲区内数据:

    COMSTAT ComStat;
    DWORD dwErrorFlags;
    ClearCommError(m_hComm, &dwErrorFlags, &ComStat);

得到ComStat.cbInQue=11, 这11个字节刚好是我发送的字节数, 说明串口缓冲区里面的数据时有的

但是, 读取到的数据长度仍然是0,调用均OK, 这令人有点崩溃了!

二 解决问题的线索

我是使用重叠方式打开串口的,这个结果肯定和重叠IO有关系,于是一阵狂搜, 在这里找到了提示:

https://wangbaiyuan.cn/c-serial-communication-write-reading.html

其中提到:

在用重叠方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的

在这种情况下,超时规定的是操作的完成时间,而不是ReadFile和WriteFile的返回时间(重叠IO将立即返回)

如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符 --------------这句话是重点所在了

在根据这个提示,串口打开后, 先做如下的读写超时设置:

        COMMTIMEOUTS TimeOuts;
          //读总超时=ReadTotalTimeoutMultiplier×N+ReadTotalTimeoutConstant
           TimeOuts.ReadIntervalTimeout = 0/*MAXDWORD*/;         //如果ReadIntervalTimeout为0,那么就不使用读间隔超时
                                                                 //如果读间隔超时被设置成MAXDWORD并且读时间系数和读时间常量都为0,那么在读一次输入缓冲区的内容后读操作就立即返回,而不管是否读入了要求的字符。
            TimeOuts.ReadTotalTimeoutMultiplier = 0;             //如果ReadTotalTimeoutMultiplier 和 ReadTotalTimeoutConstant 都为0,则不使用读总超时
            TimeOuts.ReadTotalTimeoutConstant = 0;

            //在读一次输入缓冲区的内容后读操作就立即返回,
            //而不管是否读入了要求的字符。
            //设定写超时---------------------------------------------如果所有写超时参数均为0,那么就不使用写超时
            TimeOuts.WriteTotalTimeoutMultiplier = 100;
            TimeOuts.WriteTotalTimeoutConstant = 500;
            SetCommTimeouts(m_hComm, &TimeOuts); //设置超时    

》》》 修改后执行程序:

            BOOL bRet =  ReadFile(m_hComm, buf, sizeof(buf), &dwRead, &Overlapped);

           bRet的返回不在是TRUE了, 而是FALSE; 而 ::GetLastError() 的结是ERROR_IO_PENDING,这正是串口重叠IO读操作的正确姿势啊!

======= 但问题有来了, 明明ComStat.cbInQue=11, 但是

=======在重叠IO超时后,使用::GetOverlappedResult(m_hComm, &Overlapped, &dwRead, FALSE);

=======获得的返回数据长度仍然为0, 代码如下:

    if(::GetLastError() == ERROR_IO_PENDING){
            DWORD dwRet =0;
            if(0 == ovt_sec) ovt_sec=1; //因为使用了异步IO, 必须设置相应的延时,否则read数据始终=0

            dwRet = WaitForSingleObject(Overlapped.hEvent, ovt_sec*1000);  
            ::GetOverlappedResult(m_hComm, &Overlapped, &dwRead, FALSE);

            //dwRet  返回的值是 WAIT_TIMEOUT
            if (WAIT_OBJECT_0 != dwRet){ 
                ClearCommError(m_hComm, &dwErrorFlags, &ComStat);
                if (ComStat.cbInQue)
                    ReadFile(m_hComm, buf, ComStat.cbInQue, &dwRead, &Overlapped);

                NetPrintf("\t 读串口[%d]超时(%dms), 返回%d字节!\n", m_comPort, ovt_sec * 1000, dwRead);
            }
        }        

》》》 那我读一个字节试试:

              BOOL bRet =  ReadFile(m_hComm, buf, sizeof(buf), &dwRead, &Overlapped);

 改成:

            BOOL bRet =  ReadFile(m_hComm, buf, 1, &dwRead, &Overlapped);

这次的结果变成:   

             bRet返回TRUE, dwRead 返回1          ---------- 读数据正常了

            增加,读取的字节数,一直到11 (注意ClearCommError查询到缓冲区里面的数据正是11), ReadFile都是返回TRUE

当读取的字节数增加到12时, ReadFile都是返回TRUE, 进入if(::GetLastError() == ERROR_IO_PENDING)代码块;

问题来了: 

          IO_PENDING超时后,::GetOverlappedResult(m_hComm, &Overlapped, &dwRead, FALSE); 得到dwRead=0

诡异的是: 再次ClearCommError查询缓冲区内数据, 结果为0了, 说明数据被读走了,

        如果缓冲区内数据量是未知的(串口硬件可能还在继续接收数据),或者数据长度是个不确定长度的值,我怎么知道我我通过重叠IO读取了多少数据?

 

三 问题的解决

        以上说明我对串口重叠IO操作并没有深刻地理解, 有哪个地方出现了偏差,完全是在按照自己的想法在使用重叠IO,而且直接从网上抄了一段代码,为经过充分验证。

       如果,我只是ClearCommError查询缓冲区内数据的长度来读取缓冲区, 则ReadFile返回TRUE, 重叠IO根本就用不上啊!

如果我读取未知数量的数据(ReadFile的读取期望长度参数值通常会比较大), 但是我的不到到底读取了多少字节数据啊!

选择相信微软, 肯定是自己哪里还有问题。再回顾一下:

         在用重叠方式读写串口时,虽然ReadFile和WriteFile在完成操作以前就可能返回,但超时仍然是起作用的

        在这种情况下,超时规定的是操作的完成时间,而不是和WriteFile的返回时间(重叠IO将立即返回)

         如果: ReadFile的返回时间比超时规定的是操作的完成时间要早, 会发生什么呢?做个试验吧:

》》》修改读超时时间为1秒

        COMMTIMEOUTS TimeOuts;
         TimeOuts.ReadIntervalTimeout = 0/*MAXDWORD*/
         TimeOuts.ReadTotalTimeoutMultiplier = 0;     
          TimeOuts.ReadTotalTimeoutConstant = 1000;

          WaitForSingleObject(Overlapped.hEvent, 2*1000);  

奇迹发生了:

        WaitForSingleObject返回WAIT_OBJECT_0, 而不是超时WAIT_TIMEOUT

        ::GetOverlappedResult(m_hComm, &Overlapped, &dwRead, FALSE);

         dwRead 也获得了返回的字节数11

 

四 总结 (个人理解,未找到文档做依据)

        以上充分说明, 在对串口的重叠IO操作过程中,读写超时参数 COMMTIMEOUTS TimeOuts;仍然在发挥着重要作用

WaitForSingleObject是否获得信号, 实际上是ReadFile函数是否完成的信号

     如果WaitForSingleObject返回超时, 说明ReadFile函数的超时还没有结束。

     所以如果我需要读取不定长度的数据, 最好把ReadFile超时设置的小于WaitForSingleObject超时,这样,读超时结束后,WaitForSingleObjec可以获得信号, 并通过 ::GetOverlappedResult(m_hComm, &Overlapped, &dwRead, FALSE);获得实际读取到的数据量。

       也可以每次只读取一个字节,这样,只要有一个字节到来, WaitForSingleObjec就可以获得信号, 然后根据剩余的超时时间决定是否发起下一轮的ReadFile调用。

       == 对于写超时, 因为串口是个低速设备, 当用于大量数据的发送时,可能写超时才会发生作用, 一般的应用中, 由于串口write缓冲区可能会大于写入的字节数,所以一般不会出现超时(witeFile一般会立即返回)。

      我有个未经验证的猜想,如果我把串口缓冲区设置的小于单次发送的数据量,这个时候,写超时机制应该就会发挥作用了吧?

 

如果我们想读写可靠,要根据不同的速率和应用实际情况设置合适的值。

和同步模式和异步模式、不同的通信协议无关。

 

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值