Cookies   I display ads to cover the expenses. See the privacy policy for more information. You can keep or reject the ads.

Video thumbnail
- 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?
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 and the other one is called
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 and, 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.
- [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.
- [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.
- [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)