我是一名Linux程序员,最近参与了将基于epoll的客户端与用c编写的两个文件描述符移植到Windows的工作。
如您所知,在Linux中,使用epoll或select(我知道Windows支持select,但根本没有效率),您可以阻止文件描述符,直到文件描述符
已准备就绪,您可以知道何时可以编写和何时读取。
我看过Windows IOCP,对于Microsoft世界中重叠的io来说还可以。
但是在所有示例中,它都用于多客户端服务器,每个客户端的套接字都独立于其他套接字。
使用完成端口,可以为每个客户端创建一个completionKey结构,并将一个变量放入struct中,并在调用时使其读取
WSArecv和wirt,当WSAsend和另一个变量指示套接字值并从GetQueuedCompletionStatus检索它们以知道该怎么做时(如果对套接字执行了写操作,则进行了读取,反之亦然)。
但就我而言,文件描述符(fd)实际上是重叠的。从一个fd读取,使得对另一fd进行读写,这使得很难知道
GetQueuedCompletionStatus结果中的每个fd都会真正发生操作,因为每个fd都有一个完成密钥。为了清楚起见,请考虑以下事项:
有两个名为fd1和fd2的句柄,completionKey1持有f1的句柄和状态,以及fd2和completedKey2的状态,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;
}
在上面的代码中,出现了一种情况,即某些更改的completionKeys会覆盖未决的读取或写入操作以及结果
completeKey-> status将是错误的(例如,它将报告读而不是写),最糟糕的是缓冲区将被覆盖。如果我使用锁定
completeKeys,它将导致死锁情况。
查看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放在第一个文件中即可。
现在你解决了我其他人;)
最佳答案
您实际上是在问两个不同的问题。我无法回答第一个问题,因为我从未使用过IO完成端口,但是从我读过的所有内容来看,除专家外,最好避免使用它们。 (我将为我认为您正在描述的问题指出一个明显的解决方案:与其将数据实际写入另一个套接字,而又要等待另一个写入,而是将数据放入队列中,然后再写入。您仍然必须在给定的套接字上处理两个同时的操作-一个读和一个写-但这不应该是一个问题。)
但是,使用OVERLAPPED
(或WSAOVERLAPPED
)结构来跟踪重叠请求的状态很容易。您要做的只是将OVERLAPPED
结构嵌入为较大结构中的第一个元素:
typedef struct _MyOverlapped
{
WSAOVERLAPPED overlapped;
... your data goes here ...
} MyOverlapped, lpMyOverlapped;
然后将发送到完成例程的
LPWSAOVERLAPPED
强制转换为lpMyOverlapped
以访问您的上下文数据。或者,如果您使用完成例程,则确保
hEvent
的WSAOVERLAPPED
成员未被使用,因此您可以将其设置为指向您选择的结构的指针。我不明白为什么您会认为管理重叠结构池会成为问题。每个活动缓冲区只有一个重叠结构,因此,每次分配缓冲区时,都要分配一个对应的重叠结构。
关于c - Windows中相关重叠io的编码模式,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/21245606/