Skip to content

Commit dc874cc

Browse files
committed
Clean up and readme
1 parent b90532a commit dc874cc

12 files changed

+161
-84
lines changed

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ CTestTestfile.cmake
1111
_deps
1212
.vs
1313
out
14-
CMakeSettings.json

CMakeLists.txt

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
1-
# CMakeList.txt : CMake project for StreamBase, include source and define
2-
# project specific logic here.
3-
#
1+
project(StreamBase)
42
cmake_minimum_required (VERSION 3.8)
53

64
# Add source to this project's executable.
7-
add_executable (server "server.cpp" "server.h")
8-
add_executable (exampleClient "exampleClient.cpp" "CustomClass.cpp" "CustomClass.h" "client.cpp" "client.h")
9-
add_executable (exampleAsyncClient "exampleAsyncClient.cpp" "CustomClass.cpp" "CustomClass.h" "client.cpp" "client.h")
5+
add_executable (server "server.cpp" "server.h" "common.cpp" "common.h")
6+
add_executable (exampleClient "exampleClient.cpp" "CustomClass.cpp" "CustomClass.h" "client.cpp" "client.h" "common.cpp" "common.h")
7+
add_executable (exampleAsyncClient "exampleAsyncClient.cpp" "CustomClass.cpp" "CustomClass.h" "client.cpp" "client.h" "common.cpp" "common.h")

CMakeSettings.json

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"configurations": [
3+
{
4+
"name": "x64-Debug",
5+
"generator": "Ninja",
6+
"configurationType": "Debug",
7+
"inheritEnvironments": [ "msvc_x64_x64" ],
8+
"buildRoot": "${projectDir}\\out\\build\\${name}",
9+
"installRoot": "${projectDir}\\out\\install\\${name}",
10+
"cmakeCommandArgs": "",
11+
"buildCommandArgs": "-v",
12+
"ctestCommandArgs": "",
13+
"variables": []
14+
},
15+
{
16+
"name": "x64-Release",
17+
"generator": "Ninja",
18+
"configurationType": "Release",
19+
"buildRoot": "${projectDir}\\out\\build\\${name}",
20+
"installRoot": "${projectDir}\\out\\install\\${name}",
21+
"cmakeCommandArgs": "",
22+
"buildCommandArgs": "-v",
23+
"ctestCommandArgs": "",
24+
"inheritEnvironments": [ "msvc_x64_x64" ],
25+
"variables": []
26+
}
27+
]
28+
}

README.md

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,32 @@
1-
StreamBase
1+
# StreamBase
2+
3+
Clients communicate with the server by initially sending a header message containing an Action which specifies whether they want to do a save or a get and what key to use.
4+
5+
## Send
6+
On receiving a send action, the server will expect and read a second message containing the binary archive of the data.
7+
After inserting the data into the store, the server will send a message back to the client notifying it the save has been completed.
8+
9+
## Get
10+
On receiving a get action, the server will retrieve the binary archive for the given key from the store and send it back in a message to the client.
11+
Note that the client-side `get()` function requires the type of the data being returned to be specified.
12+
13+
## Data Store
14+
The server stores data in a map from the given key to a binary archive of the provided object.
15+
The map is guarded by a shared mutex to ensure thread-safe access.
16+
17+
## Serialization
18+
19+
This implementation of StreamBase depends on `cereal` to serialize data.
20+
This means extra code may be required for certain types of data
21+
e.g. for custom classes a `serialize` method that is a `friend` of the class may be required in order to serialize its attributes, as C++ lacks reflection.
22+
23+
## Assumptions
24+
25+
For simplicity it is assumed data being sent fits within a single message, and that cases will conform to the spec meaning there is currently no error handling
26+
e.g. attempting to retrieve data which has not been saved is unhandled, clients expect the server to be running, etc.
27+
28+
## Multithreading
29+
30+
This implementation of StreamBase supports async via multithreading. The server uses a different thread and pipe instance for each client connection.
31+
32+
Multithreaded code uses a custom `log()` function as `cout` is not thread-safe.

client.cpp

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
// client.cpp : Defines the entry point for the application.
2-
//
3-
4-
#include <windows.h>
1+
#include <windows.h>
52
#include <sstream>
63
#include <string>
74
#include <vector>
@@ -12,11 +9,11 @@
129
using namespace std;
1310

1411
void sendHeader(HANDLE h, string key) {
15-
cout << "Sending header." << endl;
12+
log("Sending header.");
1613

1714
auto headerData{ serialize(Action { Type::Send, key }) };
1815
DWORD bytesWritten{ 0 };
19-
const BOOL success = WriteFile(
16+
const bool success = WriteFile(
2017
h,
2118
&headerData[0],
2219
headerData.size(),
@@ -27,14 +24,20 @@ void sendHeader(HANDLE h, string key) {
2724
if (!success) {
2825
throw GetLastError();
2926
}
30-
cout << "Sent header." << endl;
27+
log("Sent header.");
3128
}
3229

3330
bool awaitSendSuccess(HANDLE h) {
34-
std::vector<char> buf(bufSize);
31+
vector<char> buf(bufSize);
3532

3633
DWORD bytesRead;
37-
const BOOL readSuccess = ReadFile(h, buf.data(), buf.size(), &bytesRead, nullptr); // Use ReadFileEx for async
34+
const bool readSuccess = ReadFile(
35+
h,
36+
buf.data(),
37+
buf.size(),
38+
&bytesRead,
39+
nullptr
40+
);
3841
if (!readSuccess) {
3942
throw GetLastError();
4043
}

client.h

Lines changed: 12 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
1-
// client.h : Include file for standard system include files,
2-
// or project specific include files.
3-
4-
#pragma once
1+
#pragma once
52

63
#include <iostream>
74
#include <string>
85
#include <vector>
9-
#include <windows.h>
106
#include "common.h"
117
#include "serialization.h"
128

@@ -36,7 +32,8 @@ void send(string key, T data) {
3632
throw "Couldn't connect.";
3733
}
3834

39-
// else server hasnt yet made another instance for additional clients so wait
35+
// server hasnt yet made another instance for additional clients
36+
// so wait then try again
4037
auto instanceReady{ WaitNamedPipe(pipeName, NMPWAIT_WAIT_FOREVER) };
4138
if (!instanceReady) {
4239
cout << "Couldn't connect: 0x" << hex << GetLastError() << endl;
@@ -45,8 +42,6 @@ void send(string key, T data) {
4542
}
4643
}
4744

48-
cout << "CreateFile " << key << endl;
49-
5045
DWORD mode{ PIPE_READMODE_MESSAGE };
5146
auto modeSet = SetNamedPipeHandleState(
5247
h, // pipe handle
@@ -57,23 +52,21 @@ void send(string key, T data) {
5752
throw "Couldn't put pipe into message mode.";
5853
}
5954

60-
cout << "Mode set " << key << endl;
61-
6255
sendHeader(h, key);
6356
sendData(h, data);
6457
const bool success = awaitSendSuccess(h);
6558

6659
CloseHandle(h);
67-
cout << "Send " << (success ? "success" : "fail") << endl;
60+
log(string{ "Send " } + (success ? "success" : "fail"));
6861
}
6962

7063
template<class T>
7164
T get(string key) {
72-
cout << "Getting data for key " << key << endl;
65+
log("Getting data for key " + key);
7366
auto headerData{ serialize(Action { Type::Get, key }) };
74-
std::vector<char> buf(bufSize);
67+
vector<char> buf(bufSize);
7568
DWORD bytesRead;
76-
const BOOL success = CallNamedPipe(
69+
const bool success = CallNamedPipe(
7770
pipeName,
7871
&headerData[0],
7972
headerData.size(),
@@ -85,18 +78,18 @@ T get(string key) {
8578
if (!success) {
8679
throw GetLastError();
8780
}
88-
cout << "Got data for key " << key << endl;
81+
log("Got data for key " + key);
8982
auto data{ deserialize<T>(string{buf.data(), bytesRead}) };
9083
return data;
9184
}
9285

9386
template<class T>
9487
void sendData(HANDLE h, T data) {
95-
cout << "Sending data." << endl;
88+
log("Sending data");
9689
auto writeStr{ serialize(data) };
9790

9891
DWORD bytesWritten{ 0 };
99-
const BOOL success = WriteFile(
92+
const bool success = WriteFile(
10093
h,
10194
&writeStr[0],
10295
writeStr.size(),
@@ -105,8 +98,8 @@ void sendData(HANDLE h, T data) {
10598
);
10699

107100
if (!success) {
108-
// throw GetLastError();
109101
cout << "0x" << hex << GetLastError() << endl;
102+
throw GetLastError();
110103
}
111-
cout << "Sent data." << endl;
104+
log("Sent data");
112105
}

common.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#include <iostream>
2+
#include <mutex>
3+
#include "common.h"
4+
5+
std::mutex coutLock;
6+
7+
void log(std::string msg) {
8+
std::lock_guard<std::mutex> lg{ coutLock };
9+
std::cout << msg << std::endl;
10+
}

common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
#pragma once
22

33
#include <string>
4+
#include <windows.h>
5+
6+
void log(std::string msg);
47

58
constexpr LPCSTR pipeName{ R"(\\.\pipe\StreamBase)" };
69
constexpr DWORD bufSize{ 512 };

exampleAsyncClient.cpp

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,55 +7,75 @@ using namespace std;
77

88
int main()
99
{
10-
cout << "I am the async client." << endl;
10+
log("I am the async client.");
1111

12-
const auto policy{ std::launch::async };
12+
const auto policy{ launch::async };
13+
14+
const string intKey{ "MyKeyAsync" };
15+
const string strKey{ "She" };
16+
const string customKey{ "CustomClassAsync" };
1317

1418
// sends
1519

16-
auto intSend{ std::async(policy, []() {
17-
cout << "Sending int" << endl;
18-
send("mykeyasync", 101);
20+
auto intSend{ async(policy, [intKey]() {
21+
log("Sending int");
22+
try {
23+
send(intKey, 101);
24+
}
25+
catch (char e[]) {
26+
// manually catch errors since exceptions only propagate on .get()
27+
log(e);
28+
terminate();
29+
}
30+
log("Sent int");
1931
}) };
2032

21-
auto strSend{ std::async(policy, []() {
22-
cout << "Sending string" << endl;
33+
auto strSend{ async(policy, [strKey]() {
34+
log("Sending string");
2335
try {
24-
send("she", string{ "ra" });
36+
send(strKey, string{ "Ra" });
2537
} catch (char e[]) {
2638
// manually catch errors since exceptions only propagate on .get()
27-
cout << e << endl;
28-
std::terminate();
39+
log(e);
40+
terminate();
2941
}
30-
cout << "Sent string" << endl;
42+
log("Sent string");
3143
}) };
3244

33-
auto customSend{ std::async(policy, []() {
34-
cout << "Sending custom class" << endl;
45+
auto customSend{ async(policy, [customKey]() {
46+
log("Sending custom class");
3547
CustomClass custom{ 42, 81 };
3648
custom.incrementA();
3749
custom.incrementB();
38-
send("mycustomclassasync", custom);
50+
try {
51+
send(customKey, custom);
52+
}
53+
catch (char e[]) {
54+
// manually catch errors since exceptions only propagate on .get()
55+
log(e);
56+
terminate();
57+
}
58+
log("Sent custom class");
3959
}) };
4060

4161
// gets
4262

43-
auto intGet{ std::async(policy, [&intSend]() {
63+
auto intGet{ async(policy, [intKey, &intSend]() {
4464
intSend.get(); // await send success
45-
cout << get<int>("mykeyasync") << endl;
65+
log(intKey + " -> " + to_string(get<int>(intKey)));
4666
}) };
4767

48-
auto strGet{ std::async(policy, [&strSend]() {
68+
auto strGet{ async(policy, [strKey, &strSend]() {
4969
strSend.get(); // await send success
50-
cout << get<string>("she") << endl;
70+
log(strKey + " -> " + get<string>(strKey));
5171
}) };
5272

53-
auto customGet{ std::async(policy, [&customSend]() {
73+
auto customGet{ async(policy, [customKey, &customSend]() {
5474
customSend.get(); // await send success
55-
auto savedCustom{ get<CustomClass>("mycustomclassasync") };
75+
auto savedCustom{ get<CustomClass>(customKey) };
5676
savedCustom.incrementA();
5777
savedCustom.incrementB();
58-
cout << savedCustom.getA() << endl << savedCustom.getB() << endl;
78+
log("A=" + to_string(savedCustom.getA()) + ", B=" + to_string(savedCustom.getB()));
5979
}) };
6080

6181
getchar(); // wait before closing

serialization.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#pragma once
22

33
#include <string>
4-
#include "cereal/archives/binary.hpp" // assuming we can use appropriate 3rd party libs
4+
#include "cereal/archives/binary.hpp"
55
#include "cereal/types/string.hpp"
66

77
template<class T>
@@ -12,7 +12,6 @@ std::string serialize(T data) {
1212
cereal::BinaryOutputArchive oarchive{ out };
1313
oarchive(data);
1414
}
15-
// writeStr.c_str() returns a const char * so would need to copy it to get a non-const *
1615
return out.str();
1716
}
1817

@@ -24,6 +23,5 @@ T deserialize(const std::string& serializedData) {
2423
cereal::BinaryInputArchive iarchive{ in };
2524
iarchive(data);
2625
}
27-
// writeStr.c_str() returns a const char * so would need to copy it to get a non-const *
2826
return data;
2927
}

0 commit comments

Comments
 (0)