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. 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. 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.
I have tried the following without any luck:
Any advice would be most appreciated.
C++ client code:
//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:
cbReadPeek
in my case) for the next call to ReadFile. 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). 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).if (GetLastError() == ERROR_BROKEN_PIPE) { break; }
if (GetLastError() == ERROR_BROKEN_PIPE) { break; }
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. I could not find anything in the MS documentation for the function to explain this behaviour, but maybe I'm missing something obvious. Perhaps this question could be left open for a while to give someone a chance to see it and provide some insight?
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.