Skip to content

Commit 5248504

Browse files
author
Alexandre jublot
committed
docs: added readme docs
1 parent 6c2f21a commit 5248504

File tree

4 files changed

+352
-0
lines changed

4 files changed

+352
-0
lines changed

docs/DTO.md

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# How to create a custom DTO
2+
3+
## DTO definition
4+
You will need to create your custom DTO to transfer data over network.
5+
A DTO is a simple structure that contains all the fields you want to transfer.
6+
7+
> __Note__ : The structure need to be packed to be sent over the network. No padding must be left over so any client programmed in a different language can read the data correctly.
8+
9+
```cpp
10+
//packing start for MSVC compiler
11+
#ifdef _WIN32
12+
#pragma pack(push, 1)
13+
#endif
14+
15+
// DTO definition
16+
struct SampleDto {
17+
std::string value;
18+
}
19+
20+
#ifdef _WIN32
21+
// packing end for MSVC compiler
22+
;
23+
#pragma pack(pop)
24+
#else
25+
// packing for linux compilers
26+
__attribute__((packed));
27+
#endif
28+
```
29+
30+
## DTO serialization
31+
The library provides a `SerializerTrait` trait to serialize your DTO. There is already a specialization for all standard layout types.
32+
If your DTO has only standard layout types, you can use the `SerializerTrait` trait as is. Otherwise, you will need to specialize it for your DTO.
33+
34+
```cpp
35+
// Specialization of the SerializerTrait for the SampleDto
36+
namespace polymorph::network
37+
{
38+
template<>
39+
struct SerializerTrait<SampleDto>
40+
{
41+
static std::vector<std::byte> serialize(const SampleDto &data)
42+
{
43+
std::vector<std::byte> buffer(data.value.size());
44+
45+
std::memcpy(buffer.data(), data.value.data(), data.value.size());
46+
return buffer;
47+
}
48+
49+
static SampleDto deserialize(const std::vector<std::byte> &buffer)
50+
{
51+
SampleDto dto;
52+
53+
dto.value = std::string(reinterpret_cast<const char *>(buffer.data()), buffer.size());
54+
return dto;
55+
}
56+
};
57+
}
58+
```
59+
It is recommended to define the specialization of the `SerializerTrait` in the same file as the DTO definition.

docs/TCP.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
# Polymorph Network Library (TCP part)
2+
> Here is the documentation for the TCP part. If you want to implement an UDP connection check [this](UDP.md) out.
3+
4+
As mentioned in the [README](/readme.md), the library has 2 major class that you will have to instantiate. For TCP those class are `polymorph::network::tcp::Server` and `polymorph::network::tcp::Client`.
5+
6+
For readability’s sake, namespaces `polymorph::network::` and `polymorph::network::tcp::` will be omitted but keep in mind you need to mention them or use the following directive :
7+
```c++
8+
using namespace polymorph::networ;
9+
using namespace polymorph::network::tcp;
10+
```
11+
12+
13+
## Server
14+
15+
### 1) Creating a server
16+
The `Server` have a constructor that takes a port and a `SessionStore` as parameters. You can create a new Session for the server sake or use one that is already used by **ONE UDP server**.
17+
```cpp
18+
SessionStore sessionStoreServer; // Session store of the server (store any client connection)
19+
// Then, you can create the server
20+
// Simply pass the session store, the mapping and the port you want the server to use
21+
Server server(4242, sessionStoreServer);
22+
```
23+
24+
### 2) Register packet handlers
25+
You can now register callbacks to handle received packet. Here is an example of registering a callback for a packet with a payload of type `SampleDto` :
26+
```c++
27+
server.registerReceiveHandler<SampleDto>(SampleDto::opId, [](const PacketHeader &header, const APyaloadType &payload) {
28+
std::cout << "Server received \"" << payload.value << "\" from client with session id " << header.sId << std::endl;
29+
return true; // or false if you want to disconnect the client
30+
});
31+
```
32+
It is recommended to put a public static variable in all your DTOs to always have their associated opId. This is why ```SampleDto::opId``` is used in the snippet.
33+
34+
🎉 Congratulations, you have created your server and registered your first callback !
35+
36+
### 3) Start the server
37+
Now you will have to start it in order to accept incoming connections. To do so, call the `Server::start()` method.
38+
```cpp
39+
server.start();
40+
```
41+
42+
### 4) Send packets
43+
You can now send packets to your clients. To do so, you will have to get the `SessionId` of the client you want to send a packet to. You can find it in received packet callbacks in the header.
44+
```cpp
45+
server.sendTo<SampleDto>(SampleDto::opId, payload, clientSessionId, [](const PacketHEader &header, const SampleDto &payload) {
46+
std::cout << "Server sent packet to client" << std::endl;
47+
});
48+
// or, to send to all clients
49+
server.send(SampleDto::opId, payload);
50+
```
51+
52+
## Client
53+
54+
### 1) Creating a client
55+
The `Client` have a constructor that takes a host string and a port as parameters . You have to pass the address and the port of a running (or soon) server.
56+
```cpp
57+
Client client("127.0.0.1", 4242);
58+
```
59+
60+
### 2) Registering callbacks
61+
You can now register callbacks to handle received packet. Here is an example of registering a callback for a packet with a payload of type `SampleDto` :
62+
```c++
63+
client.registerReceiveHandler<SampleDto>(SampleDto::opId, [](const PacketHeader &header, const APyaloadType &payload) {
64+
std::cout << "Client received : " << payload.value << std::endl;
65+
return true; // or false if you want to disconnect the client
66+
});
67+
```
68+
69+
### 3) Connecting to the server
70+
You can now call the `Client::connect()` to initiate the connection with the server. You pass a callback which will be called when the server has accepted/refused the connection.
71+
```c++
72+
client.connect([](bool authorized, SessionId id) {
73+
if (authorized) {
74+
std::cout << "Connected with session id " << id << std::endl;
75+
} else {
76+
std::cout << "Connection failed" << std::endl;
77+
}
78+
});
79+
```
80+
81+
### 4) Sending packets
82+
You can now send packets to the server. Here is an example of sending a packet with a payload of type `SampleDto` :
83+
```c++
84+
SampleDto payload;
85+
payload.value = 42;
86+
client.send(SampleDto::opId, payload, [](const PacketHeader &header, const SampleDto &payload) {
87+
std::cout << "Client sent packet to server";
88+
});
89+
```
90+
91+
92+
## Server and Client
93+
You can see a complete example of echo TCP server and client in the [example](/examples/tcpEcho) folder.

docs/UDP.md

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
# Polymorph Network Library (UDP part)
2+
> Here is the documentation for the UDP part. If you want to implement an TCP connection check [this](TCP.md) out.
3+
4+
As mentioned in the [README](/readme.md), the library has 2 major class that you will have to instantiate. For UDP those class are `polymorph::network::udp::Server` and `polymorph::network::udp::Client`. There is also `polymorph::network::udp::Connector` which is really important as it handles send and receive operations
5+
6+
For readability’s sake, namespaces `polymorph::network::` and `polymorph::network::udp::` will be omitted but keep in mind you need to mention them or use the following directive :
7+
```c++
8+
using namespace polymorph::networ;
9+
using namespace polymorph::network::udp;
10+
```
11+
12+
## UDP specifics
13+
The implemented protocol is based on top of UDP so there is no builtin confirmation that a packet has been received. Fortunately, the library provides a builtin resend timeout and acknowledgement of packets.
14+
This is why you have to create a "safeties mapping", which is a map of `OpId` to `bool`. This map will be used to know if a packet is "important" or not and needs to be resent until acknowledgement.
15+
16+
17+
## Server
18+
19+
### 1) Creating a server
20+
The `Server` have a constructor that takes a port and a `SessionStore` as parameters. You can create a new Session for the server sake or use one that is already used by **ONE UDP server**.
21+
You will also have to create a connector with a reference of the created server. Then, you will have to reference the connector in the server with the `Server::setConnector(std::shared_ptr<Connector>)` method.
22+
```cpp
23+
SessionStore sessionStoreServer; // Session store of the server (store any client connection)
24+
// Then, you can create the server
25+
// Simply pass the session store, the mapping and the port you want the server to use
26+
27+
std::map<OpId, bool> safetiesMapping = {
28+
{ SampleDto::opId, true }, // SampleDto is an important packet, it will be resent if we do not receive a confirmation of its reception. It will also send an ACK packet if the server receives a packet with this OpId
29+
{ AnotherDto::opId, false } // This packet is not important, it will be sent once
30+
};
31+
32+
Server server(4242, safetiesMapping, sessionStoreServer);
33+
// Then, you have to create a connector
34+
auto connector = std::make_shared<Connector>(server);
35+
// And finally, you have to reference the connector in the server
36+
server.setConnector(connector);
37+
```
38+
It is recommended to put a public static variable in all your DTOs to always have their associated opId. This is why ```SampleDto::opId``` is used in the snippet.
39+
40+
41+
### 2) Register packet handlers
42+
You can now register callbacks to handle received packet. Here is an example of registering a callback for a packet with a payload of type `SampleDto` :
43+
```c++
44+
server->registerReceiveHandler<SampleDto>(SampleDto::opId, [](const PacketHeader &header, const APyaloadType &payload) {
45+
std::cout << "Server received \"" << payload.value << "\" from client with session id " << header.sId << std::endl;
46+
// Note that before calling the callback, the server will check if the packet is important or not. If it is, it will send an ACK packet
47+
});
48+
```
49+
50+
🎉 Congratulations, you have created your server and registered your first callback !
51+
52+
### 3) Start the server
53+
Now you will have to start it in order to accept incoming connections. To do so, call the `Connector::start()` method.
54+
> __Warning__
55+
> Event if there is a `Server::start()` method, you have to call the `Connector::start()` method. This is because the server is not the one that will handle the incoming connections, it is the connector. The server will only handle the packets received by the connector.
56+
57+
```c++
58+
connector->start();
59+
```
60+
61+
### 4) Send packets
62+
You can now send packets to your clients. To do so, you will have to get the `SessionId` of the client you want to send a packet to. You can find it in received packet callbacks in the header.
63+
```cpp
64+
server.sendTo<SampleDto>(SampleDto::opId, payload, clientSessionId, [](const PacketHEader &header, const SampleDto &payload) {
65+
std::cout << "Server sent packet to client" << std::endl;
66+
});
67+
// or, to send to all clients
68+
server.send(SampleDto::opId, payload);
69+
```
70+
71+
## Client
72+
73+
### 1) Creating a client
74+
The `Client` have a constructor that takes a host string, a port and a `std::map<OpId, bool>` as parameters . You have to pass the address and the port of a running (or soon) server.
75+
The safety mapping should be the same as the one used by the server. You will also have to create a connector with a reference of the created client. Then, you will have to reference the connector in the client with the `Client::setConnector(std::shared_ptr<Connector>)` method.
76+
```cpp
77+
std::map<OpId, bool> safetiesMapping = {
78+
{ SampleDto::opId, true }, // SampleDto is an important packet, it will be resent if we do not receive a confirmation of its reception. It will also send an ACK packet if the client receives a packet with this OpId
79+
{ AnotherDto::opId, false } // This packet is not important, it will be sent once
80+
};
81+
82+
Client client("127.0.0.1", 4242, safetiesMapping);
83+
```
84+
85+
### 2) Registering callbacks
86+
You can now register callbacks to handle received packet. Here is an example of registering a callback for a packet with a payload of type `SampleDto` :
87+
```c++
88+
client.registerReceiveHandler<SampleDto>(SampleDto::opId, [](const PacketHeader &header, const APyaloadType &payload) {
89+
std::cout << "Client received : " << payload.value << std::endl;
90+
// Note that before calling the callback, the client will check if the packet is important or not. If it is, it will send an ACK packet
91+
});
92+
```
93+
94+
### 3) Connecting to the server
95+
You can now call the `Client::connect()` to initiate the connection with the server. You pass a callback which will be called when the server has accepted/refused the connection.
96+
```c++
97+
client.connect([](bool authorized, SessionId id) {
98+
if (authorized) {
99+
std::cout << "Connected with session id " << id << std::endl;
100+
} else {
101+
std::cout << "Connection failed" << std::endl;
102+
}
103+
});
104+
```
105+
106+
### 4) Sending packets
107+
You can now send packets to the server. Here is an example of sending a packet with a payload of type `SampleDto` :
108+
```c++
109+
SampleDto payload;
110+
payload.value = 42;
111+
client.send(SampleDto::opId, payload, [](const PacketHeader &header, const SampleDto &payload) {
112+
std::cout << "Client sent packet to server";
113+
});
114+
```
115+
116+
117+
## Server and Client
118+
You can see a complete example of echo UDP server and client in the [example](/examples/udpEcho) folder.

readme.md

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
# Polymorph Network Library (PNL)
2+
3+
4+
A simple library to use in your networked projects.
5+
6+
* Support for TCP and UDP
7+
* Support for IPv4
8+
* Safe and easy to use
9+
* Cross-platform
10+
* Tested with GCC and Visual Studio
11+
* Tested with Valgrind
12+
13+
14+
## ⚙ How does it work?
15+
16+
The library has a simple API, which is divided into two parts: the `Client` and the `Server`.
17+
They both have pretty much the same API, but some methods are exclusive.
18+
The client and server are designed to exchange Packets.
19+
20+
### What is a packet ?
21+
A Packet just a simple structure containing base information and a payload. The payload can be any type of data.
22+
Since the data transferred over the network needs to be converted to a byte array, the library provides a `SerializerTrait` trait to do that.
23+
Where the library deliver all its power is in the `SerializerTrait` trait. Indeed, this trait can be specialized to serialize user defined types.
24+
25+
As long as you provide a serializer/deserializer for your type, you can send it over the network.
26+
27+
### What is the `SessionStore` ?
28+
The `SessionStore` is a simple class that stores the sessions of the clients connected to the server.
29+
This `SessionStore` can be shared across a UDP and a TCP server. This allows the clients to keep their session between the two servers.
30+
To do so, it generates an authentication token for each client. This token is then sent to the client and is used to identify the client on the other server and safely attribute the same session.
31+
32+
### What is an `OpId` ?
33+
The `OpId` is a simple identifier for a packet. It is used to identify the packet type and call the appropriate callback after casting the received byte stream into a header + payload form.
34+
A DTO type can have multiple `OpId` associated with it. This allows you to have multiple callbacks for the same type.
35+
Example:
36+
```cpp
37+
struct SampleDto {
38+
static constexpr OpId opIdXPos = 0;
39+
static constexpr OpId opIdYPos = 1;
40+
int value;
41+
};
42+
43+
// Registering a callback for the X position
44+
client.registerReceiveHandler<SampleDto>(SampleDto::opIdXPos, [](const PacketHeader &header, const SampleDto &payload) {
45+
std::cout << "Client received X position: " << payload.value << std::endl;
46+
return true;
47+
});
48+
client.registerReceiveHandler<SampleDto>(SampleDto::opIdYPos, [](const PacketHeader &header, const SampleDto &payload) {
49+
std::cout << "Client received Y position: " << payload.value << std::endl;
50+
return true;
51+
});
52+
```
53+
54+
55+
## 📁 Add the library to your project
56+
There is two ways to add the library to your project:
57+
* Add the library as a submodule
58+
* Use the CMake FetchContent module
59+
60+
### Add the library as a submodule
61+
```bash
62+
git submodule add https://github.com/PolymorphEngine/NetworkLibrary.git
63+
```
64+
You will then need to compile it or, if you are using CMake, add it to your project.
65+
```cmake
66+
add_subdirectory(NetworkLibrary)
67+
```
68+
69+
### Use the CMake FetchContent module
70+
You will need to download the [FinderPolymorphNetworkLibrary.cmake](FindPolymorphNetworkLibrary.cmake) (also available in releases)
71+
```cmake
72+
include(path/to/FindPolymorphNetworkLibrary.cmake)
73+
```
74+
Then, you will have to reload the CMake cache and the library will be downloaded and the headers will be available.
75+
76+
## 🔨 How to use the lib ?
77+
78+
### How to create a server/client ?
79+
See the [TCP](docs/TCP.md) and [UDP](docs/UDP.md) to see how to use the library in your project.
80+
81+
### How to create a custom DTO ?
82+
See the [DTO](docs/DTO.md) page to see how to define your DTOs for your protocol.

0 commit comments

Comments
 (0)