首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >IOCP记录和发送

IOCP记录和发送
EN

Stack Overflow用户
提问于 2014-08-20 20:05:14
回答 3查看 2.5K关注 0票数 2

到目前为止,我发现的所有例子要么只是读或写,要么是10000行野兽,我甚至不知道从哪里开始了解它们是如何工作的。

为了测试我的代码,我将浏览器指向我的服务器并发送了一个简单的http请求。结果令人困惑。

例如,GetQueuedCompletionStatus曾经返回,WSARecv表示它读取了我发送的http响应的字节数,尽管这个响应应该(并且确实)在客户端结束,而且recvbuffer甚至没有填充这些字节。

另外,我不明白什么时候在另一个浏览器关闭连接后释放缓冲区,因为GetQueuedCompletionStatus在我调用closesocket之后一直返回几次。

此外,我不知道什么时候有数据可读,什么时候有数据要写,一旦GetQueuedCompletionStatus返回。我可以两个都试一试,看看哪一个失败了,但这似乎很粗鲁。

为了揭示我对IOCP可能存在的任何误解,我编写了一些伪代码来表达我认为我的代码所做的事情:

代码语言:javascript
复制
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
    }
}

实际代码:

代码语言:javascript
复制
#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);
    }
}

示例输出:

代码语言:javascript
复制
/* 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
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2014-08-20 20:40:42

您的伪代码工作流应该更像这样:

代码语言:javascript
复制
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
                }
            }
        }
    }
}

我将把它作为一个练习,让您将其转换为代码。

票数 4
EN

Stack Overflow用户

发布于 2014-08-20 20:56:06

我从逆向工程这段代码获得了我的大部分信息。

我必须警告您不要使用这段代码,尽管它充满了错误和不良行为,它是为更早版本的windows而设计的。

但是一般的原则是相似的,所以这是一个很好的练习,看看你可以从中学到多少(但是不管你做什么,都不用修改就可以使用它)。

最好的代码部分在IOCPDlg.cppIOCPDlg.h中。

IOCP和read的一般原则是,每个端口总是有一个read请求排队。当然,除非你不想读。

在工作线程中完成状态后,您应该执行以下操作:

  • 使用GetQueuedCompletionStatusGetLastError()的返回代码检查是否有错误
  • 处理错误,但过滤超时(读取超时本身不是错误): 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类,您需要为每个请求创建一个新的类,因此需要在每个请求退出队列后释放它。

CodeProject条款中可以找到OVERLAPPEDPLUS的代码。

代码语言:javascript
复制
enum IOType 
{
    IOInitialize,
    IORead,
    IOWrite,
    IOIdle
};

class OVERLAPPEDPLUS 
{
public:
    OVERLAPPED ol;
    IOType ioType;

    OVERLAPPEDPLUS(IOType _ioType)
    {
        ZeroMemory(this, sizeof(OVERLAPPEDPLUS));
        ioType = _ioType;
    }
};
票数 1
EN

Stack Overflow用户

发布于 2015-10-15 01:56:12

您应该有一个状态变量,让您知道您是否正在读取或写入状态变量(或者可能是其他io操作state...you )--将状态变量初始化为在主例程中读取(在发出wsarecv之前),并将状态变量设置为在发送时写入,这样,在工作线程中,您将查询该状态变量,以了解是否正在读取或发送状态变量。

在您的工作线程中:

代码语言:javascript
复制
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
}
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/25413499

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档