Skip to content

Commit

Permalink
Add named pipes [windows]
Browse files Browse the repository at this point in the history
  • Loading branch information
markaren committed Sep 20, 2024
1 parent 0f30102 commit 2dd25be
Show file tree
Hide file tree
Showing 7 changed files with 178 additions and 149 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
142 changes: 13 additions & 129 deletions include/simple_socket/Pipe.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,148 +2,32 @@
#ifndef SIMPLE_SOCKET_PIPE_HPP
#define SIMPLE_SOCKET_PIPE_HPP

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

class PipeServer {
class NamedPipe {
public:
PipeServer(const std::string& name) {
hPipe_ = CreateNamedPipe(
name.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
NamedPipe();

if (hPipe_ == INVALID_HANDLE_VALUE) {
std::cerr << "Failed to create pipe. Error: " << GetLastError() << std::endl;
exit(EXIT_FAILURE);
}
std::cout << "Pipe created successfully!" << std::endl;
}
NamedPipe(NamedPipe&& other) noexcept;

void accept() {
std::cout << "Waiting for client connection..." << std::endl;
if (!ConnectNamedPipe(hPipe_, nullptr)) {
std::cerr << "Failed to connect pipe. Error: " << GetLastError() << std::endl;
exit(EXIT_FAILURE);
}
std::cout << "Client connected!" << std::endl;
}
NamedPipe(NamedPipe& other) = delete;
NamedPipe& operator=(NamedPipe& other) = delete;

void send(const std::string& message) {
DWORD bytesWritten;
if (!WriteFile(hPipe_, message.c_str(), message.size(), &bytesWritten, nullptr)) {
std::cerr << "Failed to write to pipe. Error: " << GetLastError() << std::endl;
exit(EXIT_FAILURE);
}
}
bool send(const std::string& message);

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

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

~PipeServer() {
CloseHandle(hPipe_);
}
static std::unique_ptr<NamedPipe> connect(const std::string& name, long timeOut = 5000);

~NamedPipe();

private:
HANDLE hPipe_;
};

class PipeClient {
public:
PipeClient() {}

bool connect(const std::string& name, long timeOut) {

close();

const auto start = std::chrono::steady_clock::now();
while (true) {

hPipe_ = CreateFile(
name.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) {
break;
}

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

// Wait for server to become available.
if (!WaitNamedPipe(R"(\\.\pipe\PingPongPipe)", 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 false;
}

// Optional: Sleep for a short while to prevent busy waiting
std::this_thread::sleep_for(std::chrono::milliseconds(500));// Wait 500 ms before retrying
}

return true;
}

void send(const std::string& message) {
DWORD bytesWritten;
if (!WriteFile(hPipe_, message.c_str(), message.size(), &bytesWritten, nullptr)) {
std::cerr << "Failed to write to pipe. Error: " << GetLastError() << std::endl;
exit(EXIT_FAILURE);
}
}

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

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

void close() {
CloseHandle(hPipe_);
}

~PipeClient() {
close();
}

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

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

set(publicHeaders
"simple_socket/Pipe.hpp"

"simple_socket/port_query.hpp"
"simple_socket/Socket.hpp"
"simple_socket/TCPSocket.hpp"
Expand All @@ -10,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 @@ -28,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;
10 changes: 6 additions & 4 deletions tests/integration/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,13 @@ target_link_libraries(run_tcp_client PRIVATE simple_socket)
add_executable(run_ws run_ws.cpp)
target_link_libraries(run_ws PRIVATE simple_socket)

add_executable(run_pipe_server run_pipe_server.cpp)
target_link_libraries(run_pipe_server 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)
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)
Expand Down
14 changes: 8 additions & 6 deletions tests/integration/run_pipe_client.cpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@

#include "simple_socket/Pipe.hpp"

#include <iostream>

int main() {
PipeClient client;
if (!client.connect(R"(\\.\pipe\PingPongPipe)", 500)) {
return 1;
}

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);

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

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

0 comments on commit 2dd25be

Please sign in to comment.