Skip to content
This repository has been archived by the owner on Nov 28, 2020. It is now read-only.

Commit

Permalink
Add WIP Book (#38)
Browse files Browse the repository at this point in the history
* Added book initial book draft
  • Loading branch information
jean-airoldie authored May 28, 2019
1 parent 9f357eb commit dca9b66
Show file tree
Hide file tree
Showing 25 changed files with 664 additions and 130 deletions.
13 changes: 13 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,24 @@ compiler:
- clang
env:
- RUST_BACKTRACE=1 RUST_LOG=error
cache:
- cargo
before_script:
- rustup component add rustfmt clippy
- (test -x $HOME/.cargo/bin/cargo-install-update || cargo install cargo-update)
- (test -x $HOME/.cargo/bin/mdbook || cargo install --vers "^0.2" mdbook)
- cargo install-update -a
script:
- cargo test --all-targets --no-run
- cargo test --all
- cargo test --examples
- cargo fmt --all -- --check
- cargo clippy --all-targets -- -D warnings
deploy:
provider: pages
skip-cleanup: true
github-token: $GITHUB_TOKEN
local-dir: libzmq-book
keep-history: false
on:
branch: master
1 change: 1 addition & 0 deletions libzmq-book/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
book
5 changes: 5 additions & 0 deletions libzmq-book/book.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[book]
authors = ["jean-airoldie"]
multilingual = false
src = "src"
title = "Guide"
16 changes: 16 additions & 0 deletions libzmq-book/src/SUMMARY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Summary

[About](./about.md)

* [Basics](./basics/README.md)
* [Socket](./basics/socket.md)
* [Methods](./basics/methods.md)
* [Patterns](./basics/patterns.md)
* [Advanced](./advanced/README.md)
* [Custom Protocols](./advanced/protocols.md)
* [Examples](./examples/README.md)
* [Basic Request Reply](./examples/basic_req_rep.md)
* [Reliable Request Reply](./examples/reliable_req_rep.md)
* [Secure Request Reply](./examples/secure_req_rep.md)

[Glossary](./glossary.md)
22 changes: 22 additions & 0 deletions libzmq-book/src/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# About

## This is a WIP guide

I believe that `ZeroMQ` is a diamond in the rough that is still a in the early
adoption stage despite being a mature project. In my opinon, the lack of traction
for the library is due to the community rather than the technology itself.

`ZeroMQ` suffers from a significant learning curve due to its foreign concepts.
It requires the programmer to think differently about messaging. When this is
combined with a lacking documentation, it results in very significant time
requirement to properly understand how the library works and how it can be used.

I want this guide to reduce this time requirement by explaining the key concepts of
`ZeroMQ`, give real world usage examples as well as a general vision of how it
could be use. To do so, we will use [libzmq-rs] which is a library aimed at
making `ZeroMQ` dead simple to use.

## Reading Tips
* There is a search-bar friendly glossary at the end of the guide.

[libzmq-rs]: https://github.com/jean-airoldie/libzmq-rs
3 changes: 3 additions & 0 deletions libzmq-book/src/advanced/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Advanced

Now that the basic stuff is taken care off, lets dig deeper.
40 changes: 40 additions & 0 deletions libzmq-book/src/advanced/protocols.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Custom Protocols

For two peers to be able to communicate, they must share a contract. In the
world of communication, these are called protocols. `ZeroMQ` enables
programmer to create protocols that suit their needs by removing most of the
boilerplate.

You might have realized by now that there is no strict concept of request-reply
as a socket operation. Indeed the library does not enforce a client socket
to follow a `send` call by a `recv` call. This does't mean however that this
strict type of request-reply could not be achieved. To do so, a programmer could
easily write the following code:

```rust
// Client side
fn request_reply(&mut self, msg: Msg) -> Result<Msg, Error> {
self.client.send(msg)?;
self.client.recv_msg()?
}

// Server side
fn run(&mut self) -> Result<(), Error> {
loop {
let request = self.server.recv_msg()?;
let reply = self.on_request(request)?;
self.server.send(reply)
}
}
```

This creates an implicit contract between the client and the server.
We will disregard the error handling and timeouts for simplicity.
* The client must send one request at a time and wait for one reply.
* The server must wait for a request and send one reply.

Since contract must be ensured at the application level, it must be properly
documentated for developpers to be able to respect it.

`ZeroMQ` does not enforce a particular messaging protocol, instead
it offers all the tools to build one.
3 changes: 3 additions & 0 deletions libzmq-book/src/basics/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Basics

This is the minimal set of concepts required to get a decent grasp of `libzmq`.
122 changes: 122 additions & 0 deletions libzmq-book/src/basics/methods.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# Basics

These are the basic methods required to use a socket.

## Connect

The socket [connect] method is use to connect to a peer socket bound
at an endpoint to communicate with a peer. Usually a client socket will connect
to a server socket, but it could be the other way around.

```rust
let addr: TcpAddr = "8.8.8.8:420".try_into()?;
client.connect(addr)?;
```

Calling [connect] on a socket is not guaranteed to connect to the peer right
away. Usually, the actual connect call will be delayed until it is needed
(e.g. when sending a message).

Connections in `ZeroMQ` are different from traditional connections is the
sense that they automatically handle failure. For instance, if a connection
fails because of a network error, it will be automatically reconnected if
possible.

Furthermore, to successfully connect to a peer, the handshake corresponding to
the mechanism used must succeed. This handshake is also done in the background
and might fail for various reasons.

## Bind

The socket [bind] method is used to bind a local endpoint to accept connections
from peers. Usually a server socket will bind a known endpoint so that other socket
can connect to it.

```rust
let addr: TcpAddr = "127.0.0.1:*".try_into()?;
server.bind(addr)?;
```

Contrairy to [connect], [bind] will attempt to bind to the endpoint straight
away. If the bind call succeeds, the socket will start to accept connections
attempts to this endpoint.

## Send

This a fundamental operation of a socket used to transfert messages to another
socket. To be able to send messages, a socket must implement the [SendMsg] trait.

```rust
client.send(msg)?;
```

When [send] is called on a socket, it will attempt to queue the message
to its outgoing buffer. If the buffer is full, meaning it has reached the
high water mark, the operation will block. If the [send_timeout] is set
to `None`, the operation will block until the buffer can accomodate for
the message. Otherwise if a duration is specified, it attempt to queue
the message for that duration and if it fails, return [WouldBlock].
the timeout.

There is also the [try_send] method which will return with [WouldBlock] immediately
if it cannot queue the message.

Queued messages are send by a background I/O thread to the peer socket.
For the messages to be actually sent two conditions must be met:
* The connection with the peer socket is up.
* The peer socket can receive messages (its incoming buffer is not full).

Conceptually, a full outgoing buffer can mean many things:
* The connection has crashed temporarily (network error etc.)
* The peer socket has crashed and is restarting.
* The peer socket receives message slower than we can send
them (thus this is a back throttling mechanism)
* Etc.

Many of these scenarios are conceptually indistinguishable. Therefore
the user has to decide what to do depending on the context.

## Recv

You guessed it, [recv] is a socket operation used to receive messages from
another socket. To be able to receive messages, a socket must implement
the [RecvMsg] trait.

```rust
let msg = client.recv_msg()?;
```

Calling [recv] on a socket will attempt to extract a message from its
incoming buffer. If the incoming buffer is empty, the operation will
block until a mesage is received in the buffer. If the [recv_timeout]
is specified, it will try to extract a message from the buffer for the
given duration and return [WouldBlock] if it failed.

There is also the [try_recv] method which, similarly to [try_send], will return
with [WouldBlock] immediately if it cannot queue the message.

The incoming buffer receives message from the background I/O thread from the
peer socket. For the messages to be actually received two conditions must be met:
* The connection with the peer socket is up.
* The incoming buffer is not full.

Conceptually, an empty incoming buffer can mean many things:
* The socket can receive messages faster than what the peer can send.
* The peer has no messages to send.
* The connection has a network error.
* The peer has crashed
* Etc.

Like before, many of these scenarios are conceptually indistinguishable.
We have to decide what to do depending on the context.

[send]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html#method.send
[try_send]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html#method.try_send
[SendMsg]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html
[recv]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html#method.recv
[try_recv]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html#method.try_recv
[RecvMsg]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html
[connect]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.Socket.html#method.connect
[bind]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.Socket.html#method.bind
[send_timeout]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.SendMsg.html#method.send_timeout
[recv_timeout]: https://docs.rs/libzmq/0.1/libzmq/prelude/trait.RecvMsg.html#method.recv_timeout
72 changes: 72 additions & 0 deletions libzmq-book/src/basics/patterns.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Patterns

These are the most basic socket patterns in `libzmq`.

## Client-Server

The `Client-Server` pattern is a advanced asynchronous request-reply pattern.

The [Server] receives messages with a unique [RoutingId] associated with a
[Client]. This [RoutingId] can be used by the [Server] to route replies to the
[Client].
```
<───> client
server <───> client
<───> client
```

The [Client] socket receives upstream messages in a fair-queued fashion
```
server ─┐
server ────> client
server ─┘
```

## Radio-Dish

The `Radio-Dish` pattern is an asynchronous publish-subscribe pattern.

The [Radio] socket send messages in a fan-out fashion to all [Dish]
that [joined] the message's [Group].
```
────> dish
radio ────> dish
────> dish
```

The [Dish] socket receive messages from [Group] it has [joined] in a
fair-queued fashion.
```
radio ─┐
radio ────> dish
radio ─┘
```

## Scatter-Gather

The `Scatter-Gather` pattern is an asynchronous pipeline pattern.

The [Scatter] socket send messages downstream in a round-robin fashion
```
┌──> gather
scatter ────> gather
└──> gather
```

The [Gather] socket receives upstream messages in a fair-queued fashion
```
scatter ─┐
scatter ───> gather
scatter ─┘
```

[Server]: https://docs.rs/libzmq/0.1/libzmq/struct.Server.html
[RoutingId]: https://docs.rs/libzmq/0.1/libzmq/struct.RoutingId.html
[Client]: https://docs.rs/libzmq/0.1/libzmq/struct.Client.html
[Radio]: https://docs.rs/libzmq/0.1/libzmq/struct.Radio.html
[Dish]: https://docs.rs/libzmq/0.1/libzmq/struct.Dish.html
[Group]: https://docs.rs/libzmq/0.1/libzmq/struct.Group.html
[Scatter]: https://docs.rs/libzmq/0.1/libzmq/struct.Scatter.html
[Gather]: https://docs.rs/libzmq/0.1/libzmq/struct.Gather.html
[joined]: https://docs.rs/libzmq/0.1/libzmq/struct.Dish.html#method.join
18 changes: 18 additions & 0 deletions libzmq-book/src/basics/socket.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Socket

The concept of a `socket` in `ZeroMQ` is completely novel. A `ZeroMQ` socket
differs from a traditional `TCP` socket in the following ways (but not limited to):

* A socket sends and receives atomic messages; messages are guaranteed to
either be transmitted in their entirety, or not transmitted at all.
* A socket send and receive messages asynchronously.
* A socket socket can transmit messages over many supported transports, including `TCP`.
* Incoming and outgoing messages can be queued and transmitted asynchronously
by a background I/O thread.
* A socket can be connected to zero or more peers at any time.
* A socket can be bound to zero or more endpoints at any time. Each bound
endpoint can listen to zero or more peers.
* Peer reconnection and disconnection is handled in the background.
* Support for many authentication and encryption strategies via [Mechanism].

[Mechanism]: https://docs.rs/libzmq/0.1/libzmq/auth/enum.Mechanism.html
3 changes: 3 additions & 0 deletions libzmq-book/src/examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Examples

Here are a few examples usage of varying complexity.
11 changes: 11 additions & 0 deletions libzmq-book/src/examples/basic_req_rep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Basic Request Reply

This is as simple as it gets. We have a [Server] that does some request-reply
work in a dedicated thread. We have a [Client] that sends a "ping" and gets
a "pong" back. There is no attempt at security and no attempt at error handling.
For a `INPROC` server, that might be enough.

{{#playpen ../../../libzmq/examples/basic_req_rep.rs}}

[Server]: https://docs.rs/libzmq/0.1/libzmq/struct.Server.html
[Client]: https://docs.rs/libzmq/0.1/libzmq/struct.Client.html
20 changes: 20 additions & 0 deletions libzmq-book/src/examples/reliable_req_rep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Reliable Request Reply

This is a basic example when using the `TCP` transport adapting the code
from the previous `Basic Request Reply` example.

Note that this example does not make any attempt at security.

Since `TCP` is connection oriented transport, we have to take in account that
the connection might fail at any time. We use heartbeating to detect failure
but also `send` and `recv` timeouts to prevent blocking forever.

In this example, the server is protected against failures since it will drop
messages if it is unable to route them before `send_timeout` expires (`WouldBlock`),
or it detects that the peer disconnected via the heartbeats (`HostUnreachable`).

The client in this case will simply fail if it unable to send a request before the
`send_timeout` or unable to receive a reply before the `recv_timeout` (`WouldBlock`).
The client might choose to retry later or connect to another server etc.

{{#playpen ../../../libzmq/examples/reliable_req_rep.rs}}
24 changes: 24 additions & 0 deletions libzmq-book/src/examples/secure_req_rep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Secure Request Reply

The previous example did not offer neither authentication nor encryption.
For a public `TCP` connection, its a must. Let's fix that by adapting the
previous example.

However, this time we will use an external configuration file to get
rid of all the boilerplate. This will also allows our application
to run indepently of the socket configuration.

## Config File

In this case we used `yaml` configuration file, but any file format
supported by `Serde` will work (as long as it supports typed enums).
```yml
{{#include ../../../libzmq/examples/secure_req_rep.yml}}
```

## The code

Aside from the additionnal logic for reading the config file,
the code is now simpler than before.

{{#playpen ../../../libzmq/examples/secure_req_rep.rs}}
Loading

0 comments on commit dca9b66

Please sign in to comment.