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

问题描述 投票:1回答:1

我正在尝试用C ++实现Windows命名管道客户端,它将RPC请求发送到用Go编写的命名管道服务器。所有这些都适用于较短的服务器响应长度。但是,如果服务器响应的长度超过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)
    }
}
c++ go named-pipes
1个回答
0
投票

首先调用ReadFile以获得前4096(或更少)个字节。

    调用PeekNamedPipe来检查管道中的其他数据,并获取下一次调用ReadFile的字节数(在我的情况下为cbReadPeek)。我发现一次调用PeekNamedPipe并不是100%可靠的(即使管道中有更多数据,有时也会返回读取的0字节)。为此,我将其实现为do {...} while ( fSuccess && cbReadPeek == 0 ),因此它将循环运行直到有数据要读取,或者fSuccess失败并显示错误(管道中没有更多数据)。
  • 如果要读取的数据更多,请再次调用ReadFile。如果不是,请中断循环:if (GetLastError() == ERROR_BROKEN_PIPE) { break; }
  • 在每个ReadFile迭代中,将从缓冲区(chBuf)复制cbRead / cbReadPeek中的确切字节数,并将其附加到字符串中,否则当读取的字节降至4096以下时,我们将得到一些先前读取的数据。或者我想缓冲区可以刷新。
  • 无论如何,它似乎工作正常。我仍然非常想知道为什么ReadFile一次只能从管道读取最多4096个字节。我在MS文档中找不到用于该功能的任何功能来解释这种行为,但也许我缺少明显的东西。也许这个问题可以搁置一段时间,让某人有机会看到它并提供一些见识?
  • © www.soinside.com 2019 - 2024. All rights reserved.