0

我是一名 linux 程序员,最近参与了将一个基于 epoll 的客户端与两个用 c 编写的文件描述符移植到 windows 中。
如您所知,在使用 epoll 或 select 的 linux 中(我知道 windows 支持 select,但它根本没有效率)您可以阻塞文件描述符,直到文件描述符准备好并且您可以知道它何时准备好写入和何时读取。

我已经查看了 Windows IOCP,在微软世界中重叠 io 听起来不错。但在所有示例中,它都用于多客户端服务器,每个客户端的套接字都独立于其他套接字。

使用完成端口,可以为每个客户端创建一个completionKey结构,并在struct中放入一个变量,并在调用WSArecv时读取它,并在WSAsend时读取,另一个变量指示套接字值并从GetQueuedCompletionStatus中检索它们以知道该怎么做,如果对套接字进行了写入,则进行读取,反之亦然。

但就我而言,文件描述符(fd)确实重叠。从一个 fd 读取,对另一个 fd 进行读取和写入,这使得在 GetQueuedCompletionStatus 结果中很难知道每个 fd 实际发生了什么操作,因为每个 fd 都有一个 completionKey。为了清楚起见,请考虑:

有两个句柄 fd1 和 fd2,completionKey1 持有 f1 的句柄和状态,fd2 的 completionKey2 和 completionKey 变量用于从 GetQueuedCompletionStatus 检索完成。

    GetQueuedCompletionStatus(port_handle, &completionKey.bufflen, (PULONG_PTR)&completionKey,(LPOVERLAPPED *)&ovl,INFINITE);

   switch (completionKey.status)
    {
        case READ:
            if(completionKey->handle == fd1)
            {
                fd1_read_is_done(completionKey.buffer,completionKey.bufflen);
                completionKey->status = WRITE;
                do_fd1_write(completionKey);
                completionKey2->status = WRITE;
                completionKey2->buffer = "somedata";
                do_fd2_write(completionKey2);
            }
            else if(completionKey->handle == fd2)
            {
                fd2_read_is_done(completionKey.buffer,completionKey.bufflen);
                completionKey->status = WRITE;
                do_fd2_write(completionKey);
                completionKey1->status = WRITE;
                completionKey1->buffer = "somedata";
                do_fd1_write(completionKey1);
            }
            break;
        case WRITE_EVENT:
            if(completionKey->handle == fd1)
            {
                fd1_write_is_done(completionKey.bufflen);
                completionKey->status = READ;
                do_fd1_read(completionKey);
                completionKey2->status = READ;
                do_fd2_read(completionKey2);
            }
            else if(completionKey->handle == fd2)
            {
                fd2_write_is_done(completionKey.bufflen);
                completionKey->status = READ;
                do_fd2_read(completionKey);
                completionKey1->status = READ;
                do_fd1_read(completionKey1);
            }
            break;
    }

在上面的代码中,会出现一些更改完成键将覆盖挂起的读取或写入的情况,并且结果完成键->状态将是错误的(例如它将报告读取而不是写入),最糟糕的是缓冲区将被覆盖。如果我对完成键使用锁定,则会导致死锁情况。

在查看 WSAsend 或 WSArecv 之后,注意到可以为每个发送或接收设置一个重叠参数。但这会导致两个主要问题。根据 WSAOVERLAPPED 结构:

    typedef struct _WSAOVERLAPPED {
  ULONG_PTR Internal;
  ULONG_PTR InternalHigh;
  union {
    struct {
      DWORD Offset;
      DWORD OffsetHigh;
    };
    PVOID  Pointer;
  };
  HANDLE    hEvent;
} WSAOVERLAPPED, *LPWSAOVERLAPPED;

首先,其中没有放置状态和适当缓冲区的位置,并且其中大部分是保留的。

其次,如果可以解决第一个问题,我需要检查是否没有可用的重叠剩余并且它们都用于挂起的操作,为每次读写分配一个新的,因为客户端会这样忙,它可能会发生很多,此外,管理那些重叠的池是一件令人头疼的事情。所以我错过了什么还是微软搞砸了这个?

而且由于我不需要多线程,还有其他方法可以解决我的问题吗?
提前感谢
编辑
正如我猜想的那样,我在使用重叠结构时提到的第一个问题有答案,我只需要创建另一个包含所有缓冲区和状态等的结构,并将 OVERLAPPED 作为第一个文件。现在你解决我的其他问题;)

4

1 回答 1

1

你真的在这里问了两个不同的问题。我无法回答第一个问题,因为我从未使用过 IO 完成端口,但从我所阅读的所有内容来看,除了专家之外,每个人都最好避免使用它们。(我将指出一个明显的解决方案来解决我认为您正在描述的问题:而不是在另一个写入仍在挂起时将数据实际写入另一个套接字,而是将数据放入队列中并稍后写入。您仍然必须处理给定套接字上的两个同时操作 - 一个读取和一个写入 - 但这应该不是问题。)

但是,很容易使用OVERLAPPED(或WSAOVERLAPPED)结构来跟踪重叠请求的状态。您所做的就是将OVERLAPPED结构作为第一个元素嵌入到更大的结构中:

typedef struct _MyOverlapped
{
  WSAOVERLAPPED overlapped;
  ... your data goes here ...
} MyOverlapped, lpMyOverlapped;

然后将LPWSAOVERLAPPED发送到完成例程lpMyOverlapped以访问您的上下文数据。

或者,如果您正在使用完成例程,则保证不会使用hEvent成员WSAOVERLAPPED,因此您可以将其设置为指向您选择的结构的指针。

我不明白您为什么认为管理重叠结构池会成为问题。每个活动缓冲区只有一个重叠结构,因此每次分配缓冲区时,都要分配相应的重叠结构。

于 2014-01-21T00:47:23.477 回答