【问题标题】:How do I find out that ReadFile function timed-out?如何找出 ReadFile 函数超时?
【发布时间】:2016-11-16 17:27:23
【问题描述】:

当我在将 USB-UART 转换器连接到笔记本电脑后未通过 GetTimeout 和 SetTimeout 设置超时设置时,ReadFile 会阻塞。但是,一旦我通过 SetTimeOut 设置它们,ReadFile 就不再阻塞,即使没有读取给定的字节数也会返回 true。

当 ReadFile 返回 true 但 dwRead 参数显示 0 因为没有数据通过串行端口进入我的 PC 时,我得出结论 ReadFile 函数一定已经超时。但是,使用 GetLastError 返回 0。如何在我的 C++ 程序中验证 ReadFile 是否实际超时?

【问题讨论】:

  • 是否传输了任何字节?
  • 当 ReadFile 返回 true 但 dwRead 参数显示为 0 [...] 我得出的结论是 ReadFile 函数必须超时 - 对于串行端口句柄,我相信这一点是一个安全的结论。如果有任何其他原因,调用就会失败。
  • 好的,我将在我的程序中使用它。此时我的笔记本电脑上只有 USB-UART 转换器,转换器的另一端没有设备,因此正在读取数据。
  • main - 在这种情况下,您永远不会成为阻塞。 ZwReadFile 返回 STATUS_PENDING。操作完成时 - 查找 iosb.Information(读取的字节数)和 iosb.Status - 操作的最终状态。如果超时信息将为 0 并且 Status != 0
  • @RbMm:在某些情况下,从用户模式使用内核 API 是可以原谅的。这不是其中之一。 Win32 API 非常好地支持异步 I/O。

标签: c++ winapi serial-port timeout readfile


【解决方案1】:

当使用 SetCommTimeouts()ReadFile() 时,COMMTIMEOUTS 文档说:

ReadIntervalTimeout
在通信线路上的下一个字节到达之前允许经过的最长时间,以毫秒为单位。 如果任意两个字节到达的时间间隔超过此数量,则ReadFile 操作完成并返回所有缓冲数据。零值表示不使用间隔超时。

如果发生超时,则读取完成(ReadFile() 返回 TRUE),并且在超时之前已缓冲的字节数反映在您的 dwRead 变量中。因此,如果dwRead 小于 您要求ReadFile() 读取的字节数,您将知道是否发生超时。如果没有缓冲数据,dwRead 将为 0。如果 dwRead等于您请求的字节数,则没有超时,因为 ReadFile() 在最后一个请求字节时退出已阅读。

【讨论】:

  • 非常好。奇怪的设计,nicht war?比较read():它会阻塞,直到至少传输一个字节或流结束或发生错误,包括超时。 之间字节的超时没有废话,这使得超时值很难有效地建立。
  • read() 可以处理字节之间的超时,设置读取超时可能会导致它失败并出现ETIMEDOUT 错误代码,与阻塞recv() 相同。我认为ReadFile() 的另一种设计是让它失败并出现ERROR_TIMEOUT 错误代码。
  • read() 无法处理字节之间的超时,因为它将返回超时前收到的第一个字节。超时只能发生在之前接收任何字节。
  • 但是如果使用 NT api,比如 NtReadFile - 你会在超时时得到 iosb.Status == STATUS_TIMEOUT - 这是最简单和最准确的方法
【解决方案2】:

最简单准确的方法是使用 NT api NtReadFile 并检查操作的最终状态。超时 iosb.Status == STATUS_TIMEOUT。 kernel32 api ReadFile - 丢失此状态的信息(STATUS_TIMEOUT),因为它只检查 STATUS_PENDING 和 0 > 状态

对于同步工作,可以使用这样的代码:

void SyncTest(POBJECT_ATTRIBUTES poa)
{
    HANDLE hFile;
    IO_STATUS_BLOCK iosb;
    NTSTATUS status = NtOpenFile(&hFile, FILE_ALL_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, FILE_SYNCHRONOUS_IO_NONALERT);
    if (0 <= status)
    {
        SERIAL_TIMEOUTS st = { 4000, 1, 0, 1, 0 };
        status = NtDeviceIoControlFile(hFile, 0, 0, 0, &iosb, IOCTL_SERIAL_SET_TIMEOUTS, &st, sizeof(st), 0, 0);
        DbgPrint("time %x[%x,%p]\n", status, iosb.Status, iosb.Information);
        if (0 <= status)
        {
            UCHAR buf[256];
            status = NtReadFile(hFile, 0, 0, 0, &iosb, buf, sizeof(buf), 0, 0);
            DbgPrint("read %x [%x,%p]\n", status, iosb.Status, iosb.Information);
            //assert(status == iosb.Status);
            if (status == STATUS_TIMEOUT)
            {
                DbgPrint("timeout\n");
            }
        }
        NtClose(hFile);
    }
}

对于异步:

class __declspec(novtable) IoObject 
{
    friend class UserIrp;
protected:
    HANDLE _hFile;
private:
    LONG _dwRef;

protected:
    virtual ~IoObject()
    {
        if (_hFile) NtClose(_hFile);
    }
    virtual void OnIoComplete(NTSTATUS status, ULONG_PTR Information, ULONG code, PVOID pv) = 0;
public:

    NTSTATUS BindIoCompletion();

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef)) delete this;
    }

    IoObject()
    {
        _hFile = 0;
        _dwRef = 1;
    }
};

class UserIrp : public IO_STATUS_BLOCK
{
    friend IoObject;

    IoObject* _pObj;
    PVOID _pv;
    LONG _dwRef;
    ULONG _code;

    static VOID WINAPI OnComplete(NTSTATUS Status, ULONG_PTR Information, UserIrp* This)
    {
        This->_pObj->OnIoComplete(Status, Information, This->_code, This->_pv);
        This->Release();
    }

    ~UserIrp()
    {
        _pObj->Release();
    }

public:

    NTSTATUS CheckStatus(NTSTATUS status)
    {
        if (NT_ERROR(status))
        {
            OnComplete(status, Information, this);
        }

        return status;
    }

    void AddRef()
    {
        InterlockedIncrement(&_dwRef);
    }

    void Release()
    {
        if (!InterlockedDecrement(&_dwRef)) delete this;
    }

    UserIrp(IoObject* pObj, ULONG code, PVOID pv) : _pObj(pObj), _dwRef(1), _code(code), _pv(pv)
    {
        pObj->AddRef();
    }
};

NTSTATUS IoObject::BindIoCompletion()
{
    return RtlSetIoCompletionCallback(_hFile, (LPOVERLAPPED_COMPLETION_ROUTINE)UserIrp::OnComplete, 0);
}

class MySerial : public IoObject
{
    void OnIoComplete(NTSTATUS status, ULONG_PTR Information, ULONG code, PVOID pv)
    {
        DbgPrint("OnIoComplete(%x, %p, %.4s, %p)\n", status, Information, &code, pv);

        switch (code)
        {
        case 'time':
            if (0 <= status)
            {
                if (PUCHAR buf = new UCHAR[256])
                {
                    if (UserIrp* Irp = new UserIrp(this, 'read', buf))
                    {
                        static LARGE_INTEGER ByteOffset;
                        status = Irp->CheckStatus(NtReadFile(_hFile, 0, 0, Irp, Irp, buf, 256, &ByteOffset, 0));
                        DbgPrint("read begin = %x\n", status);
                        return ;
                    }
                    delete buf;
                }
            }
            break;
        case 'read':
            DbgPrint("read end(%x, %p)\n", status, Information);
            if (status == STATUS_TIMEOUT)
            {
                DbgPrint("timeout\n");
            }
            delete pv;
            break;
        }
    }

    virtual ~MySerial()
    {
        DbgPrint("--MySerial<%p>\n", this);
    }

public:

    MySerial()
    {
        DbgPrint("++MySerial<%p>\n", this);
    }

    NTSTATUS Open(POBJECT_ATTRIBUTES poa)
    {
        IO_STATUS_BLOCK iosb;
        return NtOpenFile(&_hFile, FILE_ALL_ACCESS, poa, &iosb, FILE_SHARE_VALID_FLAGS, 0);
    }

    NTSTATUS SetTimeouts(ULONG ms)
    {
        if (UserIrp* Irp = new UserIrp(this, 'time', 0))
        {
            SERIAL_TIMEOUTS st = { ms, 1, 0, 1, 0 };
            return Irp->CheckStatus(ZwDeviceIoControlFile(_hFile, 0, 0, Irp, Irp, IOCTL_SERIAL_SET_TIMEOUTS, &st, sizeof(st), 0, 0));
        }

        return STATUS_INSUFFICIENT_RESOURCES;
    }
};

void AsyncTest(POBJECT_ATTRIBUTES poa)
{
    if (MySerial* p = new MySerial)
    {
        if (0 <= p->Open(poa) && 0 <= p->BindIoCompletion())
        {
            NTSTATUS status = p->SetTimeouts(4000);
            DbgPrint("set timeout=%x\n", status);
        }
        p->Release();
    }
}

【讨论】:

  • 这里肯定有很多做事的方法
  • @quantum231 使用原生 api 最准确的方式 - 只需检查最终的 NTSTATUS 操作。在您的情况下 - 这将是 STATUS_TIMEOUT。不需要检查字节数、GetLastError 等
猜你喜欢
  • 2011-12-18
  • 2011-11-14
  • 1970-01-01
  • 1970-01-01
  • 2018-03-30
  • 2012-09-21
  • 2021-04-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多