C++ Chat Client and Server using Boost Networking TS | C++ in 2021

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments
Captions
hey what's up guys welcome back to c plus plus in 2021. today we're going to continue our adventure into network programming with bustazio by implementing a full uh chat client and server application it'll set us up nicely for getting towards um being able to do server commands and being able to do actual game objects right now we're just gonna be passing in simple uh strings from one client to the other going through the server but eventually the server is going to be holding some state and it's going to be taking more than just string that will also be taking uh binary data like structs and and stuff like that so if you're interested in the series continuing uh the last video did decently leave a comment down below letting me know what you're interested in where you'd like me to go hit the like button all of that nonsense and let's just get right into it one thing i almost always forget to mention in these videos is you can always get the source code for everything i go over at my corporate github account so github mobile technologies here and this particular lesson will be under moife so mud on your face okay so uh what we had last time if if you see here i'm running the server and if we run the client as well you'll see that the server i believe only sends a message to the client and then uh it just leaves the connection open right hello beautiful client sends data and if i close if i kill the client it detects a disconnection so what we're going to do is we're going to reorganize all of this these files to make it a little bit more robust and so that we can send messages at will to the client and to all connected clients and all that kind of stuff so to start off what we're going to do is we're going to reorganize a little bit of our stuff up here we're going to move the tcp server connection stuff into a new directory called server and we will just move these into here we'll hit refactor just so it moves uh automatically changes the files i'll show you where this will do it again also in our source folder here all right so we've renamed all of it right now if you look inside our cpp file uh the refactor it automatically moved the header and in our main.cpp as well so just remember to do that if you're not in clion and then do it automatically for you so we're going to start in our server there's i imagine there's probably not too much we need to change in here well there's some but let's just look at what we're doing here so our tcp server itself doesn't need to change uh the constructor the run function doesn't need to change either that one's okay uh we will add um things we will add uh first we'll remove all these listen callbacks because we're we're not quite there yet um that was just ideas that i threw out at the end of the last video that we're going to get rid of so we'll get rid of that and we'll make sure that there was none of that nonsense down here uh which there was so let's remove that so we've got a pretty much clean basic server here so we've got our run function we've got our start accept function which will definitely or likely need to update and we also need a broadcast function which will handle sending a message to all connected clients so in our tcp server right under run we will do a void broadcast and this it will actually just broadcast a message and we're let's just take that as a reference first let's deal with broadcast let's generate it uh let's move it up to where we want it underneath run i like having stuff defined in the same order as my.h file in my cpp file and that's why i did that so notice how here we're creating the connection um at when we accept it here and then we are pushing it into the the array or the vector and then then we're starting our async except right that's not exactly what we want because here what we're going to be end up doing here is you'll notice here that we're always accepting a connection on the same socket right every time we go to create a connection to connections every one of the connections we make will go in here and it will restart the connection every time a new client connects so you can't have multiple clients with this setup it's actually not written well so what we're going to do is we're going to make it so that we have a a we'll call it a dummy socket um an idle socket that's created that's waiting for a connection and when we get to connection it will move itself into the connection itself to be used and then handled within that connection if that makes sense we'll work through it together so the first thing we need to do is create a thing to hold that that socket object that temporary socket object so if you go to our header file again in here what we will do is we are going to create a an optional socket object which looks like also one thing i'm going to do as well as you notice this boost.seo stuff i'm actually going to um i'm going to rename that io equal boost as you and that's just going to make it so that we can make stuff a little bit shorter and let's put this in here and then we can replace all this with io and that's just going to make it easier for ourselves um as we move move forward oh not i don't i don't need to do the namespace i can just do this sorry name space i o equal blue statue i just uh spelled it wrong so what we want to do is we want to create this dummy socket object right so let's do that now so what we're going to do is we are going to declare a stud optional and what this is can be it's going to be an i o uh ip socket uh tcp socket object called socket right and optional is not declared so let's make sure to import that up here and we're going to include optional [Music] and another other thing we will do is we're going to change this vector down here to actually uh we're going to make this a key uh unordered set and we will also include that on ordered set and that's just going to make it uh easier to work with later oops right so let's go into our tcp server and start rewriting our start accept function so the first thing we're going to do when we start accepting uh a new connection while we're waiting for a connection we're going to create a new socket object uh waiting on that io context so we're going to do socket dot in place [Music] and this will take the arguments for initializing a new object so this will just take our i o context [Music] so this creates a socket that we're waiting on and then we can actually wait on that socket instead down here so you can reference it just like a pointer when you're using an optional so you just dereference it with the star and that will get the socket and you know it always exists because we create it right above here now once you get inside the function itself that's when you you're going to want to create the connection right so we'll move this down here and instead of push back we're actually going to do insert because it is a set and not a vector uh what else do we want to do so we have if there's no error we don't need to bring in the connection anymore so let's just bring in this and then we start the connection and then we after we start the connection we start accepting again so this is uh getting closer we're we are getting much closer if we go inside this tcp create function now uh if we go inside here and you look at tcpconnection.create which i guess i made up here it creates a new pointer with i o context this takes an i o context and the constructor creates a new socket using the i o context right now what we're doing we actually want to change this because we are using move semantics which i'll try to i might take a break from this networking project for a couple of weeks while we look over some of these concepts we're going over but using move semantics we can move the memory that's held by the optional in our server here that we created here into our tcp connection so that we don't actually need to create a new socket object once we go inside so what ideally what we would end up doing is we would do an sdd move here and dereference our socket like this what this will do is when you do a move on an object especially at most of the stl objects what it's going to do is going to clear the optional so the optional will become false falsie so there won't be anything inside and it will move that object into wherever you're sending it so in our tcp connection here we're going to change our um [Music] our create pointer here to take a uh it's going to take a right right hand value socket object which is how you do you declare a move operation is happening so it would be uh well let's uh we did it using up here but um let's do an again namespace io equal boost seo we'll do that uh and we let's our create function here is going to take an io tcp ip tcp socket and then it's going to be a double reference which means a right hand value and then it's going to be stocking right [Music] so that's our new create function now we need to propagate that out to our tcp connection so we'll just copy this again and we will put this inside here right and we will go inside here and make sure that our concept is there and we will put socket here [Music] and then finally uh with all that done we're going to actually take our socket here so notice if i just put socket here it's going to say give me an error because once you move something in and its right hand value once you reference it again it becomes a left hand value so we need to uh to forward it we also need to move it uh one more time into our tcp connection here and then and this again i believe will need a stood move as well so that's how we move our socket from our server into our connection object so so that's how you move it in you declare a move create a right right hand side object so all this does um it converts the type into that that double end type that we saw so we convert that we move it into our tcp connection create function and then once this gets received we move it into our tcp connection function and then with that created we move it into our socket object and what that will do is it will have one initializer one initializer that will do everything for us right so let's go back to our server and see what else needs to be done so with that done we should be able to uh theoretically um it'll go inside here and it will do exactly what it did before but now it will work for as many uh clients as we want so let's just play that and make sure that it is not dying and i move stuff so i gotta remember reload my cmake project perfect so we have our server now our server is being hosted at one three three seven with our client here we can actually just do that quickly hello beautiful client and uh we can see here it does the same thing sends 25 if we close the client we'll do this here now one thing we're going to do now that we're um we're going to be disconnecting the client from the server a little bit and it might not work uh properly we are going to be switching to telnet now i'm using the ubuntu subsystem or the linux subsystem for windows or windows subsystem linux or whatever they call it um i'm using that because uh telnet works better so make sure you have telnet you can get it on windows by uh if you go inside uh programs uh if you go into apps and features here uh it might be at the bottom here yeah so at the bottom of apps and features there's this programs and features and here you'll find turn windows features on or off and in here you'll see uh telnet client uh you'll want this if you're using the windows one and then you'll just open your command line and call telnet like this and you can do localhost one three three seven and you'll see here that we connected just the same way as our client did before and we can quit and it detects that we quit properly right that's exactly what we want now we're going to be able to talk to each other using clients using this telnet client for now and then when we make our own client we're going to just make sure it behaves the same way right okay so now we're going to uh work on the connection side of things a little bit before we move uh too much further so we've got it so that we're taking in our socket object here right now what we want to do is we also want to be able to kind of have a username a little bit uh for now for our clients right so what we're going to do is we're going to keep a connection name when we create the client so inside our connection uh header here let's remove this uh we're going to store name and we actually don't need this message anymore because that's going to go away so from in here we're going to keep a string and it's going to just say it's going to be our uh let's say our username right [Music] and then we're going to just grab the username now so um one thing we will need is we'll need to keep a uh error error code so we'll get the error code you see we will take a spot for the we'll create a string stream that will hold the name and what we will do is we will uh put the socket remote endpoint into the name and then we are going to store the username as name.string so this will keep our username um on connection and that's all we need to do in here on start we're actually going to rewrite this entire function right so we'll remove that and uh we'll go into our our our function here so this is our tcp connection as it is now instead of doing everything within our start function what we're going to do is we're going to kick off an asynchronous process and what why is this connected all right this is not i o system so it'll just be boost system sorry all right let's build this all right with that with that being built properly um let's get back to my explanation so what we need to do with our start function is we will gather the information we need from the client so we're actually going to put some arguments inside our start function here arguments which will actually these are going to be callbacks that we're going to call from our server or from our main application so that we can customize what our application is actually doing from our main server here so we can make this a more generalized server and from within the start function what we're going to do is we are going to kick off an asynchronous read function which will make sure that we're always waiting for a new message to read from the client right in addition to always waiting for a new message from the client we're also going to need to be able to send the client new messages right so to be able to send a new message what we're going to do is we're going to create a new function called send message which is just going to take a const string reference message apparently send message is already defined so we're going to call this post like i did in the um in the repository so this was this is gonna this function here is going to post a message to the client right now the asynchronous functions what this is going to do things are going to be a little bit more uh split up uh than you would probably think they should be but um our async our our start function is going to call a function called void async read and this is going to wait for a new message from client and then once a read a new message is received it's going to call another function called void onread which takes a boost system error code and it also sends us the bytes transferred the number of bytes that that was transferred similarly when we call post what it's going to do is we're actually going to queue up messages so you can uh request to send as many messages but it's only going to send one at a time um but similarly on our post message post function what we're going to do is we're going to call a function called asyncrite which actually we're going to rename this one here to fit with the rest async read just make it camel case instead uh and we're going to call this async right [Music] and similarly whenever a write completes we it's going to call a function called on right which is boost system error code and same thing bytes transfer [Music] and that is all of the functions we need now as i mentioned uh when we write a message we are going to write it to a queue so the queue that we want to be writing to is um going to be in a standard queue so no need to repeat myself too much and this will just be a queue of strings and we'll call this outgoing messages and q is not declared so let's make sure to include in addition to that when we if we go back here let's go back to our tcp connection and actually just go back here you'll notice here that we created a buffer here and we prepared the buffer and then we um we read into the buffer right let's just put this all back similarly um when we read in our async read we're going to need a place to store each message as they come in so we're going to create a buffer inside here so this one is going to be simple enough it's going to be io stream buffer [Music] stream buff we'll call this stream [Music] and and we're going to initialize our stream buff object with a max capacity of six five five three six which is a max int i believe um just basically just so that our stream buff the buffer doesn't just go on forever by accident if we make a mistake and it'll it'll cut itself off so that's pretty much everything we need for our tcp connection object for now we will come back to the callbacks later and now let's just implement some of these functions so first we will go through and we will just scaffold out all of this stuff there we go save that okay so under tcp connection we're gonna do first thing as i mentioned before all of our all our start function is going to do right now is start our async read uh loop and let's let's implement that loop now and we'll come back to post after that our async read loop will uh be basically what we did before um but uh slightly a little bit different so it's gonna be async read until uh so i think i can do async read until and then we give it the socket we give it the stream buffer that we want to put the message into then we give it a delimiter so it's going to read until it finds a new line so it's going to read one line at a time which is fine for our chat object it's something we're probably going to go back to later as we make this more into a game and what we did before is we made this a shared from this so we're going to just make sure to say self equal shared from this then we give it the uh [Music] we give it the signature that it's looking for which is uh basically exactly the same as what we gave our on read function here and then we will call on read error code bytes transfer [Music] and this will be self uh so on read [Music] in our on read function it's going to be fairly simple the first thing we're going to look for is if there was an error on the connection so if error code ec is truthy which means there was an error uh what we're going to do is we're going to socket dot close we're going to close the socket now one thing uh that can happen here is when you close a socket it can throw an error so if you don't want it to throw an error you can actually give it an error code object where it will write its error into now i'm not going to do that because it i'm not going to check it afterwards because i'm not concerned about the error here but just know that if you do it this way here you could check again what the error was if there was an error and everything will work fine and then here we would um add an error handler [Music] this is where we would call one of the callbacks i mentioned that we're going to add to the start function um just if there's an error we can send that error to the calling to the calling function we'll call it once we've determined that there's no error uh we also want to return here and make sure we don't continue once we've determined that there's no error what we're going to do is we're actually just going to read in the message and we for now we're just going to print it a console but eventually we're going to pop that message out to our our consumer so uh we will it's we're going to do it pretty much the exact same way we did up here so we're going to make a string stream string string message we will into our message we are going to add our connection name and then we will uh not connection name we call the username so this would be yeah username and then our username and then we will add our we will create an input stream from our buffer from our stream buffer and then we will read from that buffer now i'm going to pause here and just explain something real quick um and then uh actually before i continue let's just print that message that's our message here and then add a message handler and then at the end we will kick off the next loop of our read so that we can um wait for another message and that's the basic loop so it's asynchronous so this is just going to wait on a thread now the thread itself we can talk about later but um our you can basically think that your i o context defined in your server here your i o context that's going to be your generally what you can we'll consider your thread is technically an executor but just consider it your threat for now um and we can get more in depth as we move forward um and we want to dot string here and we will put it on the end line okay right so this read buffer thing so in general notice how we get this bytes transferred uh variable here now usually what you would do is do something like a stream buffer dot i think consume and you would do bytes transferred here right and this tells the buffer that you've consumed a certain number of bytes and that it can clear it clear those number of bytes from the from the buffer now i did some testing and read buffer here actually under the hood calls a consume on the stream buffer itself so this is actually not required in this case but we we can leave it there anyway but just remember that if you call read buffer it's going to consume the the bytes on the buffer so uh if you want to do multiple things with those bytes you want to use different function than read buffer that's that's the point i'm getting too now that's our basic uh on read loop this will be able to wait for a message from the client and it will print it to the console now we should be able to test that already let's build this and see what it gives us okay and i'll hit play on that that should start the server good and let's go back to our telnet connection let's say hello and you'll notice here that um it received the message and it it printed the message and i'm so cool well i've got a name and now you'll notice here there's an extra new line here because since we read until there was a new line uh our message actually contains a new line so we don't actually need it so let's just restart our server now and now if we open it up hello my name is molly i have a lolly i want to play with my dolly see and we have our chat message here now right now the server is the only one reading these messages but that's uh it's a good start so now let's make it so that we can send a message to our back to our client uh later right so if you notice here when we do this we send the message but we we don't get a chat server will generally echo your messages back to you so for now we will write our create our write function so the first thing we want to do is post now what post is going to do is when you request to write a message to the client it's actually going to cue it and the queue will keep on running until it's out of messages so what we're going to do is the first thing is we're going to say we're going to keep it here and is the queue idle so is q idle equal q dot empty so the q the q is idle if the q is empty then we will add to the queue we go q dot um i forgot the i forgot the push push i think push push the message into it then if the queue is idle we will kick off our async right loop right so this is fairly simple to to grasp if the queue is empty it's idle we pushed a message into the queue and if um we probably called this outgoing messages right [Music] yeah we called this outgoing messages and if the queue is empty it's idle we push the message into the queue and then if the queue is idle we start writing if not we assume it's already trying to write the queue out so we'll go into our async write function here now this one's uh actually fairly simple as well it's going to be very similar to what we did here so we can actually uh copy this just to make it simpler so instead of async read it until we say async right now instead of the buffer what we're going to do is we are going to say uh io we're going to create a buffer out of the front object on the queue so outgoing messages dot front then there is no completion condition oops i deleted too much and then we create our function which has the same signature as our on right which is also the same signature as on read so we don't need to change anything and here we change it to on right and then we're done so the same thing that we did here we're going to check if there's an error and if there's an error we will close the socket and return to the error handler and afterwards what we're going to do is we're going to remove the message from the queue so we're going to say outgoingmessages.pop which removes the message from the front of the queue and if the queue is not empty we will kick off or write loop again so if uh messages dot empty and then we let's put the not symbol at the front we will say async right and we will continue the loop and that's our entire right loop so now we will be able to write to the client now for this to work we are going to need to um implement our broadcast function here so our broadcast function's gonna be simple enough it's gonna be four um we're gonna take the connection in our connections and for each one of our connection we will call post and we will just post the message now that's just going to broadcast all the messages now this is still not going to quite work because um as it stands now nothing is calling this broadcast function and this is where we're going to start going into our callbacks and we're actually nearing the end of what we want to do here today for the server so what we want to do is we want to be able to handle a few conditions from our server so if we go back to our tcp server here we want a few things to be able to be done on our server and we're going to create a a few definitions um first of which i have functional which is good we're going to need it we're going to say using we're going to create an on join handler and what this is going to do it's going to be called whenever a user joins the server so stood function void ccp connection pointer like that there we go that's better so when someone joins a server it will we want it to send uh trigger this on join handler sending down the connection to it we also want a on leave hander handler so when a user leaves the chat or leaves the server we also want to be able to catch that so you use on leave handler and this will actually be the exact same definition and finally we also want to be able to define what will happen whenever the server receives a message from the client so using on client message handler and this will just be a stood function void and which will have a string inside right now we need a place to store these now i'm going to use a very naive way of doing this there's only gonna be one handler per server but you might want to do what we did in some of the other videos where you can register multiple handlers for the same events and all that but i'll leave that as either something we do later or uh something you can do yourself later so we will add a few public members here and these are going to be public just because there's no reason for them not to be public but we're going to create an onjoin handler and we'll call it on join on on leave handler on leave and on [Music] client message handler on client message so with these uh three things uh defined when we go into our server and we look at our start accept function we can already start calling some of this stuff so right after we create the connection we know that the that someone has joined so what we want to do is we want to say if on join so if onjoin has been defined we call on we call on join with the connection uh the other two we will leave um we will leave for a minute here i'll show you how these callbacks are gonna work right so we know that uh if we look at our server so with this defined we can actually start showing you i can actually start showing you how these callbacks are going to work before we implement the other two which requires some changes to our tcp connection as well so in our server project you're going to see here that right now i just have server.run the purpose of our our handlers is so we can use server dot on join and this will be this will be a tcp tcp connection pointer server and then we can also do on leave on leave and we can also do server dot on client message which is going to be a little bit different which is just good string we can do we have to make this a constant string reference string reference message like that so we can actually uh pass in the functions from our application layer on what we want these things to do so for for now we want to be able to get our connection name here so we need to add a function here for being able to get our our username so in we'll do this right underneath here uh let's just say inline uh [Music] stood constant string [Music] string reference get username const return username right but we're basically going to return the username and now in our main here we can actually just print it off stood out user has joined the server and we will go server dot we can just take a username like that that'll work i know well uh it should work anyway oh yeah just good and fine and we'll have a new life so now when someone joins a server we'll get that message so now let's play that and see what that gives us and that should be fairly straightforward we should see when we join the server users join the server and we get the username and we still have it so when we receive a message it'll print it out as well so we'll see that the names and stuff match up so we have that remember to keep an eye out on this window here we join users join the server hello and there you go you have a hello message here all right so that's how we're going to define our callbacks let's um do the similar thing here user has left the server so when the user leaves the server we'll get a message saying we leave the server and on the client message so whenever a message comes from the client we are going to say um server dot broadcast and we will broadcast the message to all of the clients right now the reason we want um we want to also capture the server right yeah and we don't want to do that we want to capture a reference to the server and that will work i know it'll work so the reason why we can do it this way now that we have a server object up here inside our networking library is that if we want to say parse the message and like do do game server things and then send send message to client or whatever we want to do we don't this doesn't automatically make this a chat server anymore is my point so when we get a message from the client which right now is just a string but imagine if this was a type t or whatever it was just a more generic type then this would be much easier right so that's what we are working with here so now um this is never these are never going to be called because we don't call them in here so what we need to do to make that happen is basically the last two steps we need to do on our server on the server side of things so in our tcp connection we want to declare two new handler types so above our tcp connection here we will say using message handler equal stood function [Music] stood function void stood string [Music] and i did that wrong because i'm really good at function syntax sometimes so the message handler what easy enough when um it will just it's just a thing that you can pass a string through which is basically the message it's the exact same thing as we saw on the server side but which just popped up to the connection level the error handler itself um you you're probably going to want to get more intricate with it but for now we are just going to make this a void function that has no that has no parameters so that uh we can just say there was an error basically which in this case since we're closing the socket as soon as there's an error it's an analogous to a user leaving the server so with that done we need to change our start function to take these things in so we're going to say message handler and this is gonna be a right-hand side variable as well which is gonna could be moved in but we can also declare it in line which is the intent uh message handler [Music] and we will say um error handler again right hand side error handler like this and we will need a place to store these so we will store these down here which is going to be message handler message handler and error handler now we need to change our definition of the start function and this is actually just going to be fairly simple we are going to change the signature and we are just going to store them so our message handler is going to be equal as we're going to move in the message handler from the parameters and we are also going to do the same thing for our error handler like that then in our here at error handler we can just say error handler [Music] and same thing down here error handler here [Music] and finally on our read once we receive a message we have the message handler here we're going to say messagehandler message.string and we can actually just remove that so what that will do uh just went a little bit quickly so we stored our handlers inside our start function and then when we receive a message through our async read on read we either throw an error or we say that an error has occurred or we also we send a string down in through our message handler and we do the same error handling stuff on our right function so with that done what we will do now is we go into our server and we can add the final few things so really all we have left to do is add our handlers in here right so what do we want to do we want to pass in this and this is going to be our message handler so we say it's our string here and we're going to make it a const stream reference and we're going to say message [Music] and this is actually just going to be very very simple so we can do this in one line actually because it's not that much so we want to check that if on client message is defined we will call on client message with the message and that's that's going to be our message handler for our error handler it's going to be a slightly more complicated function so we are in here we are going to grab a reference to everything inside this function we will create a weak pointer to our connection so weak pointer to our connection and since we could put a parenthesis there but uh those are optional there's no no um arguments and here uh we're going to lock the pointer just make sure that the pointer is valid when we pass it in then we are going to say if the weak pointer or the shared pointer was reserved properly with using the lock we will then remove also remove the connections from the connection which is why we change it to a set and if we properly found a so if the connection is still valid the connection object is still valid and we found the same connection object within our connections set we then we'll check to see if only is defined and if it is we will call on leave and that's our entire server so now we should uh be able to uh i've got an error somewhere let's see where uh well right i gotta give it the connection so shared well that should be everything we need for um for our server let's build it to make sure that uh we didn't make mistakes along the way now this is still really being dumb air handler not bound what oh one one too many hours my fault do that again there we go so we've built everything now let's run our client right now if everything went the plan we should uh so we saw that it joined if we leave uh using the special keys you see users left the server so it's detecting join and leave properly and now let's check our messages if we say hello we get echoed back our hello message and that tells me that we should be able to create another client now close that and if i say hello on here hi i'm the new user [Music] you'll see here it now prints hi i'm the new user and then we can say hello new user i'm so cool aren't i you see they each have different uh ids on here and they're being passed around properly because they're being baked into the strings being sent around but that is a working chat client so that's great um that's our server now i'm going to uh create the client so the client itself is going to be very similar to what we did with our tcp connection actually it's basically our tcp connection is a client for all intents and purposes right so for the most part we can just use the same things but we will write rewrite it from scratch just to be um to be thorough so let's create a new folder uh inside our wife networking and we'll call it client uh we'll also do it in our source folder we'll call this client as well and we will create a new class called tcp client uh we will make sure that this does this in snake case because that's the convention i like for my file names we'll add it to github we'll make sure that it didn't add it inside this nonsense because i forget always forget to uncheck that checkbox uh here one thing i'm going to do is i i recently learned that i can just do glob recurse dot cpp and that should be enough to do everything i need now in our tcp client we will name space it under moife and we will do the same thing in our cpp file namespace moife and we will move this wife we will move this cpp file into our client folder into our source and that does not belong anywhere and then in our main.cpp we can do include include moife networking client slash client tcp client.h and we'll just reload uh the project just to make it clear right now that should resolve that so we're going to do the same type of stuff we were doing before we're going to namespace io equal boost asio and we don't have that uh included so we're going to include include boost slash seo.htphpp [Music] um we are going to declare a message handler so we'll do that by using um message handler equal oops handler equals stood function void stood string and that will be good so we want a constructor i'm going to go a little bit quicker on this one because we've gone through a lot of it so our tcp client will take a address and report so const stood string [Music] reference address and int and go on and port having trouble today a little bit we will be able to run the client we will be able to stop the client and we'll be able to write messages to the client string constant message right for consistency we'll call this post for consistency with our server um then on our private [Music] uh we're gonna have similar stuff that we had on the server we're gonna avoid async read and we're gonna avoid on read which is going to have a boost system error code bc size t bytes transferred [Music] now we will just copy these and change this to async right and async read i think right and on right async right and on right we will be able to define our message handler so uh same thing on our server we will say message handler on message uh one too many s's up here and then in our private fields we're gonna have very many much many of the same things that we had in our server as well so we're gonna need an i o context so we're going to call i o context and we will initialize that we are going to have a socket io ip tcp socket and we will call it socket and we will have a resolver uh we haven't gone over that but the resolver will essentially resolve uh endpoints for you based on the names that you give it so you can give it like localhost or google.com and it will resolve it and will give you a list of addresses that you can connect to well technically it'll give you a list of endpoints that you can connect to so that being said we're going to create io iptcp resolver and we'll call it resolver and we are also going to create a actually we're not going we're not going to store the resolver we are just going to store a list of iptcp endpoints which is under results resolver results type endpoints [Music] and then we will need a same thing stream buffer to hold incoming messages so stream buff which again will be 65536 and then we are going to hold a queue of outgoing messages so it's going to be still queue string outgoing messages and we'll initialize that to nothing and we will include q there we go so that will be our entire uh interface for our tcp client so notice again uh these are the same method methods we had in our server these are basically the same methods we had in our server as well but instead of start we are using run and we are giving it the ability to stop the um stop the client because it's actually going to be running on a completely different thread so let's uh quickly let's implement let's implement all these functions um let's put all of these into our a cpp file there we go all right so we have them all here now tcp client a couple of things we need to uh there's a few things that need to be initialized pretty much right as you init you construct your application because there's no default constructor so the first thing is socket which we will give our i o contacts that we initialized before we will also um actually that's pretty much it i think we will then also populate our endpoints by creating a resolver so io tcp io ip tcp resolver resolver and we initialize it with our i o context then we say um we want to get our list of endpoints so our endpoints will be resolver dot resolve and it's going to be not underscore resolver.resolve we will give it the address and we will say stood to string we will give the port and that will get our list of endpoints for our running method um oops forgot a semicolon for our run mess method all we're going to do is we are going to attempt to connect to the endpoints one of the endpoints and then if it is successful it will um if it is successful it will just start the read loop so it'll be if and it'll be async connect and it's not i o async connect we give it the socket that we want to connect on we give it the list of endpoints that we got on the resolver up here we then give it a lambda or a function pointer which takes a which will return a error code and it will also return the endpoint that it connected to iptcp endpoint and point [Music] now we're not really concerned with the endpoint itself here but we are concerned that there are no if there are no errors then we will say async read and at the end of all that what we want to do is we want to do our i o context dot run right so if you remember from the last lesson if you go inside here we have we don't have an io context here i don't think we are doing a socket read some we're reading one and we're just doing a loop which this was actually really badly done so let's look at our main here if you look at our server which we did last time in a much better way than we did our client when we start our server we start the accept which basically just defines our asynchronous functions and then we call a run which basically runs and blocks so this will start the thread and will run until there are no more until there are no more asynchronous options operations in the queue so considering that our connection was always waiting for a message there will always be an asynchronous operation in the queue since there's something always in the queue we can just assume that i didn't give this name we can just assume that i o contacts run this will block here pretty much forever and will always keep on running now one way to cancel all operations and to make this exit would be to close the socket right so what we are going to do is we are in our stop function we are going to call a socket dot close now if we did this by itself in this naive way it will almost certainly throw an error which would bubble up the stack and actually cause make it look like your application is doing stuff you don't want it to do so to prevent that error code from being thrown and bubbling up we can do what we did before and [Music] define an error code object here and pass that into close now this will just silence any errors and you could if error code here uh process error and let's just add curly braces here just so you know what where that goes right so that's our stop function our post function is going to be pretty much identical to our post function in our tcp connection so again we will just check bool is the queue idle is equal to outgoing messages dot empty we will push the uh message outgoingmessages.push we'll push to push the message into the queue and if the queue is idle we will async right async read again is pretty much identical to our server connection as well so all we're going to do is we're going to say i o async read until on our socket using our stream buffer and we will give it a this pointer now the reason we're only giving a this pointer here is because we're expecting that there sorry we've got an s we're expecting that this function or this client that there will only ever really be one client on this thread and the client will live until it's no longer needed so we don't need to keep a shared pointer to the client because we will just we assume that everything's working properly until the end so again we will take a boost system error code error code size t bytes transfer [Music] and just like the other one we will just say on read ac bytes transfer our on read function is going to be actually identical pretty much to the other one as well so if error code we will just close the socket um which we could actually just call stop actually and we will call return and then same thing here we will string stream message we will put the message into the [Music] uh we will put the buffer into the message by creating an input stream uh of type sorry i'm getting a little bit winded here stream buffer and then we will um we will actually can i do this dot read buffer yeah like that and then uh we will call our on message [Music] function and then we will kick off the read loop again async right again identical to what we were doing before so i don't know why this is uh mad at me oh right uh we didn't give it the delimiter character like this and async right here is not what we're doing here that's the next part async right what it's going to do is it's going to basically do the same thing as this one but we are going to remove the delimiter and we are going to take the front of the queue and we are going to call async right so we will write async right take the front of the queue instead of the stream buffer so we say i o stream buffer or i o buffer yeah i o buffer i o buffer and we will take the outgoing messages dot front and we will on right and then async right is just gonna do that and it's going to pop the messages so outgoing messages dot pop and if it's not empty if not outgoing messages dot empty [Music] we will async right and that's our entire client now we went through that quickly but uh it's literally it's the almost it's 95 the same code as what we did inside our connection with the added idea of we added a resolver then we run we run the context we connect to the endpoint and if there's no error we start reading from it we have a stop function that gets called from our on read or on right or on error and um [Music] and yeah that's that's pretty much everything and we would do a read loop a basic read loop a basic right loop and that's our entire client now uh the last final step of this video is to update our client now what we're going to do here is we're actually going to remove the entire code that we put last time we are going to remove everything except our tcp client here and we will remove this because we don't want access to raw networking concepts uh and we will keep i o stream probably [Music] because we'll want to be able to print stuff so first thing we're going to do is we're going to using namespace just so we don't need to type it over and over again we will create our tcp client so tcp client our client will be we're going to do it at localhost so we should be able to do localhost let's try it and we will say it'll be point port 0.1337 so that's our client we will define what we are going to do when we get a new message client dot on message equal we don't need anything const stood string message and on message we're just going to print it i'm just going to print the message now with this is pretty much everything we need to do for our client because all we're all the client is really doing at this point is listening for messages now we could just run the client like this right so we can do client dot run and that will run the client on this thread so let's run our server and let's run our client now in theory the client should be listening so you'll see the client is here listening to one it should be listening to messages so if i go here and i say hello you'll see that the client receives a message and prints it now we want the client to be able to talk otherwise it's not a chat client right so let's uh let's let's do that right stop the client the server can stay running because we're done with the server so the issue is that as you saw that this client.run actually blocks this client.run blocks the main thread so we need the network or the input thread to run on a separate thread we're gonna kick the client off to a separate thread um for our purposes here so so what we're going to do is we're going to include thread and we are going to wrap this client.run into a thread scd thread t and we give it the client we give it no parameters and we just call client dot run like this right yeah that looks right now this will kick it off to its own thread now if we run this you're gonna see that if we just run this it's just gonna run quickly and then immediately exit because it's running on a thread and it actually calls it creates an error because it doesn't shut anything down properly right so the one thing we want to do is before we leave we want to call p dot stop not t dot stop we want to call client dot stuff and client dot stop will cause the thread to uh its context of stop and then you can do client you can do t dot join to wait for the thread to end before uh closing the application so now in theory it should run the thread for a little bit stop it and quit the program um didn't quit the program like it should have well let's just wait a little bit we'll go back to that in a second i will continue with the client this should have worked it worked in my testing so yeah so we'll just go back we'll we'll finish up the client and then we'll see why this is not working so now all we have to do at this point um assuming that this will work eventually is we need to do an input loop so we can start sending messages so we'll do we'll put it in a wall true loop like this what we're going to do is we're going to get a message from the input message and we are going to get line [Music] like this and that will get a line of input we will then uh check to see if that message is a quit request so if message equal quit which is we're just going to backside backslash quit and if it is we will break otherwise we're going to add a new line to the message because remember the server if you remember the server connection is reading until it finds a new line getline doesn't give you the new line uh character so we're just adding that new line so that it will properly terminate or properly get caught on the client side or on the server side and we will finally call client.post and we will post the message now uh we should have a working client so we got this here we say hello and we get the hello message now let's go inside here uh hi so we get the hi message we say hello there and we get the hello there message so it's communicating properly and if we want to quit we hit slash q and it quits properly so what i'm guessing is happening here um if i had to guess is this is spinning up uh but client.stop is being called so quickly that the client is not uh connected um yeah that's that's my guess is that it's real it's uh just going too fast so there's a slight race condition but realistically since we've got other things going on this works fine so if we didn't put this client.stop here [Music] and we didn't put this here you'll see that we get an error we'll see that we get an error here and if we quit we get an error it tries to quit the program but the thread is still running and it throws an error if we just call t.join you'll see that it will uh quit the input loop but it will be stuck waiting for the thread to finish because we never called stop so we're here we call this and you see now it's stuck in a thing and we're not getting any messages and that's why we put in the stop function [Music] now if uh let's not do this say hello you'll see that they all all get the message just like that and so this is how you can do a simple chat server using bustasio and c plus i hope you enjoyed um or at least found this informative and again leave all the the engagement crap down below and i will see you next time
Info
Channel: Ozzadar
Views: 660
Rating: undefined out of 5
Keywords: c++ chat, c++ chat app, c++ chat program, c++ asio, c++ networking ts, c++ boost, c++ boost asio, boost asio, networking TS, C++ chat application, c++ networking, c++ TCP, TCP programming, networking programming, C++20, ozzadar, c++ networking tutorial, c++ networking ts tutorial, networking ts tutorial, c++ chat tutorial, c++ networking library for games, boost asio tcp, boost asio c++, boost asio tcp server, c++ networking programming, boost asio tutorial c++
Id: n2BxlZ-A9QU
Channel Id: undefined
Length: 75min 37sec (4537 seconds)
Published: Wed Sep 22 2021
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.