繁体   English   中英

C++ 命名管道客户端不会读取超过 4096 个字节

[英]C++ named pipe client won't read more than 4096 bytes

我正在尝试用 C++ 实现一个 Windows 命名管道客户端,它将向用 Go 编写的命名管道服务器发送 RPC 请求。 这一切都适用于较短的服务器响应长度。 但是,如果服务器响应的长度超过 4096 字节,客户端将不会读取超过 4096 字节并且响应被缩短。 我在下面包含了客户端和服务器代码的最小可重现示例,为简洁起见,删除了大部分错误处理。 要重现该错误,请将服务器代码中的“一些大数据字符串”更改为约 5000 个字符的字符串。

我尝试了以下方法但没有任何运气:

  • 将所有缓冲区的长度设置为比 4096 大得多的值。
  • 尝试在客户端和服务器中同时使用 MESSAGE 和 BYTE 模式。
  • 检查了 http 响应头:响应没有分块。

任何建议将不胜感激。

C++客户端代码:

//Minimal implementation of C++ named pipe client. Most error handling removed for brevity. 
//Adapted from https://docs.microsoft.com/en-us/windows/win32/ipc/named-pipe-client

#include <windows.h> 
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#define BUFSIZE 1048576

int _tmain(int argc, TCHAR *argv[]) 
{ 
    HANDLE hPipe; 
    const char  *lpvMessage="POST / HTTP/1.0\r\nHost: localhost\r\nContent-Length: 33\r\n\r\n{\"method\":\"test\",\"params\":[\"\"]}\r\n\n";
    char   chBuf[BUFSIZE]; 
    BOOL   fSuccess = FALSE; 
    DWORD  cbRead, cbToWrite, cbWritten, dwMode; 
    LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mypipe.ipc"); 

    // Try to open a named pipe then close it - attempt 1. 
    while (1) 
    { 
        hPipe = CreateFile( 
        lpszPipename,   // pipe name 
        GENERIC_READ |  // read and write access 
        GENERIC_WRITE, 
        0,              // no sharing 
        NULL,           // default security attributes
        OPEN_EXISTING,  // opens existing pipe 
        0,              // default attributes 
        NULL);          // no template file 

    // Break if the pipe handle is valid. 
        if (hPipe != INVALID_HANDLE_VALUE) 
        break; 
        // Exit if an error occurs. 
        _tprintf( TEXT("Could not open pipe. GLE=%d\n"), GetLastError() ); 
        return -1;
    } 
    CloseHandle(hPipe);

    // If successful, open pipe again for use. For some reason, pipe must be opened and closed once (attempt 1) before actually using.  
    hPipe = CreateFile( 
    lpszPipename,   // pipe name 
    GENERIC_READ |  // read and write access 
    GENERIC_WRITE, 
    0,              // no sharing 
    NULL,           // default security attributes
    OPEN_EXISTING,  // opens existing pipe 
    0,              // default attributes 
    NULL);          // no template file 

    // The pipe connected; change to message-read mode. 
    dwMode = PIPE_READMODE_MESSAGE;  //PIPE_READMODE_BYTE doesn't solve the problem either; 
    fSuccess = SetNamedPipeHandleState( 
    hPipe,    // pipe handle 
    &dwMode,  // new pipe mode 
    NULL,     // don't set maximum bytes 
    NULL);    // don't set maximum time
    if ( ! fSuccess) 
    {
    _tprintf( TEXT("SetNamedPipeHandleState failed. GLE=%d\n"), GetLastError() ); 
    return -1;
    }

    // Send a message to the pipe server. 
    cbToWrite = (lstrlen(lpvMessage)+1)*sizeof(char);
    fSuccess = WriteFile( 
    hPipe,                  // pipe handle 
    lpvMessage,             // message 
    cbToWrite,              // message length 
    &cbWritten,             // bytes written 
    NULL);                  // not overlapped 

    do 
    { 
    // Read from the pipe. 
    fSuccess = ReadFile( 
    hPipe,    // pipe handle 
    chBuf,    // buffer to receive reply 
    BUFSIZE*sizeof(char),  // size of buffer 
    &cbRead,  // number of bytes read 
    NULL);    // not overlapped 

    if ( ! fSuccess && GetLastError() != ERROR_MORE_DATA )
    break; 

    printf(chBuf);
    } while ( ! fSuccess);  // repeat loop if ERROR_MORE_DATA 

    printf("\n<End of message, press ENTER to terminate connection and exit>");
    _getch();
    CloseHandle(hPipe); 
    return 0; 
}

转到服务器代码:

//Minimal implementation of Golang named pipe server. Most error handling removed for brevity. 
// +build windows

package main

import (
    "github.com/Microsoft/go-winio"
    "io"
    "io/ioutil"
    "log"
    "net"
    "net/http"
    "os"
)

func main() {
    log.Print("Starting IPC server...")
    StartIPCServer()
}

func HandleDefault(w http.ResponseWriter, req *http.Request) {
    body, _ := ioutil.ReadAll(io.LimitReader(req.Body, 1048576)) 
    defer req.Body.Close()
    log.Printf("Received: '%q'", string(body))
    response:= "some large data string" //If length of response plus http headers >4096 bytes, client will not read past 4096.  
    io.WriteString(w, response)
}

func serve(l net.Listener) error {
    http.HandleFunc("/", HandleDefault)
    return http.Serve(l, nil)
}

func StartIPCServer() {
    var c winio.PipeConfig
    c.SecurityDescriptor = ""
    c.MessageMode = true //changing to false (byte mode) does not solve the problem. 
    c.InputBufferSize = 1048576
    c.OutputBufferSize = 1048576

    path:= `\\.\pipe\mypipe.ipc`
    listener, err := winio.ListenPipe(path, &c)
    log.Print("IPC server running!")
    defer listener.Close()

    err = serve(listener)
    if err != nil {
        log.Fatalf("Serve: %v", err)
        os.Exit(1)
    }
}

我已经实施的解决方案如下:

  • 首先调用 ReadFile 以获取前 4096 个(或更少)字节。
  • 调用 PeekNamedPipe 以检查管道中的其他数据,并获取下一次调用 ReadFile 的字节数(在我的情况下为cbReadPeek )。 我发现 PeekNamedPipe 在调用一次时不是 100% 可靠的(它有时返回 0 字节读取,即使管道中有更多数据)。 因此,我将它实现为do {...} while ( fSuccess && cbReadPeek == 0 ) ,因此它会循环直到有数据要读取,或者 fSuccess 因错误而失败(管道中没有更多数据)。
  • 如果有更多数据要读取,请再次调用 ReadFile。 如果不是,则中断循环: if (GetLastError() == ERROR_BROKEN_PIPE) { break; } if (GetLastError() == ERROR_BROKEN_PIPE) { break; }
  • 在每次 ReadFile 迭代中,cbRead / cbReadPeek 中的确切字节数从缓冲区 (chBuf) 复制并附加到一个字符串,否则当读取的字节数低于 4096 时,我们最终会得到一些先前读取的数据。或者我猜可以改为刷新缓冲区。

无论如何,它似乎工作正常。 我仍然很想知道为什么 ReadFile 一次最多只能从管道中读取 4096 个字节。 我在 MS 文档中找不到任何解释这种行为的函数的内容,但也许我遗漏了一些明显的东西。 也许这个问题可以暂时悬而未决,让某人有机会看到它并提供一些见解?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM