如何使用命名管道(C++服务器、C#客户端)

本文关键字:服务器 客户端 C++ 何使用 管道 | 更新日期: 2023-09-27 18:27:38

我正在尝试开始使用命名管道,因为我将来需要在我的项目中使用它们。

目前,我有一个C++服务器,它会等待客户端连接并发送测试消息。我大致按照本教程开始学习。相关代码如下:

    #define MESSAGE L"TestMessage"
HANDLE hnamedPipe = INVALID_HANDLE_VALUE;
hnamedPipe = CreateNamedPipe(
    L"''''.''pipe''testpipe",
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_MESSAGE|
    PIPE_READMODE_MESSAGE|
    PIPE_WAIT,
    PIPE_UNLIMITED_INSTANCES,
    1024,
    1024,
    NMPWAIT_USE_DEFAULT_WAIT,
    NULL);
if(hnamedPipe == INVALID_HANDLE_VALUE)
{
        cout << "Failed" << endl;
}
while(true)
{
    cout<< "Waiting for client"<< endl;
    if(!ConnectNamedPipe(hnamedPipe,NULL))
    {
        if(ERROR_PIPE_CONNECTED != GetLastError())
        {
        cout << "FAIL"<< endl;
        }
    }
    cout<<"Connected!"<<endl;
    //Send over the message
    wchar_t chResponse[] = MESSAGE;
    DWORD cbResponse,cbWritten;
    cbResponse = sizeof(chResponse);
    if(!WriteFile(
    hnamedPipe,
    chResponse,
    cbResponse,
    &cbWritten,
    NULL))
    {
        wprintf(L"failiure w/err 0x%08lx'n",GetLastError);
    }
    cout<<"Sent bytes :)" << endl;
}

客户端代码(C#)如下:

        using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "testpipe", PipeDirection.InOut))
        {
            while (true)
            {
                Console.WriteLine("Connecting to server...");
                pipeClient.Connect();
                Console.WriteLine("Connected :)");
                Console.WriteLine(pipeClient.ReadByte());
                pipeClient.Close();
                Console.WriteLine("Closed");
            }
        }

目前,我已经让客户端成功连接到服务器,它打印出第一个字节。我想知道如何做两件事:

  1. 读取整个消息-我尝试通过pipeClient使用StreamReader读取消息,但它无限期地挂在ReadLine()上。

  2. 持续发送消息-我希望服务器向客户端发送一条又一条消息,客户端将一次读取一条消息并打印出来。我对IPC有点不了解,所以一开始我试图在while(true)循环中使客户端断开连接并重新连接到服务器,而服务器处于while true循环中,该循环在顶部总是等待新的客户端连接,然后再发送另一条消息。我的尝试在上面的代码中。

如有任何帮助,我们将不胜感激。最终目标是将图像从服务器发送到客户端。然后客户端会将它们实时打印到屏幕上。在尝试图像数据之前,我想用简单的字符串消息来实现这一点。

编辑:

最终,我希望能够从客户端向服务器发送一条消息,指示它想要获得最新的图像帧,然后服务器将发送最新的帧,然后客户端将在屏幕上显示该帧。所以流程是:

  1. 客户端->服务器:指示客户端需要最新的帧信息。(一些简单的东西,可能是一个值为1的无符号int)
  2. 服务器->客户端:最新帧信息。(640x480图像存储在具有RGB字节值的字节阵列中)
  3. 客户端:在显示器上显示框架

如何使用命名管道(C++服务器、C#客户端)

ReadLine挂起,因为它正在等待测试消息中不包含的换行符。

如果您希望服务器连续发送消息,只需在WriteFile调用周围设置一个循环。您不需要连接多次。类似地,在客户端中,将循环放在ReadLine周围。

如果每条消息都由换行符终止的文本组成,那么这就足够了,但如果你真的想让管道客户端以消息模式工作,你需要调用:

pipeClient.ReadMode = PipeTransmissionMode.Message;

然而,我怀疑这是否会与StreamReader很好地交互。相反,您应该使用pipeClient.Read阅读单个消息。

更新

回答您的新问题:

在服务器上,一旦客户端连接,就进入一个循环,其中:

  • 服务器从客户端进行读取。这将被阻止,直到客户端请求帧为止
  • 服务器发送一个帧

在客户端上,一旦连接到服务器,就进入一个循环,其中:

  • 客户端发送"请发送帧"消息
  • 客户端从服务器读取帧
  • 客户端显示框架

我不会使用消息模式管道。如果帧的大小是固定的,那么客户端就知道要从服务器读取多少数据。否则,在帧之前添加一个包含其长度的uint。

这是一个使用命名管道将字符串从C#应用程序(客户端)发送到C++应用程序(服务器)并在C++应用程序控制台中显示接收到的消息的简单示例,这两个应用程序都是Visual Studio中的控制台应用程序。

C#客户端应用程序代码

using System.IO.Pipes;
using System.Text;
namespace CSclient
{
    class Program
    {
        static void Main(string[] args)
        {
            // Create Named Pipes
            using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "mynamedpipe", PipeDirection.InOut))
            {
                string message = "Test message from C# client!";
                // Connect Named Pipes
                pipeClient.Connect();
                byte[] messageBytes = Encoding.UTF8.GetBytes(message);
                // Send the message to the server
                pipeClient.Write(messageBytes, 0, messageBytes.Length);
            }
        }
    }
}

C++服务器应用程序代码,下面的链接被用作引导管道服务器

#include <windows.h> 
#include <stdio.h> 
#include <tchar.h>
#include <strsafe.h>
#include <iostream>
#include <string>
#define BUFSIZE 512
DWORD WINAPI InstanceThread(LPVOID);
VOID GetAnswerToRequest(char*, LPTSTR, LPDWORD);
int _tmain(VOID)
{
    BOOL   fConnected = FALSE;
    DWORD  dwThreadId = 0;
    HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
    LPCTSTR lpszPipename = TEXT("''''.''pipe''mynamedpipe");
    // Create Named Pipe
    hPipe = CreateNamedPipe(
        lpszPipename,             // pipe name 
        PIPE_ACCESS_DUPLEX,       // read/write access 
        PIPE_TYPE_MESSAGE |       // message type pipe 
        PIPE_READMODE_MESSAGE |   // message-read mode 
        PIPE_WAIT,                // blocking mode 
        PIPE_UNLIMITED_INSTANCES, // max. instances  
        BUFSIZE,                  // output buffer size 
        BUFSIZE,                  // input buffer size 
        0,                        // client time-out 
        NULL);                    // default security attribute 
    // Connect Named Pipe
    fConnected = ConnectNamedPipe(hPipe, NULL) ? TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);
    if (fConnected)
    {
        HANDLE hHeap = GetProcessHeap();
        char* pchRequest = (char*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(char));
        TCHAR* pchReply = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE * sizeof(TCHAR));
        DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
        BOOL fSuccess = FALSE;
        // Read client requests from the pipe. This simplistic code only allows messages
        // up to BUFSIZE characters in length.
        fSuccess = ReadFile(
            hPipe,        // handle to pipe 
            pchRequest,    // buffer to receive data 
            BUFSIZE * sizeof(char), // size of buffer 
            &cbBytesRead, // number of bytes read 
            NULL);        // not overlapped I/O 
        if (!fSuccess || cbBytesRead == 0)
        {
            std::cout << "Reading error!";
        }
        // Process the incoming message.
        GetAnswerToRequest(pchRequest, pchReply, &cbReplyBytes);
    }
    return 0;
}
// This routine is a simple function to print the client request to the console
VOID GetAnswerToRequest(char* pchRequest, LPTSTR pchReply, LPDWORD pchBytes)
{
    std::string requestMessage = pchRequest;
    // Show the message in the console
    std::cout << requestMessage.c_str();
}