登录
首页 >  Golang >  Go问答

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

来源:stackoverflow

时间:2024-03-21 11:54:34 448浏览 收藏

**文章首段摘要:** 在使用 C++ 实现 Windows 命名管道客户端时,客户端无法读取超过 4096 字节的服务器响应。尽管尝试了多种方法,包括调整缓冲区大小和更改消息模式,但问题仍然存在。客户端只能读取响应的前 4096 字节,导致响应被截断。

问题内容

我正在尝试用 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://learn.microsoft.com/en-us/windows/win32/ipc/named-pipe-client

#include  
#include 
#include 
#include 

#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");
    _getch();
    closehandle(hpipe); 
    return 0; 
}

go服务器代码:

//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; }
  • 在每次 ReadFile 迭代中,cbRead / cbReadPeek 中的确切字节数都会从缓冲区 (chBuf) 复制并附加到字符串中,否则当读取的字节数降至 4096 以下时,我们最终会得到一些先前读取的数据。或者我猜缓冲区可以被刷新。

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

理论要掌握,实操不能落!以上关于《C++ 命名管道客户端不会读取超过 4096 字节》的详细介绍,大家都掌握了吧!如果想要继续提升自己的能力,那么就来关注golang学习网公众号吧!

声明:本文转载于:stackoverflow 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>