 Hey everybody, it's Brian and welcome to the 68th Qt tutorial with C++ and GUI programming. Today we are going to make another server. This time we're going to make a multi-threaded server. So we'll call this multi-server, put it in the usual place. If you watched the last tutorial you probably noticed that it only did pretty much one connection at a time, which is pretty weak. So we want to handle multiple connections at a time. So first things first, let's add in our network module. And then we need to make a couple classes here. So the first one we're going to make is going to be the server. And we're going to call it myServer. And the base class for this is going to be a QTCPServer. Here it's QObject. Make sure you spelled that right when you do that. I made that mistake one time. It was actually kind of difficult to debug. All right, the other class you want to make, we'll call myThread. And it is going to have a base class of QThread. Here it's QObject. Once again, make sure you spell that correctly. All right. And then let's compile this at this point just to make sure that we spelled everything correctly. We don't have any booboos. All right, we've got a good clean build. Let's continue. So first thing we want to do here is we want to open up myThread. This is going to be a thread that your connection is going to run in. And what you really need to understand about this is that every time you have a new connection, a new thread is going to be created. And we're going to discuss a little bit about that at the end. So because this is actually a thread, one thing we need to do is actually have the run function in there. So we'll say void, run. And then we need to add in our signals and slots. So we'll say public slots. And of course, we want the slots of the socket, which are the ready read and disconnected. But we're going to just go QTCP. Ah, we didn't add in our includes. Sorry about that. Let's go ahead and add in our includes before I forget again. QTCP socket. And QDebug, just so we have those in there. All right, now we can go QTCP. I hope if I could spell that right. QTCP socket. We're just going to call this socket. And we also need the socket descriptor. Don't really need it, but you're going to see what's going on here in a minute. Let me just type this out. The socket descriptor is the underlying socket ID number from the operating system. And we're going to actually want to modify our constructor here a little bit. So we'll say int id. And then of course we need to do that over here. And then we need to implement our run. And that's where the socket's going to start when it does its multi-threaded function. So we're just going to say thread starts here. Go back in here. Now we need those slots. So what we're going to do is just look up QTCP socket. I'm doing this just in case you didn't watch the last tutorial. You notice how there's really not much in here. It's because it inherits pretty much everything. So it's from the QIO device. And we want the... Signals, here we go. Sorry about that. We want the ready read. It tells us that there is information available. Well, that didn't work out well. Having nothing but problems today. Here we go. Copy this. Still didn't copy it. Well you know what? I'm just going to type it in then. Void. Ready read. Obviously I've got my keyboard still set up for gaming. We want disconnected. That way it's not going to conflict with the thread disconnect. So those are our slots here. Our socket, when we create an instance of this, is going to just emit our signal. And these slots are going to consume them. Very simple class we have here. But now we need to implement it. So, first things first. We need to say this. Socket descriptor. Equal. ID. That way we know the underlying socket ID number. Now we could at this point actually create the socket and set the socket descriptor right there. But I just want you to understand that there's a very big difference between the socket descriptor and the socket itself. Okay. Now what we're going to do here is we're going to actually kind of add in a signal. Sorry, I'm kind of scatterbrained today. And we're going to say void error. That way if something goes wrong we understand something went wrong. QTCP. Socket. We're going to say socket error. We'll call this socket error. Just in case. I don't really mind messages. I just don't like it when I get like a thousand messages saying, hey, how come this won't work? And then I'm trying to figure out how your internal network functions. All right, so now our header is pretty much complete here. Now in our run, remember this is when the thread actually starts. We haven't connected the socket yet, but this is when the thread starts. So what we need to do here is just say Q to bug, just so we know that something's happening. And we'll say starting thread. Kind of messed up my quotes there, didn't I? I bet some of you are probably giggling my horrible typing. Anyways, that way we know that we're actually starting the thread. And then we're going to say socket equal new QTCP socket. Make this the parent. Actually, no, I forgot. Don't make this the parent. You're going to start having all sorts of issues if you do that. I had some testing and it wasn't very good at all. So socket and then we'll say set socket descriptor. And this is where you set the ID. You can just set the socket descriptor there. And so we're saying if not, in other words, if something bad happened, then we're going to just admit our signal and we're going to say, hey, there was an error. Something bad happened. And if you've looked at the, if you go into here and choose examples and then go into where's networking. We're up at the top. If you go into the fortune server and then there's a threaded fortune server, you'll notice this is very similar to that. Okay, now we need to connect our signals and slots up. Now, one thing you should know is that we're going to do this a little bit differently. So we're going to say connect, socket, signal, ready read, and we're going to connect it to this slot, ready read. And this is where the different part comes in. We need to treat this a little bit differently because it is multi-threaded. So we're going to say cute direct connection. And what that does is it makes a direct connection to that thread. Otherwise you're going to start having all sorts of cross-thread operation errors. And then disconnected, connect up our other slot here. There we go. Now we've got our signals and slots all connected and ready to go. And then we can just say cute debug. We want to know that the socket connected. So we're going to say, let's see here. We want to know which one it is. So let's say socket descriptor. Because we're going to have multiple of these, right? So we want to know which one's actually doing what. And I know this might look really confusing and we're going to go over this. So don't worry. Just trying to get the code out of the way. Client connected. Now of course we need to implement our ready read and disconnected. So we'll say void, my thread, ready read. Remember this is when we're actually got data in the pipe and we're ready to go. And then void, my thread, disconnected. And this is when the client socket is disconnected. So we need to treat these very differently obviously. Now what we want to do is basically, oops sorry, I just nailed the microphone. What we need to do is make what we're going to call an echo server. Echo server used to be really popular back in the day. You rarely see them anymore. And what it is is you send information to it and it sends you the exact same information back. It was a very handy troubleshooting tools that some administrators used a long time ago. So we're going to say que byte array. And we're just going to call this data. Equal socket, read all. So now we get the information. And we want to know that they actually sent us data. So we're just going to say data in. That way we can see on the server window what was actually sent. And then we're just going to write that back out. So we'll say socket, right. And we're just going to write data out. And that's all there is to that. Now one thing I should probably back up here. One thing you should know is we are missing a critical step here that I want to discuss in my thread run. If you know anything about threading, when it goes through this run command, it'll hit the end of this and stop. So if you haven't noticed already, we have a huge flaw, meaning it's going to just drop that thread and destroy it. So what we need to do is called exec. What exec does is it creates a message loop, meaning this thread will stay alive indefinitely until we tell it to close. Now in the disconnected, the first thing we need to do is exit. And give it an exit code. So we're telling the thread at this point, okay, we no longer need you. Go away. That's how we can do the signals and slots. If you emit exec, like if you just don't have it in your code, your code is going to run, get to this point, and the thread is immediately going to drop and the socket is just going to sit out there and never, never land. And you'll get some pretty unpredictable results at that point. So in disconnected, we need to do two things. We need to A, get rid of that socket because it's still in memory. So we'll say socket, delete later. And what that does is it flags it for deletion. So after the execution context leaves this function, Qt will automatically delete that object for us. And then we want to tell the thread to exit. Now if you're wondering why we don't just make socket parent as this, it's because you get some real funky cross-thread operations, or at least I did. Let me know if you have different results, but I had a really hard time getting that to work. Then we just want to know which client is connected. Let's just go ahead and compile this real quick. Data was not declared in the scope. Ah, yes, see, that's why we do a very nice little compile here. So we miss all that fun. All right, let's compile it one more time. Not a signal. Okay, let me pause the video and see what I did here. I should probably slow down when I make these things. Silly me. All right, there we go. Should get a clean compile out of this now. There we go. Sorry about that. It's Friday. It's been a long day. I'm really trying to get this video out before the pizza gets here. All right, now we've got our server, which we haven't done anything with. So what's the big deal with the server? You see how it inherits QTCP server? Well, we have to do two things. We have to start the server. And we have to handle incoming connections. And that's really the whole job of the server here. So in the public section, we're just going to say void, start server. And then we're going to make a protected area. If you remember from our inheritance concepts. Void. And we want to do incoming connection. And it's actually socket descriptor. Not that it really matters much, but you get the point. So we have our incoming connection. And start server. So let's just save a bit of time here by copying that. And my server, we just need to implement this really quickly. And this is where we're going to actually start the server. And then we're going to just do this. And this is where we're going to handle incoming connections. I should probably put that in the proper namespace. We'll have all sorts of weirdness going on here. All right. So let's go ahead and start the server here. We'll say if, not this listen. And this is old school stuff. You know, we did this last tutorial. You know exactly what to expect here. We're going to set it to any. And we're just going to set port one, two, three, four. Just figure out what you can set any port you want. And then we want to do a queue debug. And we want to know that we could not start server. We want to know that the server is currently listening. So say queue debug. Sorry about that. My fingers happen to go faster in my brain. We'll just say listening. So very simply, we just start the server. And then on the incoming connection, this is where the magic happens. And we need to add a reference here. So let's go back into my server. And we need to actually include queue debug. And let's go ahead and include my thread. And then back in server land here. We need to actually say we have a new connection here. So say queue debug. That way we know that that guy is connecting. And then we want to say my thread. We'll just call this thread equal new my thread. And we of course want the ID. So we need to give it the socket descriptor. And we'll give it a parent. And this is an adequate parent. So we'll add this class as the parent. And now we need to connect up the signals and slots. We haven't actually added those in yet. But what we want to do is we want to know when that thread's finished, we want to delete it later. That way we don't have to mess around with this. And it sounds confusing, but it's very simple. We just do connect thread signal. Oops, sorry. Finished. So we want to know when that thread's actually finished. And then we're going to put the slot back on itself. And we're going to say delete later. So what that does is it flags that thread for deletion. So once it's no longer used, queue will automatically delete it for us. And we don't have to worry about it. And then of course we just need to start our thread. Which calls our run function. We can set the priority if we want, but we're not going to really mess around with that too much. Alright, now in main.cpp, we need to of course add in the server and start it up. And we want to start server. There we go. Now let's go ahead and run this, see if we actually get a good compile. And I've been kind of whipping through this and made a few mistakes. There's listening. And let me go ahead and open a command prompt here. And do telnet. Open 127.001.1234. And you can see how we have a connection. And when we type data, you can see that it's echoing the data back. I press the M key once and we get two M's. That's because this has a local echoes. We're seeing what we typed and what we got back from the server. And if we really wanted to be nice, we could actually say, you know, data from server equals, blah, blah, blah. But I'm just going to open up another one of these. I mean the whole concept of this tutorial is to show you that we can handle multiple connections at the same time. So I'm going to say telnet. Open 127.001.1234. And you can see now we have two clients connected. You see we have 292 and 348. This is 292. This is 348. Now how would you tell those apart? Very simple. You could go into the actual socket and get the remote host and the remote IP address and the remote port. That's how you tell those apart. But the whole concept here is that you can see we have multiple connections at the same time. And when we close one, you see 348 disconnected. This guy stayed live. And we can actually make another connection. And let's just go to telnet. Open 127.001. If you're wondering what that is, because you haven't watched my previous tutorials, that's the local loopback interface. All modern computers have that. And you can see the socket ID is 380. Now if we kill the server, you'll see both of these disconnect at the same time. And see, connection to host lost, connection to host lost. Pretty neat stuff. All right, now I believe a very good review is in order, and I'm going to try and go through this rather quickly, but very informatively at the same time because my dinner's coming. All right, now in main CPP, we have an include to my server. Server, start server, that's the function we made. So when we go into server, you can see yes, there is our start server. And we're handling the incoming connection with the underlying socket descriptor, which is just a number from the operating system. And you can see in the implementation, start server, all we're doing is starting the server. And we're saying if we listen, yes, we listen, otherwise we can't. Incoming connection, we're creating a new instance of our thread. And we are actually flagging that thread for delete later when it's finished. And then we're just saying start. When we call start, it instantly jumps down into the run function and executes this code. But when it was created, we grabbed the socket descriptor. That way we know what number it is. And you should note that we could very easily have created the socket and set the descriptor right here. But I wanted you to understand that the socket descriptor and the socket itself are two different things. The socket already exists. The socket's already connected. I should say it's in a pending state, as far as the operating system's concerned. But as far as our code's concerned, we haven't created that socket, so it doesn't exist. That's the difference between the socket descriptor and the socket itself. And then we connect up the signals and slots. You can see the ready read. So we know when we have data ready and the disconnected. We know when the client is connected. And then, of course, here's our ready read. We're just getting a Q byte array with all the data and shoving that back out into the socket, just echoing the data back out there. And you could send anything you want. And then disconnected, we're just saying, OK, socket delete later. That way it doesn't stay out in memory. And then we're going to force the thread to exit. Actually, that's not true. We're not really forcing the thread to do anything. We're just telling the thread, OK, stop this message loop that we set up. Because without this message loop, you're going to get very, very unpredictable results. I think on most operating systems, it'll go through this code and then just stop. The thread will actually just die because it has nothing else to do. But because we have this message loop, it's going to sit there and sit there and sit there until we tell it to go away. So that is multi-threading in a nutshell. Now, there's some potential pitfalls with this application. I should tell you that 90, 95% of all TCP server applications are written just like this. They do a little special magic, like they make a Q-list and they put the sockets in it so they know what are connected and they can enumerate through them and disconnect or send messages to specific clients. But the underlying mechanism is almost identical to what we've just done. They make a socket and they make a thread per socket. The pitfall to that is threads are very expensive resources on an operating system. So let's go Q-thread. And if you read through this, you'll see, and I think they actually mention it in here somewhere, that Q-thread represents a separate thread of control within a program. Shares data with all other threads and processes independently, blah, blah, blah, blah, blah. Basically, a thread is an expensive operation. You will run out of threads. So if you're having a server running in 3,000 people connect to you, I don't know many computers that are going to run 3,000 threads per app. It's probably going to just crash and die. And then you're going to be scratching your head going, well, it worked perfectly. Why is it suddenly doing that? So there's a better way of doing this. And you should look up the Q-thread pool. And what a thread pool is, it's a collection of threads where you can have, say, two, three, five, ten threads. And those threads alternate and do a lot of work. That's how web servers like IIS or Apache work, is they have just an ungodly amount of resources behind them. But they use a thread pool. And what that does is you have two or three threads that do the work of hundreds. What do they do is they read the socket and if the socket doesn't need to do anything, they just shove it off in the background. Now the thread pool works by the Q-runnable class. Very similar to a thread, but it's kind of a fire and forget sort of mechanism. See it's got the run and that's where your execution will jump in. So you'll implement a Q-runnable class and basically do your code within the run function. Now I'll admit that I kind of played around with this a little bit, but I don't have a full working example. And what I'm really hoping to get for you guys is a full working example using Q-runnable here very soon. But almost out of time, getting hungry and I'm sure my food's almost here, but I wanted you to know the potential pitfalls of using threading. So if you're going to use a purely threaded interface, which is fine, there's nothing wrong with it, just make sure you set a max connections. Otherwise you will run out of resources if you get too many connections. So this is Brian. I hope you found this tutorial educational and entertaining and thank you for watching.