Skip to content

Commit

Permalink
Add named pipes [Windows] (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
markaren authored Sep 20, 2024
1 parent 91f458f commit c4aea71
Show file tree
Hide file tree
Showing 7 changed files with 240 additions and 5 deletions.
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
A simple cross-platform Socket (TCP, UDP, Web and Unix Domain Sockets) implementation for C++ (no external dependencies)
for education and hobby usage.

On Windows, Named Pipes are also available.

> NOT for use in production.

Expand Down
33 changes: 33 additions & 0 deletions include/simple_socket/Pipe.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@

#ifndef SIMPLE_SOCKET_PIPE_HPP
#define SIMPLE_SOCKET_PIPE_HPP

#include <memory>
#include <string>
#include <vector>

class NamedPipe {
public:
NamedPipe();

NamedPipe(NamedPipe&& other) noexcept;

NamedPipe(NamedPipe& other) = delete;
NamedPipe& operator=(NamedPipe& other) = delete;

bool send(const std::string& message);

size_t receive(std::vector<unsigned char>& buffer);

static std::unique_ptr<NamedPipe> listen(const std::string& name);

static std::unique_ptr<NamedPipe> connect(const std::string& name, long timeOut = 5000);

~NamedPipe();

private:
struct Impl;
std::unique_ptr<Impl> pimpl_;
};

#endif//SIMPLE_SOCKET_PIPE_HPP
23 changes: 18 additions & 5 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
set(publicHeaderDir "${PROJECT_SOURCE_DIR}/include")

set(publicHeaders

"simple_socket/port_query.hpp"
"simple_socket/Socket.hpp"
"simple_socket/TCPSocket.hpp"
Expand All @@ -9,11 +10,6 @@ set(publicHeaders
"simple_socket/WebSocket.hpp"
)

set(publicHeadersFull)
foreach (header IN LISTS publicHeaders)
list(APPEND publicHeadersFull "${publicHeaderDir}/${header}")
endforeach ()

set(privateHeaders
"simple_socket/common.hpp"
"simple_socket/WebSocketHandshake.hpp"
Expand All @@ -27,6 +23,23 @@ set(sources
"simple_socket/WebSocket.cpp"
)

if (WIN32)
list(APPEND publicHeaders
"simple_socket/Pipe.hpp"
)

list(APPEND sources
"simple_socket/Pipe.cpp"
)
endif ()


set(publicHeadersFull)
foreach (header IN LISTS publicHeaders)
list(APPEND publicHeadersFull "${publicHeaderDir}/${header}")
endforeach ()


add_library(simple_socket "${publicHeadersFull}" "${privateHeaders}" "${sources}")
target_compile_features(simple_socket PUBLIC "cxx_std_17")

Expand Down
123 changes: 123 additions & 0 deletions src/simple_socket/Pipe.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@

#include "simple_socket/Pipe.hpp"

#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <vector>
#include <windows.h>


struct NamedPipe::Impl {

HANDLE hPipe_{INVALID_HANDLE_VALUE};

~Impl() {
if (hPipe_ != INVALID_HANDLE_VALUE) {
CloseHandle(hPipe_);
hPipe_ = INVALID_HANDLE_VALUE;
}
}

static std::unique_ptr<NamedPipe> createPipe(HANDLE hPipe) {
auto pipe = std::make_unique<NamedPipe>();
pipe->pimpl_->hPipe_ = hPipe;
return pipe;
}
};

NamedPipe::NamedPipe(): pimpl_(std::make_unique<Impl>()) {}

NamedPipe::NamedPipe(NamedPipe&& other) noexcept: pimpl_(std::make_unique<Impl>()) {
other.pimpl_->hPipe_ = INVALID_HANDLE_VALUE;
}

bool NamedPipe::send(const std::string& message) {
DWORD bytesWritten;
if (!WriteFile(pimpl_->hPipe_, message.c_str(), message.size(), &bytesWritten, nullptr)) {
std::cerr << "Failed to write to pipe. Error: " << GetLastError() << std::endl;
return false;
}
return true;
}

size_t NamedPipe::receive(std::vector<unsigned char>& buffer) {

DWORD bytesRead;
if (!ReadFile(pimpl_->hPipe_, buffer.data(), buffer.size() - 1, &bytesRead, nullptr)) {
std::cerr << "Failed to read from pipe. Error: " << GetLastError() << std::endl;
return -1;
}
buffer[bytesRead] = '\0';
return bytesRead;
}

std::unique_ptr<NamedPipe> NamedPipe::listen(const std::string& name) {
const auto pipeName = R"(\\.\pipe\)" + name;
HANDLE hPipe = CreateNamedPipe(
pipeName.c_str(), // Pipe name
PIPE_ACCESS_DUPLEX, // Read/Write access
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,// Message type pipe
1, // Max. instances
512, // Output buffer size
512, // Input buffer size
0, // Client time-out
nullptr); // Default security attribute

if (hPipe == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to create pipe. Error: " << GetLastError() << std::endl;
return nullptr;
}

std::cout << "Waiting for client connection..." << std::endl;
if (!ConnectNamedPipe(hPipe, nullptr)) {
std::cerr << "Failed to connect pipe. Error: " << GetLastError() << std::endl;
return nullptr;
}

std::cout << "Pipe created successfully!" << std::endl;

return Impl::createPipe(hPipe);
}

std::unique_ptr<NamedPipe> NamedPipe::connect(const std::string& name, long timeOut) {
const auto pipeName = R"(\\.\pipe\)" + name;
const auto start = std::chrono::steady_clock::now();
while (true) {

HANDLE hPipe = CreateFile(
pipeName.c_str(), // Pipe name
GENERIC_READ | GENERIC_WRITE,// Read/Write access
0, // No sharing
nullptr, // Default security attributes
OPEN_EXISTING, // Opens existing pipe
0, // Default attributes
nullptr); // No template file

if (hPipe != INVALID_HANDLE_VALUE) {
return Impl::createPipe(hPipe);
}

if (GetLastError() != ERROR_PIPE_BUSY) {
std::cerr << "Could not open pipe. Error: " << GetLastError() << std::endl;
return nullptr;
}

// Wait for server to become available.
if (!WaitNamedPipe(pipeName.c_str(), 5000)) {
std::cerr << "Pipe is busy, retrying..." << std::endl;
}
const auto now = std::chrono::steady_clock::now();
const auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start).count();

// Check if the timeout has been exceeded
if (elapsed >= timeOut) {
std::cerr << "Connection attempt timed out after " << timeOut << " ms." << std::endl;
return nullptr;
}

std::this_thread::sleep_for(std::chrono::milliseconds(500));
}
}
NamedPipe::~NamedPipe() = default;
8 changes: 8 additions & 0 deletions tests/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@ target_link_libraries(run_tcp_client PRIVATE simple_socket)
add_executable(run_ws run_ws.cpp)
target_link_libraries(run_ws PRIVATE simple_socket)

if (WIN32)
add_executable(run_pipe_server run_pipe_server.cpp)
target_link_libraries(run_pipe_server PRIVATE simple_socket)

add_executable(run_pipe_client run_pipe_client.cpp)
target_link_libraries(run_pipe_client PRIVATE simple_socket)
endif ()

if (UNIX)
target_link_libraries(run_tcp_server PRIVATE pthread)
target_link_libraries(run_tcp_client PRIVATE pthread)
Expand Down
29 changes: 29 additions & 0 deletions tests/integration/run_pipe_client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@

#include "simple_socket/Pipe.hpp"

#include <iostream>

int main() {

auto conn = NamedPipe::connect("PingPongPipe", 500);

if (!conn) return 1;

// Ping-Pong logic
std::string input;
while (true) {
std::cout << "Enter message: ";
std::getline(std::cin, input);

conn->send(input);
if (input == "exit")
break;

std::vector<unsigned char> buffer(512);
const auto bytesRecevied = conn->receive(buffer);
std::string received{buffer.begin(), buffer.begin() + bytesRecevied};
std::cout << "Server: " << received << std::endl;
}

std::cout << "Client shutting down..." << std::endl;
}
27 changes: 27 additions & 0 deletions tests/integration/run_pipe_server.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

#include "simple_socket/Pipe.hpp"

#include <iostream>

int main() {

auto conn = NamedPipe::listen("PingPongPipe");

if (!conn) return 1;

std::vector<unsigned char> buffer(512);
// Ping-Pong logic
while (true) {
const auto len = conn->receive(buffer);
std::string received{buffer.begin(), buffer.begin() + len};
std::cout << "Client: " << received << std::endl;

if (received == "exit")
break;

std::string response = "Pong: " + received;
conn->send(response);
}

std::cout << "Server shutting down..." << std::endl;
}

0 comments on commit c4aea71

Please sign in to comment.