- So I'd like to thank each
and every one of you personally
for attending this talk.
It's called Get-rich-quick
with Beast WebSockets
and networking TS.
My name is Vinnie Falco
and I'm the founder
and president of the C++ Alliance,
it's a nonprofit that tries to
advance the interests of C++.
I'm also the author of Beast
which is a Boost library
that implements the HTTP
and WebSocket protocols.
It uses Boost.Asio as
its networking layer,
you're gonna see how that
plays into the networking TS.
You can visit the repository
for documentation and examples.
I'd like to say a few words about Boost.
It's a wonderful collection
of peer reviewed libraries,
two of its great features
are the permissive license
which allows you to do almost
anything that you want,
and then it has a fantastic review process
where experts in the
field, review the library
and they offer feedback to the author
and they determine whether
the library is accepted
and what needs to happen
in order for it to go in.
So it results in really
high quality stuff.
You've probably seen
components of Boost going
to the standard library,
It's great.
So if you have a laptop
here or a tablet or a phone
and you want to browse the source code
while I'm showing it on the slide,
so you can see the whole file,
this is where the repository is at.
Everything that I'm
gonna show you compiles
and it runs, there's a
docker file in the repository
if you want to deploy it to a cloud
and run the server that
I'm gonna show you.
I'm gonna have to move
a little bit quickly
there's a lot of material to cover here
so it's very unlikely
that you're gonna be able
to pick up everything
from just one viewing.
I encourage everyone to go to YouTube,
watch the video, every time you watch it
you're probably gonna
learn a little bit more,
this material is can be
a little bit advanced
if you want to go into the
deep functionality of it.
So that said so who's ready to get rich?
(laughing)
So it's not just a gimmicky title,
we're actually going to
look at some technologies
that have been incredibly successful
and I'm gonna do a case study
of two programs one of them
is called Agar.io and the
other one is called slither.io.
These are both heavily
multiplayer games that run in
the browser and you go
up against other people
and hopefully you stay alive,
if not your game ends pretty quickly.
Each of these programs
was individually authored
by one person each ,they're free to play,
anyone can open up their
browser and go to the website
and they can start playing this game,
there's no installation or it's very easy
and accessible to get into,
you can play a quick game.
So each instance of the game can host
hundreds of people at once
So you're going up against a
lot of people in this arena
and they brought in at
their peak $100000 per day
in advertising revenue.
So what's the secret sauce.
It's a WebSocket server written in C++
that runs in the cloud and then
you have a WebSocket client
that runs in the browser.
Running in the browser is a great choice
it does a lot of things for you
and I think you're gonna see
when I show you that stuff
that it's pretty great.
So in this talk I'm going to review
the two key protocols HTTP and WebSocket,
then I'm going to show you some concepts
in the upcoming networking TS
that are absolutely critical
then we're going to look at some code
which is available online and it's for
a multi-user chat server.
You can connect to the server
and you can talk to other
people that are there,
exchange messages, it looks very nice
and then I'll have a few closing words.
So the protocols that we need to care
about are HTTP and WebSocket.
HTTP powers the World Wide Web,
it divides computers
up into two categories,
you have clients and you have servers.
The client establishes a
connection to the server
and once that connection has been made
then an HTTP conversation can take place.
It starts when the client sends a message
called an HTTP request
which has a certain format.
The server receives the
message and performs
some computation and
then it sends that back
the result which is
called an HTTP response.
So these are two types of messages
and then once that happens,
the client can send another request
and the server sends another response
and then that process continues
until either side decides
that it wants to close the connection.
An interesting feature of HTTP
is that it's transportable
by intermediate computers
called proxies or firewalls.
So you can have a computer in the middle,
and it'll relay the message,
it understands the HTTP protocol
it can do things like enforce policy,
effect security, disallow
certain activities
or shape the traffic according
to corporate policies.
So that's HTTP.
WebSocket is developed as
an improvement of HTTP.
One of the problems with HTTP is that
it's inherently a half-duplex protocol.
The server has to wait
for the client to send
the request before it can take any action.
The server can't just send
an unsolicited message
to the client in HTTP.
So WebSocket is developed
to ameliorate that problem
and it's what we call
full duplex and symmetric.
That means in a WebSocket
session either side
can send a message at any
time without being prompted
and they can send multiple messages
they can do whatever they want.
The contents of the
message are completely up
to the application, there's
no predefined format
you could send text, you could send Jason,
it could be binary format,
it's whatever the two endpoints agreed to.
Like HTTP, its transportable by proxies.
An interesting feature
of WebSocket is that
it divides messages up
into packets called frames.
Each frame has a well-defined
length and a message
can have zero or more
frames associated with it.
There's three special types of
messages close, ping and pong
which they do what they sound like,
they allow you to control the
meta attributes of the session
So in order to talk WebSocket
of course you need to have a
connection, but a WebSocket
connection starts out as HTTP.
So the client connects to
the server and they send
a special type of HTTP request
called an upgrade request.
They ask the server I'd
like to talk some WebSocket.
So in that request, you
can see it's over here,
it's an HTTP GET request
and it has the special
fields upgrade and connection
and then some protocol
specific gobbledygook
we don't need to get into
that, it has to be there.
So that's the request,
the target the URI here
it's chat.cgi allows the
client to differentiate
which WebSocket service
it wants for the case
where the server has more
than one WebSocket service
on the same port.
So the server sees that
the client wants to do
a WebSocket upgrade and
then if it likes the client,
if everything checks out,
the server will send
back the upgrade response
which is a 101 status code.
It means that it going to switch protocols
and as soon as the server is
done sending that response
from that point onwards, all of the bytes
that are transferred
between those two end points
are gonna be WebSocket protocol.
So now they're gonna talk WebSocket.
So Beast fits into this by
providing HTTP interfaces,
WebSocket interfaces, one
of the interesting features
about Beast, is that it doesn't
try to reinvent the wheel,
so when you make a connection
to a server using Beast
you don't actually use
Beast for that you use
the networking TS or
you use your boost.Asio.
You use those api's to
establish the connection
and then once you have
that socket connected,
then you can do some
transactions with Beast.
So the networking TS, you've
probably heard about it
it's been 17 years in
the making in reality
and it provides an
abstraction for networking
so that your program can work
anywhere that networking TS
is implemented in the standard library.
It comes in three flavors: networking TS,
reference implementation,
boost.Asio and Asio.
They're all largely the
same, in fact the source code
for each flavor is produced from
a single source using a script.
The only major difference
is where you're gonna get
the header files, the
name of the header file
and then the namespaces can
be a little bit different.
One major difference is
that Asio and Boost.Asio
have features that are not
yet in the networking TS
and won't go in for the first iteration,
such as support for capturing signals,
I believe serial ports, not
exactly sure about that one.
So I'll show you some
basics of networking TS
we're all going to need to
know it 'cause it's coming.
The first thing you need
to have is something called
an I/O context, in order
to do any type of I/O,
you have to have it.
It needs to be there.
We've just declared one called ioc.
Now we're going to create a socket,
so the socket has to be
associated with an i/o context.
Here's our socket.
Now once that socket is connected,
we might want to write some data to it.
So here you can see we're calling
this function called write
I use the net name space alias to show you
that it's networking TS.
The actual name space might
differ depending on the flavor
that you're using, you all are smart
I'm sure you can figure that out.
So here we're calling it write on a socket
and then there's some B there,
presumably that's a buffer
and then it seems to return
the number of bytes that
it was able to write.
So what what's the type of B?
So we know we need to have
some kind of memory area
with some bytes, B can't
be a simple pointer
that's not enough.
So I'm gonna show you two types
that networking TS provides
that are suitable types for B,
mutable buffer and Const buffer.
As the name suggests these
are nothing more than
a thin wrapper around a pointer in a size.
In one version the pointers Const
and the other one it's non Const.
So this is really straightforward,
there's no mystery to it,
it's the equivalent of like
a fancy pair with the good name
and some accessor functions.
So now we're gonna write
a hypothetical function.
We're gonna write this
function called send message.
This function accepts a
buffer from the caller
and it sends it on the
socket in a special format
which is a header that's
calculated from the data,
it's fixed in length and then the payload
which the caller provides.
You can see in the function signature
we have the stream which
is some templated type
that we won't get into and
then we have the payload
which is that Const buffer
type that you just saw.
That wrapper around a pointer in size.
So how would we implement that?
Well, here's a one way to
implement it we call write
and then we construct the header
and we passed that as a buffer
and then we call write again
and then we pass the payload.
So we're doing it in two
steps we're writing the header
then we're writing the payload.
So that's not such a
great idea and the reason
is because I/O operations
are very expensive.
If you're writing a network
program and all that you do
is try to minimize the number of I/Os,
then by the time you're done
it's probably gonna work pretty well,
so you're gonna do pretty well with it,
if you don't pay any attention to that
you might find that it's not
functioning very well at all.
It'll perform poorly.
So it would be nice if we
could do that in a single call.
So here's one way we might do it,
we're creating an array of two elements
and it has those two buffers in it.
So wouldn't it be nice if we
could just call right once
and then pass that array
which is a type of range.
It turns out you can do that,
here's the signature for the
networking TS write function,
you can see that it's
templated on this thing called
a const buffers sequence.
So what's a constable for sequence?
Well, it's a type of range that
has a zero or more elements
where each element is an actual buffer
one of those const buffer that
you saw a pointer in a size.
It needs to be lightweight and copyable.
It doesn't own the memory
it just refers to memory
that's owned elsewhere.
So some other object has to maintain
the lifetime of it in
order for those operations
to work correctly.
Beast comes with several
different buffer sequence types
optimized for various use cases.
Here's the actual requirements
for a const buffer sequence.
This diagram this is called
a valid expressions list.
It's a table that shows all
the valid expressions on a type
that meets the requirements
along with some notes.
You can see that a const buffer sequence
has two nested types a value
type and const iterator.
Buffer sequence has to be copyable
and then it has a familiar
begin and end functions
which you expect to find in a range
and then we have a
little special carve out
where we say that Const
buffer and mutable buffer
are a const buffer sequences as well.
So a special case those
two types can be used
wherever a buffer sequence is expected.
When you dereference the iterator
you have to get something
that can give you a const buffer.
So whatever fancy a
range or lazy evaluation
you wanna do, at the end of the day
each element of that range has to has
to pony up a pointer in size.
Mutable buffer sequence
is another concept.
It's just like the const
buffer sequence except
it's writable.
Every mutable buffer sequence is also
a const buffer sequence.
So that's that.
Now how do we use it?
Let's say we have a string,
we're gonna construct a string
and now we want to write it.
So we're constructing a Const
buffer and we're passing
the pointer and size of the string.
That's a little bit inconvenient,
so networking TS provides a
function it's called buffer,
it'll take the string and it'll construct
that Const buffer for us.
It's a nice convenience function.
Almost every type in the standard library
that can be used as a buffer
has an overload in networking
TS for the buffer function
that you can use to conveniently
get a buffer to that thing.
So those buffer sequences
are nice but sometimes
we need something a little bit different.
Here's a function called read
until which we're calling.
This function is a networking TS function
which will read from the socket
and and it'll store the data
in our buffers until it
finds a certain pattern.
In this case, a double
carriage return line feed.
So what's the type of B?
We know that it can't be a buffer sequence
because but four sequences
have no concept of resizing.
It's just a range or a view
to some memory regions.
So here's the signature for
networking TS read until
you can see rather than a buffer sequence
we have this new type
called the dynamic buffer.
So this is the second important
concept in networking TS,
a dynamic buffer is used very
often especially in Beast.
So here's what it is, it's
a resizable buffer sequence,
it's got two areas there's
a read area and a write area
and as you can imagine you can
you read from the read area
and you put the bytes in the write area.
When you don't know the size of the input
and you want to write an algorithm
or you want to invoke an
algorithm that can return
a variable amount of data dynamic buffer
is a good choice for the signature.
Now this is a concept which means
that user defines dynamic
buffers are possible,
again Beast comes with a nice
variety of dynamic buffers
that you can use , optimize
for various scenarios.
I'm gonna show you how
these dynamic buffers work through code.
So here we have a dynamic buffer
called a we're declaring it
it starts out empty,
the read and the write
areas are zero bytes.
So the first thing we want to
do is get some bytes in there.
We call a read sum function on our socket
and then you can see we're calling prepare
on the dynamic buffer with the number 128.
So we're resizing the write area
to have 128 bytes of possible storage.
And then we're calling read some,
so that reads sum is gonna
hopefully get some bytes
from the socket and put
it in that right area
and then the number of bytes that
it was able to actually
acquire is gonna be returned.
So now we have some bytes and now
we wanna make them available
so we call a function called
commit on those bytes.
Let's say we got 60 bytes.
So we had enough room
for 128, we only got 60
now we call commit and
that moves those bytes
from the write area to the read area
so now they're available for reading.
So now we want to process that data,
I have this function called
process, it processes the data
and we're calling a function
on the dynamic buffer called data.
This returns a Const buffer sequence,
let's say that process only
uses the first 40 bytes
and it returns the
number 40 for bytes used.
So now that we're done
with those 40 bytes we want
to get rid of them, we don't
want to see them anymore.
so we call a function called
consume on the dynamic buffer
we had 60, we consumed
40, now we're left with 20
at this point we could read
some more and commit that
or we could deal with those 20 bytes
and maybe get rid of the, who knows?
So you saw the valid expressions table
for dynamic buffer I'm going to show you
a different form of documentation,
this is called an exemplar.
It's a C++ declaration
that hopefully compiles
or mostly compiles that
contains all the elements
that are required for the type.
Here we have a prototype
of dynamic buffer,
you can see it's got some nested types
it has some accessor
functions related to the size,
various sizes and then
we have the functions
that I showed you data
prepare, commit and consume.
That's dynamic buffer.
The next important concept
is Asynchronous I/O.
Here we have a call to read some,
this is a synchronous function ,
that means that when you call read some
it's gonna block until
it fulfills its contract.
That threat of execution is
going to be stuck there until
it either generates an
error or return some bytes.
But very often we want to
write asynchronous programs,
they perform better, they scale better
and they're a little more complicated
but we need to do this thing.
This is what that looks like.
Asynchronous operations in networking TS
all start with the word async underscore.
Here we're calling an
asynchronous function,
we're gonna read some
data into some buffers
and as soon as we call async read some
that function call is gonna return
it's not gonna block and the operation
is gonna be happening in the background
and we need a way to to know
when the operation is done,
so we can look at those buffers.
This is that handler
parameter that's there,
so what's the type of that?
Well here's what one possibility is,
we have a lambda expression with
a particular signature and that lambda
is gonna be invoked, when the
read some operation completes
So async read sum will
return and then at some point
the lambda expression will be called.
So what are the valid
types for this handler?
Well, it turns out the
next concept is called
a completion Handler.
Asynchronous operations
invoke a completion handler
which is a concept in networking TS,
it's an in vocal function object that has
a particular signature.
They need to be moved constructible,
the asynchronous operation
will take ownership
of the completion handler
by move constructing it
and it'll keep it alive until
after it invokes the handler.
Here are three examples
of completion handlers;
the first is the lambda,
which you've already seen,
the compiler will generate
the function object for you
and some anonymous type with
a really long obnoxious name.
And then the second example
is a user-defined function object,
this is no different
than the lambda except
you're just writing it out by hand,
it has the function call operator
And then the third example
we have the return value
of stood binds which
returns a call wrapper.
Again, this is an in
vocal function object.
In this case, the standard library,
is creating that type for you.
So back to our asynchronous example.
We're going to call async read some,
that call is going to return immediately
and then sometime later our
lambda is going to be invoked.
But maybe you noticed something strange
there's a question mark here?
What's the problem?
Let me give you a hint.
So that's a thread.
Which thread is going to
invoke the completion Handler?
It can't be the thread
that calls async read sum
that would be bad.
It would be undefined
behavior if the implementation
just interrupted your
thread at some point.
That wouldn't be very good.
So networking TS provides something called
the basic guarantee.
A completion handler will
only be invoked from a thread
that's calling a member
function called run on
the i/o context associated
with the i/o object.
That sounds pretty obscure, let's see
what it actually looks like.
Here we have an I/O text object
and we're calling run on it.
So what that does is, that
relinquishes the current thread
to the networking implementation.
And what will happen is
that call to run will block
and it'll invoke completion
handlers and do things until
there's no more work remaining.
Now what if we don't want to
give up the current thread?
Well we can give it a new thread,
here you can see I'm constructing a thread
and then that's right is
gonna call I/O context run
but there's a little problem here because
I'm creating a temporary variable
and then that temporary
is going to be destroyed,
when the expression is done evaluating.
So this is not going to work very well.
We need to detach the thread
in order for it to be able
to continue and outlive that temporary.
So we can have multiple threads
calling I/O context run,
here's what that looks like.
This is a little snippet
that will create end threads
and including the current thread
so it'll create n minus 1
threads and call I/O context run
on that and then it'll
commandeer the current thread
and call run on that one.
So you'll get n threads out of it.
Why do we have all this ceremony
with the i/o context run?
So I like to call this
bring your own threads.
Networking TS philosophy
is not to make odd choices
on behalf of the user.
So the threads are going
to be under your control
as the author of the code,
you can decide how many of them you want
which determines what
synchronization model
you're gonna use,
you can even decide
which thread facilities
you're gonna provide.
It could be the standard thread,
maybe it's an operating
system specific calls,
you have control over
the threads entry points
so you can do interesting things
like wrap it in a try-catch
It's really all up to you.
The code that I'm going to
show you is single threaded,
it keeps things very simple.
You can get rich with
single threaded code.
All those applications that I
showed you are single threaded
each game instance runs in one thread
although multiple CPU cores are used
to host multiple game
instances on the same machine,
but each instance is
still single threaded.
There's one piece of information that
I'd like to share with you
about multi-threaded Network programs,
and that is the networking TS has a
let's call it Executor's light.
An executor in networking TS defines
how a completion handler is invoked.
The most important executor is the one
that's associated with an
object called a strand.
A strand makes sure
that completion handlers
will not execute concurrently.
So if you're gonna write a
multi-threaded application,
strand is something you're
gonna want to look at.
It involves additional
synchronization overhead but
it's really essential.
And in conclusion with networking TS,
there's three very
common threading models.
The first one which is
what I'm gonna show you
is single threaded, that means only
one thread calls i/o context run.
We don't need to worry
about any synchronization
since nothing's happening concurrently.
It's easy to write, it's fast,
but it only has one thread.
Then we could have multiple
threads calling i/o context run.
So you're gonna utilize more
of the machines resources
but now you're going to be
exposed to synchronization issues
which could be a good trade-off.
Finally the most complex,
is multiple threads
where each thread has its own I/O context.
So that requires more work on your part
it's more complicated and
you'll need to balance
the connections among the
i/o contexts in order to make
that work but it does
offer the greatest speed.
It has the advantage of not
requiring synchronization
in most cases and it has
the highest capacity.
Okay, now I know you're
itching to see some code.
Let's see that code.
I'm going to show you a chat
server that's written C++
that uses Beast and it uses WebSockets
and it allows people to connect
and exchange messages with each other.
So to keep the slides small
and to not have any
particular bias towards
a version of networking,
I've created some handy
aliases, some namespace aliases
and some type aliases, the code that's in
the repository uses Boost.Asio
flavor of networking TS
because Beast itself is in
boost and it uses boost Asio.
So here's little shortcuts.
So this servers divided up
into five major components.
First we have main, hopefully
everyone knows what that is
then we have a shared
state, which is information
that every object in the
system needs to have access to.
Then we have an object called a listener.
This is responsible
for monitoring the port
and accepting incoming
connections and then finally
we have these two session objects
an instance of these objects
represents one connected user
of the corresponding protocol.
So here's the shared
state, this is very simple
there's two pieces of
information that we have
to have on hand at all times.
One is the document
root, so the chat server
is also an HTTP server.
It serves HTML files, it serves
images or whatever you want
to put there, advertisements,
if you want to make that money
The doc root is the file
system path to those files.
Then we have a set of
WebSocket session pointers.
This keeps track of all the users that
are currently connected,
because we want to do things
like send them messages
or maybe kick them off.
So how do we implement
the member functions
of our shared state?
What we have the functions join and leave,
these insert and remove
sessions from the container
to maintain it as people come and go.
And then we have a function called send.
So this is the function
that's gonna broadcast
the chat message to all
the connected users.
As you can see we're taking
ownership of the string
that's passed in and we're creating
a shared pointer out of it.
We're gonna use shared
pointers in our application
even though it's single threaded because
it's a very convenient way
of allowing multiple objects
to refer to the same piece of data.
So we construct a shared string then
we loop over each
connected WebSocket session
and we call send on that shared pointer,
so then they can send
the message to the user.
So that's our shared state, pretty simple.
So here's main, there's
some command-line arguments
that you need to use to
invoke the server besides
the address input we
need the file system path
to the document route, so
here we're going to decode
those parameters and now
we have an i/o context.
You gotta have that you're gonna see it
in every networking program.
So once we declare our i/o context,
we wanna create that listening port
so that we can start getting some users
and making that money.
So I'm calling make shared,
I'm constructing a listener
object for the port
and then we're invoking a
member function called run
on that listener.
Now notice that we're not
storing the return value
of make shared, in any variable.
So what's gonna happen
is, when that expression
is done evaluating, that shared pointer
is gonna be deleted.
It's the responsibility
of the run member function
of listener to make a
copy of the shared pointer
in order to extend the
lifetime of the listener.
If we don't do that, then the
listener is gonna be destroyed
and we're not going to really have much
to talk about in main.
So we create the listener
then we call I/O context run.
At this point the main threat of execution
which thus far is the only
thread in the entire program
is going to be blocked on that run
and at that point networking
TS takes over the thread
and it uses it to invoke
our completion handlers.
So that's main.
Listener here's the
declaration for listener.
Now you can see we have
some networking TS types
we have an acceptor, we have a socket
and then we have that shared state.
So the first thing we
want to do in listener
is implement our run function
this is what's called from main
and now we're calling async
accept on that acceptor.
As you know asynchronous
functions they start
with the word async underscore,
so this is one of them.
So we're calling async accept.
This call will be
outstanding until it receives
an incoming connection
and then it'll invoke
our completion Handler
with the error code and
the socket underscore
member variable will
hold that new connection.
So when our completion Handler is invoked
we're gonna call on accept.
Now notice that in the lambda
I'm calling shared from this
so we're binding a
shared pointer reference
into that lambda and what that will do is
that will extend the lifetime
of the listener object
as we talked about in main run
has to extend the lifetime.
So networking TS is going
to take ownership of
that completion Handler and
as long as the operation
is outstanding our listener
object is gonna exist.
So once we get an incoming connection
we're gonna call on accept,
we're gonna have an error code,
if there was an error we'll
report it like good citizens
you'll see a message otherwise
we're gonna create an HTTP session.
As we talked about
earlier WebSocket sessions
start out as HTTP.
So we create our HTTP session object,
once again I'm using the same idiom.
I'm calling make shared
to create the HTTP session
and we're not storing
the return value anywhere
we're just calling a
member function called run.
So it's the responsibility
of run to make a copy of
the shared pointer in order for
the HTTP sessions lifetime to be extended.
So now that we've accepted
our incoming connection
that called async accept was good
for only one connection.
So we blew that operation now
we need to start another one.
So we're calling async accept again,
this is the same
invocation you saw earlier,
the lambda does the same thing.
So we've created a type of loop here.
We call async accept, that
operation is outstanding
when we get an incoming connection,
we process it and then we
call async accept again
and that keeps going until the
user terminates the server.
So one last note on the listener,
we want to know how to report the errors,
so there's a special error
code called operation aborted.
This is what will happen
if you interrupt the server
like for example you press control+ C
or you cancel an operation,
you're gonna get that error
in the completion handler
we don't want to flood
the the log with those
so we just don't bother reporting it,
otherwise we give them
a nice error message.
So that's our listener
pretty straightforward.
So as we saw the listener
constructs an HTTP session,
here's that HTTP session.
Now notice again we're deriving
from enable shared from this
we're managing our HTTP
session with the shared pointer
and now we have more variables.
We have the socket
associated with the session,
we have a buffer which Beast needs
and then we have our shared state
which has the document root
and then we have a variable
that will hold the incoming HTTP request.
So when we construct the session,
we call run on it and now here's
a Beast function called HTTP async read.
So this will read an entire
HTTP message that's right.
It's that simple.
The message will be stored in
the req underscore variable
and then once we have
the complete message,
it's going to invoke
our completion Handler
in this case it's a lambda.
Just like before, we're
binding shared from
this into the lambda,
so the lifetime of our HTTP session object
is gonna be extended, at least until
the completion handler returns.
So normally when you program with classes,
you explicitly call the
constructor to create your object
and you call some member functions
and then you explicitly
destroy the object.
Here things are a little bit different.
In this code, the default
behavior is for objects managed
by shared pointers to be destroyed,
unless you extend their lifetime manually
which is what we're doing
by calling shared from this.
You're not going to see
any explicit destruction
of objects in this code,
instead what you're going
to see is that the lifetime
is extended when it needs to be.
So this model is very easy to program
once you understand how it works,
and then you'll never have a problem
with objects not living long enough
or living for too long
and having memory leaks
which is a common source of problems
with asynchronous programs.
So that's our HTTP session
run we're going to read
an HTTP message,
once we get a complete HTTP request,
the function on read is called.
So first we check to see if
the client decided to close
the connection and then here
we just gonna shut down the socket.
Notice how we just return.
As I said before, the default
behavior is for objects
to be destroyed when they're
managed by a shared pointer
unless we extend the lifetime by returning
from this function without making
a copy of the shared pointer
we're gonna guarantee
that the HTTP session
is gonna be eventually destroyed
when the completion Handler is destroyed.
Otherwise, if there's
an error, we report it
and then if you recall earlier
I said that WebSocket
sessions start out with
an HTTP upgrade request.
So this is a Beast function is upgrade,
it checks if the request
is a WebSocket upgrade
if it is, then we want to
create the WebSocket session.
So by now you're pretty
familiar with this idiom,
we construct a shared
pointer on object managed by
a shared pointer we call run on it
and we give ownership of the request
to the WebSocket session
so we can look at it
and figure out what it wants to do,
and then we return.
By returning we avoid
extending the lifetime
of the HTTP session object,
guaranteeing that it's going
to be eventually destroyed.
Now what if it's not a WebSocket upgrade.
Well our chat server is
also a complete HTTP server.
We wanna be able to serve
files from the document route
so if it's not a WebSocket upgrade,
then we want to call this
function called handle request
which will handle the request
as a normal HTTP request,
it'll check to see if the
file is in our document route
and it'll serve it hopefully
it won't allow the user to
exploit any vulnerabilities
with you know putting
like dot dot in the path.
Make sure you don't do that
if you're on a live server.
We're not gonna go into that function.
It's very long you can
look at it in the code
it's in all the Beast examples.
But this function returns an HTTP response
and that response can have
different types depending on
what's in the request.
Now in C++ a function can really
only have one return type.
So handle requests can't
use the return channel
to return the object.
Instead, it invokes a function
object which you pass in
and it'll pass that response that
it wants you to send in its parameter.
You can see I have a lambda
which has auto as the parameter.
So that's going to be a generic lambda
and the lambda is
responsible for sending it.
So in order to send it,
we put it into a shared pointer
so that we can extend its lifetime
as long as the operation is active
and then we call a Beasts
function async write.
So async write writes
an entire HTTP message
you give it to Beast and it'll send it
and it'll take care of it and then
when it's done you know
that the message went out.
Again we're binding a
copy of the shared pointer
to the HTTP session into our lambda
and once that HTTP
message with the response
has been sent then we're gonna
call our on write function
which gets called when
the operation is complete.
So at the end of the on write function
we're reading another request.
Once again that's that loop
that we talked about earlier,
we read a request we send the response
then we do the read again.
So that's gonna end in one of three ways,
either the clients gonna
close the connection,
they're going to get an error
or they're gonna do a WebSocket upgrade
otherwise they're gonna
keep getting file served.
So now we're down to the last class,
the WebSocket session, this
is the moneymaker here.
This does the thing that
we want the server to do.
It's the chat.
So you can see we have
some variables, a buffer
this is a dynamic buffer, flatbuffer
is a dynamic buffer.
It's going to hold the incoming message
and then we have something
called a WebSocket stream.
So in the HTTP session
you may have noticed that
the functions to send
and receive HTTP messages
these were simple free functions
that's because HTTP at its lowest level
is pretty much stateless.
WebSocket is more complicated,
there's state information
that has to be there
so we need an object
to represent that state
and that's what the WebSocket stream is.
Beast provides that class
for you and it manages
all the boring protocol details.
So that's our ws variable
then have our shared state
which has our list of
connected WebSocket sessions
and then we have a queue
for our outgoing messages.
So one of the interesting
things about Beast
the philosophy of don't make odd choices
on behalf of the user,
the WebSocket stream can only
send one message at a time.
To allow it to send more than one message
it would require making a decision.
How do we queue those messages?
And then we would have
to answer the question of
what allocator to use,
what data structure to use
and no matter what I pick
in that implementation,
someone's gonna be disappointed.
So rather than making
that choice, Beast pushes
that responsibility onto the caller.
So you're responsible for
implementing your own queue
that's gonna be optimized
for your own needs.
Here's our WebSocket session
when we launch the session we
take that HTTP upgrade request
and we just pass it to Beast.
We call this function called async accept.
It's an asynchronous function,
it's going to look at
the upgrade request to
make sure that everything
about it is kosher.
It'll send back the proper
response and then once
our completion handler is invoked
now we know we can
start talking WebSocket.
We're gonna call a
function called on accept
you can see again we're binding
shared from this in there,
by now you're pretty
familiar with that idiom.
After we accept the WebSocket connection
if there's no error we
call join on our state.
So if you remember that's
gonna insert the session into
our list of active sessions.
We don't want to insert it
before the handshake is done
because then we might
end up sending a message
to a client before the handshakes complete
who knows what that would do.
So now we're okay to
insert them into that set.
Now we wanna read a message,
they're gonna send a chat
message we want to read it
and here's async read, this
is a WebSockets dream function
notice how the interface
to asynchronous functions
they're all very similar,
we pass our buffer in which is
what's going to hold the result
and then we have our completion Handler
just like before we call shared from this
to extend the lifetime
of our WebSocket session.
And once we get a message,
we call on read.
If there is an error we report it.
otherwise we want to
send that chat message
to everyone who's connected.
You saw the send function
in the shared state
it calls send on each WebSocket session
and then once we're done
sending that message
we want to get rid of it in the buffer.
We don't want it there anymore
we want to get a new
message into the buffer.
So we call async read again.
This is that loop that
we were talking about.
We read a message into the buffer
when that completes we send
it to everyone clear out
the buffer and then we call read again.
It's pretty straightforward.
How do we send?
So this is the most complex function.
When we send a message we're
sending a shared pointer
if you remember our shared state puts
the string into a shared pointer.
The first thing we do is put it
into our outgoing message queue.
This will keep that string alive no matter
how many people are referencing it
now we make sure that
since we're looking at it
we have a reference to it.
It's not going to go away.
We need to check if we're
already writing because
as I said earlier Beast only allows
one write operation at a time.
So if we're already writing,
we need to just return
and wait for that thing to complete,
otherwise, if we're the
first item in the queue
now we call async write.
So this is the Beast function that sends
a WebSocket message, you
can see we're calling
the buffer function on
the front of the queue
after it's dereferenced,
so that will convert a
string into a Const buffer
and then again we have
a completion handler
which will be invoked after
the message is done sending.
And we're gonna call on right.
So this is that loop again.
On write, we report the error if any,
we erase the message that we just sent,
if it was the last reference,
if it was the last shared
point of reference,
it's gona get deleted
and then if there's more messages to write
we call async write again that's the loop.
So now we're gonna keep
on sending messages
until there's nothing left in the queue.
So here's the last main function fail.
Here we've got two messages
that we don't want to report,
operation aborted you saw earlier
but now there's a new
message called closed.
So as I said earlier WebSocket
has three special messages
close, ping and pong.
Close means that the client wants
to shut down the connection,
it's an orderly shutdown
and Beast will report that
as a closed error.
So we don't want to
report that to the console
we don't want to flood the console
otherwise if it's a legit
error that will print it.
So finally, I know what you're thinking
how do we remove the WebSocket session?
Well that's easy.
In the destructor, we just
call leave on our state.
So when there's no more references
to the WebSocket session,
the destructor will be invoked
and we'll just remove it from that set.
Now if you remember
there's only one thread
in this program, so we're
guaranteed that nothing
is executing concurrently.
We don't need any luc
it's all very simple.
So that's the server I know it was a lot
but you can look at the program
and you can tinker with it it works.
Now we're gonna take a look at the client.
Now the interesting thing about our client
is that it's got some graphics.
That's right and we
don't need a TS to do it.
So here's the client you can see there's
a you put in the URI
then you press connect
and it'll connect to the server.
You can put your name in there
and then you're gonna see the chat.
You can type messages and press send
and you can talk to people
and then when you're done
you can press disconnect.
So we're gonna whip up
this interface using HTML.
It's gonna be very quick
it's not gonna be like C++
we don't need 8 files.
It's gonna write a few lines.
this is HTML everyone
pretty much understands
what that's like you've got tags that are
in the angle brackets here
you can see some orange text
where it says UI and app
that's an HTML comment.
We're gonna insert our user interface
into that part of our file.
So there's only a few different controls
this one is called a button
I'm sorry this is an edit
box, it's the input tag.
You can see that there's
some name value pairs,
some of them have to do with the size
and how it's drawn but then
there's one that's called ID.
You can see I've put URI
as a string for the ID.
So that's like the equivalent
of like a variable name.
So that's the the name that
we're going to give that item
if we want to refer to it in code.
So here we're creating the edit box
that lets you put the URI in.
Now we have a couple of buttons,
again we have this ID that
gives the thing a name
and you can see there's
connect and disconnect.
So now we also want to get the user's name
so we can put it into the chat
again we're using an input,
that's an edit box you
can see it's got an ID.
So by now you can see the pattern,
you have input, you have button obviously
and then finally we
need that big rectangle
that shows all the text
for the chat messages.
So this is called the pre
tag means pre-formatted.
It won't try to rap it'll
preserve character turns,
again we have an ID messages is its name
and we're going to use that
and we're going to refer to it.
and then finally we have the the place
where you actually put in
the message and send it.
So that's an edit box
followed by a button again
we have these IDs.
Now this this is all very
nice but we've created
a user interface that
doesn't do anything then
when you press the
buttons nothing happens.
All we've done is to find how it looks
and how it's supposed to react
but not what it does when
you manipulate the controls.
So in order to do that we
need to write some code
and I need to explain something
that's called the DOM.
Which is not the popular French Champagne,
it's actually the document object model.
So this is a system that
allows you through code
to access every element
of that HTML document
if you wanna show things or hide them
or if you want to change text
or you wanna make something disappear
or change the style, you do
that with this thing called
the DOM and that happens through code.
So now we're at the
coding part of the client.
This is the obligatory trigger warning.
The code is written in
JavaScript of course.
So here's what that looks like,
inside our HTML we create a script tag
and it's corresponding closing tag
and everything inside there is gonna be
this wonderful JavaScript.
Now I'm not gonna go in too much depth
you can kind of look at it and
kind of guess it what it does
You see new WebSocket
hopefully that should
be self-explanatory.
So yes in JavaScript
it only takes one line
to create a WebSocket
connection to a particular URI
and then return the object.
So you saw what it looked like in C++,
this is what it looks like in
JavaScript don't get jealous.
Now when they press the connect button
we want something to happen.
So here what we're doing is,
we're assigning a function
to the on click property
of the connect button.
So all that really means it's
a fancy way of saying that
when you press connect, it's gonna run
that function which is gonna connect
to the WebSocket at the URI
that we're passing in there.
Okay so ws is that WebSocket
stream in JavaScript
you can see it has on open on close.
These are like events and we're
attaching functions to them
you can see that when the
connection is established,
we are creating this
text connection opened
and we're appending it to
this messages.inner text.
So that's that area that
shows the chat messages
all we're doing is we're
just appending a string.
It's that easy to append a
string which will appear.
It's pretty much painless.
And then on message its get called
when we receive a WebSocket message
that's what the server sends the client
in that broadcast that you saw.
All we're doing is just
appending the contents
of the WebSocket message into the control
and then putting a character
turn at the end of it.
So that's how you're going to
see the messages that come up
if there's an error,
hopefully we won't ever see
that I've never seen it.
And then if you disconnect we close
the WebSocket connection.
Now the most complicated
part of this interface
is of course when you type
your little message in
and then you want to press send,
so if you click the send button
then we're calling send
on the WebSocket object
and it's gonna send your
name and then a colon
and then whatever text that typed.
And then we're gonna clear the field.
We don't wanna have to make
them backspace every time
they send a message
that would not be cool.
Finally, one last detail we
don't want to have to click
the button every time you
want to press the Enter key,
so this is a little snippet of code
when you release the Enter
key that it will simulate
a click
on the send button and
that's it for our client.
So believe it or not this
slide shows the entire client.
So this is a full client
that's WebSocket enabled
this is one HTML file that you
can find in the repository.
This includes the graphics
and the user interface,
it includes the edit box,
it includes the JavaScript code,
everything this is the client.
I think that's pretty
amazing how small it is
and that allows you to
rapidly create things
you don't even need an IDE
you just need a text editor
and then you refresh it
in your browser and you
drop the file in there
and you can refresh it
and you can start seeing
your program come to life with your HTML.
So how are you gonna use this to get rich.
Well agar.io and slither.io,
when they became successful
they sparked 100 clones everyone
jumped on this bandwagon,
there's lots of i/o games,
and there's other programs
that are not games
that you can find that are out there.
So what are you gonna write.
Well in the program that I showed you
it's a chat program
which sends text messages
but you don't need to send text messages
you could send chess moves
or you could send the XY
coordinates of your character
as they get through a maze
or you could send commands
to some type of textual role-playing game
you can see here, you
could send scrabble moves
or you could even have a game like chess
where you can also chat with your opponent
or you can have a lobby
where you're chatting
with other people that
are looking for a game.
The chat code that I showed
you would be suitable for that.
So really the sky is the limit
by writing a program
that runs in the browser
you can get that up and
running very quickly
with graphics you could even have sounds
and you don't have all of
the bulk and complexities
that you might get if
you try to do that in C++
and if you make your program
free then anyone can run it.
You're gonna get a lot
of users very quickly.
I don't know what you're gonna write.
Maybe someone will write
tinder for C++ users who knows.
One of the questions that
I get which is very common
they asked me, " what book should I get?
" Do you have a website that teaches you?"
I haven't really found
anything that's great.
The only advice that I can give
is to read the documentation
for the networking,
read the documentation for
Beasts, look at the examples
write as much code as you can,
the more that you write
the better you'll get
it's gonna take you know
a certain number of hours
of writing code and making mistakes until
you're gonna be professional at it.
It certainly took me
quite a number of hours.
You can ask questions on
Stack Overflow in the slack
that's a great place to ask questions.
And if you're in a commercial setting,
I think the best way to
learn is to have a mentor.
If there's someone at your
job or you know someone
in your community who's
an expert in networking
you wanna talk to them
and have them answer
your questions in a more
interactive setting.
It's gonna help you get
ahead much more quickly.
So that's the conclusion
of the talk I'm really glad
that I made it within the time
I was a little bit concerned I had to cut
a lot out of this talk.
There's obviously a lot more things
that I could tell you I touched upon
just very lightly scratched
the surface of networking,
we only saw the single threading examples
so here you can see
where to get the slides,
you can check out Beasts
and you can also check out
the rest of the Boost libraries.
I highly recommend them.
I think they're great.
So in conclusion, I would
like to thank John Cobb
and the program committee
and all the volunteers
who worked tirelessly to
put this conference together
so that I could bring my ideas
and share them with you
thank you all for coming.
That concludes the talk.
I do have about five
minutes to answer questions.
If you want to ask a question I'll ask.
(audience clapping)
Thank you.
(audience clapping)
I'll also be available afterwards
in case they need the room
I don't know if there's another session
but that's fine.
Would you like to ask a question?
- [Man] Yeah, so the pattern
where you're creating
a shared a pointer using
enable shared from this,
does it like it repeats
so often and it only works
if you created the shared Pointer
like that for the first time
so probably does it sound a good idea
to create a static create
method with the same signature
as the constructor which could return
you the shared pointer for all of those.
- I don't understand the question.
Is there a question in there?
- [Man] Yeah, like basically this works
because when we created the shared pointer
for the first time and called run on that,
alright I believe it works only
if you had already a
shared pointer otherwise.
(man speaks off microphone)
so the question was having a create method
and not have the constructor create it.
- Oh you mean like an extra function?
- Yeah.
- I mean, sure that's possible.
I mean it's just a matter of style,
it's one extra function in the interface
but I mean to me they're kind of the same.
I was in a rush to put
the program together.
- [Man] Thank you.
- Thanks for asking.
Next.
- [Man] Hello pretty cool.
I also have a question
about like shared pointers
in your example and I guess
it's a quite common example
you're running like single threaded
and I guess like you will
have lots of atomic unless
unnecessary in atomic instructions
because of like shared pointer.
Any thoughts on that?
Is there maybe like a way to like.
- Listen when those guys
are making a 100000 a day
they weren't worried about
those atomic operations.
(all laughing)
You can get rich and waste a few cycle.
But to your point, yes you're right,
so atomic operations they have a cost
it can be pretty considerable
in this chat server,
you're probably not gonna notice,
but if you have like a high
performance application
you're gonna want to do
something about that.
So you don't need to use shared pointer,
you could use your own
smart pointer wrapper
that doesn't use atomic operations
and in fact I think that Peter DeMuth has
if he hasn't committed already
he has some wonderful work
in shared pointer in Boost
which provides non atomic operations
and also a shared pointer that's optimized
for single threaded applications
which does what you said.
- [Man] SDC pass also
allows you to actually like
instantiate a shared pointer
which doesn't do that
but I would like to see like something
maybe standardized that's why I'm asking.
- I'm not aware of any standard solution
that doesn't have reference counting.
- [Man] A second question
is like HTTP to support
is there something on the horizon?
- It's not really part of Beast.
HTTP one is mostly stateless,
HTTP two is something else entirely.
It's very complicated.
There's a lot of drama and
contention around the development
of the HTTP 2 standard.
I know that I don't think
Google has gotten completely
on board with HTTP 2,
it seems like they have like
kind of like their own solution
So I'm a little bit reluctant
to invest the massive amount
of resources necessary to produce
an HTTP 2 implementation when the benefit
the benefits not really there yet.
I don't really see it.
And WebSocket offers a
great solution for doing
the two-way messaging.
Next.
- [Man] So I was really taken with
by the way great presentation,
I was really taken with the
JavaScript websocket interface
which was like make a web
socket four or five centers
of callbacks and then like run.
And that was after about 90 slides of C++
you're implying that we
can't have nice things
but can you explain a
little bit why we can't have
that particular or can we have it
but we have to build it
ourselves on top of Beast
or do we have to build it some other way
or can we not have it for
a philosophical reason?
- So I think that's a great question.
So the question is why is something
in JavaScript to operate
web sockets so small
and simple and then in C++
it's large and complicated.
You're right about that.
It's definitely verbose in C++
and I attribute that to
a couple of core reasons.
First of all, there's no
mistaking it but networking
in C++ is late.
We've needed an abstraction for networking
for a very long time and
it's taken a while to get it.
Every other language has abstractions,
javascript has a networking abstraction
you can even change the backend
and use the same abstraction
but another vendors implementation.
They have a very robust
ecosystem of packages
and libraries we don't have that yet.
Networking TS is going to
be really a game changer
and it's going to allow
us to start catching up
and you're gonna see more
libraries like Beast,
libraries that are
written on top of Beast.
And to what you said, we do need people
to build middleware solutions
that make it easy to do things
Beasts HTTP support is very low level,
there's lots that it doesn't do;
it doesn't deal with proxies,
it doesn't deal with authentication,
it really only understands
a few of the fields.
So we really need more
libraries that are gonna
be built on top of it to handle things
so that people don't need to
write large cumbersome programs
- [Man] To check my understanding of what
you said about JavaScript,
you're saying that
there is something
similar to in complexity
and low levelness in javascript
but what I'm seeing in your five lines
is a very high level bit but
they have the low level bit
- That's right.
- [Man] And we have the low level bit
or I mean we now will have a little bit
but we don't have the high level bit yet.
- That's right.
- [Man] What do I Google
to find out more about
the low level bit in other languages?
- oh I mean I think JavaScript
has something called socket.IO
I think I'm not really sure
I don't really know those
other languages very well.
- [Man] Okay, thank you.
- Next.
- [Man] Perfect chat.
So my first chat server was
using no Js on the server using
a socket I/O web socket and it
had a pub/sub interface also
so when you would shoot
messages back and forth
it would automatically on either side
say hey I want to subscribe
to this and then route it.
I assume that was in
the web socket standard.
Is that something at a
higher level abstraction
that is in the web socket
standard but Beast doesn't do yet
or is that a JavaScript agreement
between the browser no js'
- So the question is what facilities exist
in Beast web sockets to do group messaging
publish and subscribe.
Publish and subscribe is not
part of the WebSocket protocol
these are interfaces
that are that are added
by the library providers.
Beast does not provide
anything like that .
In my opinion that goes against
the low-level philosophy.
Something like that is easily written
and I look forward to people developing
those middleware solutions
and then battling each other
to see which one's the best.
Doesn't that answer your question?
(man speaks off microphone)
Right, right
Next question.
- [Man] so I had two questions.
The first is I noticed
that there was a place
where you moved a socket object
into one of the Constructors
and then you then copied
the same socket object
back into like a read async
to reschedule yourself but
you didn't reinitialize
the socket in between
I was curious if that's
- Right okay.
So the question is why did I move a socket
and then use the moved from socket later.
Okay that's a good question.
So I want to step back and
look at move construction
and move assignment in general.
So we all understand
the consequences of move
in the place that we're moving to,
but often we don't
understand the consequences
of what happens to the
object that we moved from.
And the answer is that the
language doesn't specify
that in general it's up to
each type that supports move
to define what is the state
of the moved from object.
Now in the standard library,
in most cases or all cases
I think, the moved from object
has a well-defined state
and in networking moved from
sockets are also well defined
they behave as if they
were constructed again
with the original constructor parameters.
- [Man] Okay thank you.
My other question was instead
of using shared pointers
and shared from this when I capture
you know you usually do
it like a self equals
shared from this could I do like
a self equals dereference this
and copy this into the lambda instead.
- So the question is can
we simply make copies
instead of having a
shared pointer reference
in the lambda?
- [Man] Yes.
- So the answer is no.
Sockets are not really copyable,
Beasts websockets stream is not copyable.
So you have state that really
can't be duplicated,
so you need to have you
need to have a reference.
You need to make sure
that only one object owns
the socket or stream or else
you'll run into problems.
Does that answer?
- [Man] Yeah thank you.
- Okay great.
Next.
- [Man] I think you mentioned
it quickly but just to confirm
so there is no way but using
just the language and TS
to do SSL or support client certificate
in the networking.
- Okay so the question is,
I think the general
question is how do we do SSL
in networking?
So that's it a great question.
So far my efforts to
settle on the right slides
has really failed.
So I think what I'm gonna do
is I'm just gonna go ahead
and just pop out here.
Don't look at this, don't
look at the sausage-making
and I'm gonna go down to
so if you notice
there we go
So here's our read until function.
So this is a networking TS function
and I didn't go into much
detail during the talk
but you'll notice that
the first parameter is
called sync read stream.
So this is a concept that's introduced
in networking TS.
Oh it's over.
Okay do they need the room?
- [Man] Yah finish the question.
- Oh okay, okay great!
So this is a concept any type that meets
the concept requirements
can be used there,
networking TS will work with it,
Beast respects these concepts as well
and you can use SSL stream implementations
that are provided by other
vendors Asio has an SSL stream
and all of Beasts algorithms
work with SSL streams.
When you construct the Beast web socket
you can use an SSL stream
as its underlying transport.
- [Man] Okay sounds good.
- Okay great.
So I think that's it.
No more
so if you have questions,
I'll be happy to answer them outside.
(audience clapping)
