到目前为止,我发现的所有例子都只是读或写,或者是10000行兽,我甚至不知道从哪里开始了解它们是如何工作的。
为了测试我的代码,我在我的服务器上指了一个浏览器并发送了一个简单的http请求。结果令人困惑。
例如,一次GetQueuedCompletionStatus返回,WSARecv表示它读取了我发送的http响应的字节数,尽管这个响应应该(并且确实)最终在客户端,并且recvbuffer甚至没有填充那些字节。
另外,当其他浏览器关闭连接时,我不明白何时释放缓冲区,因为GetQueuedCompletionStatus在我调用closesocket后会一直返回几次。
此外,当GetQueuedCompletionStatus返回时,我不知道何时有数据要读取或要写入数据。我可以尝试两种方式,看看哪种方法失败但看起来很粗鲁。
为了揭示我对IOCP的任何误解,我写了一些伪代码来表达我认为我的代码所做的事情:
main {
create server socket
create io completion port
while true {
accept client socket
create completion port for client socket
create recv buffer and send buffer for client
call WSARecv once with 0 bytes for whatever reason
}
}
worker thread {
while true {
wait until GetQueuedCompletionStatus returns
do something if that failed, not quite sure what (free buffers?)
if no bytes were transferred, close socket
try to recv data
try to send data
}
}
实际代码:
#include <stdio.h>
#include <stdlib.h>
#include <winsock2.h>
#define BUFFER_SIZE 1024
typedef struct {
WSAOVERLAPPED overlapped;
SOCKET socket;
WSABUF sendbuf;
WSABUF recvbuf;
char sendbuffer[BUFFER_SIZE];
char recvbuffer[BUFFER_SIZE];
} client;
DWORD WINAPI worker_thread(HANDLE iocp){
DWORD flags = 0, n = 0;
ULONG unused;
client *c;
while (1){
int ret = GetQueuedCompletionStatus(iocp, &n, &unused, (LPOVERLAPPED*)&c, INFINITE);
printf("%3d triggered\n", c->socket);
if (ret == FALSE){
printf("%3d GetQueuedCompletionStatus error %i\n", c->socket, WSAGetLastError());
continue;
}
if (c->socket == INVALID_SOCKET){
printf("error: socket already closed\n");
continue;
}
if (n == 0) {
printf("%3d disconnected\n", c->socket);
closesocket(c->socket);
c->socket = INVALID_SOCKET;
continue;
}
/* how do I know if there is data to read or data to write? */
WSARecv(c->socket, &(c->recvbuf), 1, &n, &flags, &(c->overlapped), NULL);
printf("%3d WSARecv %ld bytes\n", c->socket, n);
WSASend(c->socket, &(c->sendbuf), 1, &n, flags, &(c->overlapped), NULL);
printf("%3d WSASend %ld bytes\n", c->socket, n);
/* TODO handle partial sends */
c->sendbuf.len = 0;
}
return 0;
}
SOCKET make_server(int port){
int yes = 1;
struct sockaddr_in addr;
SOCKET sock;
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
sock = WSASocket(AF_INET, SOCK_STREAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (const char*)yes, sizeof(yes));
bind(sock, (struct sockaddr*)&addr, sizeof(addr));
listen(sock, SOMAXCONN);
return sock;
}
int main(){
const char *text =
"HTTP/1.0 200 OK\r\n"
"Content-Length: 13\r\n"
"Content-Type: text/html\r\n"
"Connection: Close\r\n"
"\r\n"
"Hello, World!";
SOCKET server_socket = make_server(8080);
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
CreateThread(NULL, 0, worker_thread, iocp, 0, NULL);
while (1){
DWORD flags = 0, n = 0;
client *c;
struct sockaddr_in addr;
int addrlen = sizeof(addr);
SOCKET client_socket = WSAAccept(server_socket, (struct sockaddr*)&addr, &addrlen, NULL, 0);
printf("%3d connected\n", client_socket);
CreateIoCompletionPort((HANDLE)client_socket, iocp, 0, 0);
c = (client*)calloc(1, sizeof(*c));
c->socket = client_socket;
c->sendbuf.len = strlen(text);
c->recvbuf.len = BUFFER_SIZE;
c->sendbuf.buf = c->sendbuffer;
c->recvbuf.buf = c->recvbuffer;
strcpy(c->sendbuf.buf, text);
/* for some reason I have to receive 0 bytes once */
WSARecv(c->socket, &(c->recvbuf), 1, &n, &flags, &(c->overlapped), NULL);
}
}
示例输出:
/* Browser makes two tcp connections on socket 124 and 128. */
124 connected
128 connected
/* GetQueuedCompletionStatus returned for socket 124. */
124 triggered
/* We received the browser's http request. */
124 WSARecv 375 bytes
/* Send http response to browser. */
124 WSASend 96 bytes
/* GetQueuedCompletionStatus returned again. */
124 triggered
/* This is wrong, we should not receive our response to the browser. */
/* Also we didn't even receive data here. */
/* recvbuffer still contains the http request. */
124 WSARecv 96 bytes
/* this is ok */
124 WSASend 0 bytes
124 triggered
124 disconnected
/* Why does GetQueuedCompletionStatus still return? the socket is closed! */
/* Also how can I tell when I can safely free the buffers */
/* if GetQueuedCompletionStatus keeps returning? */
-1 triggered
-1 GetQueuedCompletionStatus error 1236
-1 triggered
-1 GetQueuedCompletionStatus error 1236
/* same again for second http request */
128 triggered
128 WSARecv 375 bytes
128 WSASend 96 bytes
128 triggered
128 WSARecv 96 bytes
128 WSASend 0 bytes
128 triggered
128 disconnected
-1 triggered
-1 GetQueuedCompletionStatus error 1236
-1 triggered
-1 GetQueuedCompletionStatus error 1236
128 connected
128 triggered
128 WSARecv 375 bytes
128 WSASend 96 bytes
128 triggered
128 WSARecv 96 bytes
128 WSASend 0 bytes
128 triggered
128 disconnected
-1 triggered
-1 GetQueuedCompletionStatus error 1236
-1 triggered
-1 GetQueuedCompletionStatus error 1236
128 connected
128 triggered
128 WSARecv 289 bytes
128 WSASend 96 bytes
128 triggered
128 WSARecv 96 bytes
128 WSASend 0 bytes
128 triggered
128 disconnected
-1 triggered
-1 GetQueuedCompletionStatus error 1236
-1 triggered
-1 GetQueuedCompletionStatus error 1236
答案 0 :(得分:4)
您的伪代码工作流应该更像是这样:
main {
create server socket
create io completion port
create worker thread
while not done {
accept client socket
associate client socket with completion port
create recv, send, and work buffers for client
call WSARecv with >0 bytes to start filling recv buffer
if failed {
close client socket and free associated buffers
}
}
terminate worker thread
close client sockets
close server socket
}
worker thread {
while not terminated {
call GetQueuedCompletionStatus
if failed {
if failed because of IO error {
close socket and free associated buffers
}
else if not timeout {
handle error as needed
}
}
else if no bytes were transferred {
close socket and free associated buffers
}
else if IO was WSARecv {
move data from recv buffer to end of work buffer
while work buffer has a complete message {
remove message from front of work buffer, process as needed
if output to send {
if send buffer not empty {
append output to end of send buffer, will send later
}
else {
move output to send buffer
call WSASend
if failed {
close socket and free associated buffers
}
}
}
}
call WSARecv with >0 bytes to start filling recv buffer
if failed {
close socket and free associated buffers
}
}
else if IO was WSASend {
remove reported number of bytes from front of send buffer
if send buffer not empty {
call WSASend
if failed {
close socket and free associated buffers
}
}
}
}
}
我会把它作为练习留给你将其翻译成你的代码。
答案 1 :(得分:1)
我从逆向工程中获得了大部分信息this code 我必须警告你不要使用这个代码,虽然它充满了错误和不良行为,它是为更早版本的windows制作的。
但是一般原则是相似的,所以看看你可以从中学到多少是一个很好的练习(但不管你做什么都不用而不经过大量修改)。
最佳代码部分位于IOCPDlg.cpp
和IOCPDlg.h
。
IOCP和Reads的一般原则是每个端口应始终有1个读取请求排队。当然,除非你不想读它。
如果您的工作线程中已完成状态,则应执行以下操作:
使用GetQueuedCompletionStatus
和GetLastError()
的返回代码检查是否有错误:
处理错误,但过滤掉超时(读取超时不是错误):
if (!ReturnValue && GetErrorValue != ERROR_SEM_TIMEOUT ) {
/-- i free up the socket and associated buffers here --/
continue;
}
流程IO。我使用以下内容来区分IO(我将在底部添加代码):
/-- get the base address of the struct holding lpOverlapped --/
pOverlapPlus = CONTAINING_RECORD(lpOverlapped, OVERLAPPEDPLUS, ol);
如果刚刚处理的IO类型为IORead
,则排队另一个读取。
不应在此线程中进行发送,不同的线程/功能应该将发送IO请求添加到IOCP。
释放OVERLAPPEDPLUS类,您需要为每个请求创建一个新类,因此您需要在每个请求出列后将其释放。
OVERLAPPEDPLUS的代码,可以在CodeProject Article。
中找到enum IOType
{
IOInitialize,
IORead,
IOWrite,
IOIdle
};
class OVERLAPPEDPLUS
{
public:
OVERLAPPED ol;
IOType ioType;
OVERLAPPEDPLUS(IOType _ioType)
{
ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
ioType = _ioType;
}
};
答案 2 :(得分:0)
你应该有一个状态变量让你知道你是在读还是写(或者可能是其他io操作状态......你初始化那个状态变量以在你的主程序中读取(在发出wsarecv之前),并设置每次发送时要写入的状态变量,在工作线程中查询该状态变量以了解您是在阅读还是发送
在你的工作线程中:
switch (state_var)
{
case IO_READ:
//process wsarecv
//process data
break;
case IO_SEND:
//process wsasend
//send more data
//if there are no more to send
//state_var = IO_READ;
//call wsarecv
break;
//process other io command you define
}