简体   繁体   English

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

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

I am trying to implement a Windows named pipe client in C++, that will send RPC requests to a named pipe server written in Go.我正在尝试用 C++ 实现一个 Windows 命名管道客户端,它将向用 Go 编写的命名管道服务器发送 RPC 请求。 It all works for short server response lengths.这一切都适用于较短的服务器响应长度。 However if the length of the server response exceeds 4096 bytes, the client will not read past 4096 bytes and the response is cut short.但是,如果服务器响应的长度超过 4096 字节,客户端将不会读取超过 4096 字节并且响应被缩短。 I have included a minimal reproducible example of the client and server code below, with most of the error handling removed for brevity.我在下面包含了客户端和服务器代码的最小可重现示例,为简洁起见,删除了大部分错误处理。 To reproduce the error, change "some large data string" in the server code into a string of ~5000 characters.要重现该错误,请将服务器代码中的“一些大数据字符串”更改为约 5000 个字符的字符串。

I have tried the following without any luck:我尝试了以下方法但没有任何运气:

  • Setting the length of all buffers to a much larger value than 4096.将所有缓冲区的长度设置为比 4096 大得多的值。
  • Tried using both MESSAGE and BYTE mode in the client and server.尝试在客户端和服务器中同时使用 MESSAGE 和 BYTE 模式。
  • Checked the http response headers: the response is not chunked.检查了 http 响应头:响应没有分块。

Any advice would be most appreciated.任何建议将不胜感激。

C++ client code: 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; 
}

Go server code:转到服务器代码:

//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)
    }
}

The solution I have implemented is as follows:我已经实施的解决方案如下:

  • Call ReadFile first to get the first 4096 (or fewer) bytes.首先调用 ReadFile 以获取前 4096 个(或更少)字节。
  • Call PeekNamedPipe to check for additional data in the pipe, and get the number of bytes ( cbReadPeek in my case) for the next call to ReadFile.调用 PeekNamedPipe 以检查管道中的其他数据,并获取下一次调用 ReadFile 的字节数(在我的情况下为cbReadPeek )。 I have found that PeekNamedPipe is not 100% reliable when called once (it sometimes returns 0 bytes read, even when there is more data in the pipe).我发现 PeekNamedPipe 在调用一次时不是 100% 可靠的(它有时返回 0 字节读取,即使管道中有更多数据)。 I have therefor implemented it as a do {...} while ( fSuccess && cbReadPeek == 0 ) , so it loops until there is either data to read, or fSuccess fails with an error (no more data in the pipe).因此,我将它实现为do {...} while ( fSuccess && cbReadPeek == 0 ) ,因此它会循环直到有数据要读取,或者 fSuccess 因错误而失败(管道中没有更多数据)。
  • Call ReadFile again if there is more data to read.如果有更多数据要读取,请再次调用 ReadFile。 If not, break the loop: if (GetLastError() == ERROR_BROKEN_PIPE) { break; }如果不是,则中断循环: if (GetLastError() == ERROR_BROKEN_PIPE) { break; } if (GetLastError() == ERROR_BROKEN_PIPE) { break; }
  • On every ReadFile iteration, the exact number of bytes in cbRead / cbReadPeek is copied from the buffer (chBuf) and appended to a string, otherwise we end up with some of the previously read data when the bytes read drops below 4096. Or I guess the buffer could be flushed instead.在每次 ReadFile 迭代中,cbRead / cbReadPeek 中的确切字节数从缓冲区 (chBuf) 复制并附加到一个字符串,否则当读取的字节数低于 4096 时,我们最终会得到一些先前读取的数据。或者我猜可以改为刷新缓冲区。

Anyway, it appears to be working fine.无论如何,它似乎工作正常。 I would still really like to know why ReadFile only reads a maximum of 4096 bytes at a time from the pipe.我仍然很想知道为什么 ReadFile 一次最多只能从管道中读取 4096 个字节。 I could not find anything in the MS documentation for the function to explain this behaviour, but maybe I'm missing something obvious.我在 MS 文档中找不到任何解释这种行为的函数的内容,但也许我遗漏了一些明显的东西。 Perhaps this question could be left open for a while to give someone a chance to see it and provide some insight?也许这个问题可以暂时悬而未决,让某人有机会看到它并提供一些见解?

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

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