In this lesson we'll go back to the code that we wrote in the previous lessons,
and I'm going to explain everything again, and give you some more details.
We're going to start from the server.
As I've mentioned before, the entry point to any application is its main method.
When you run a program, that's what it will look for.
It's going to look for the main method to start executing from. Now,
this is a regular main method that you'll be using in pretty much all of your Java applications.
And the only two keywords we need to worry about right now, are "public" and "void".
So "main" is pretty much always going to be - public static void -
and it's going to forward "String[] args".
This is an array of arguments, but "public" and "void" is what we'll need in these lessons.
So let me just shortly explain those.
We will use "public" for a method any time we need a certain method to be accessible from other classes,
and then "void" means that this method doesn't really return any value - it doesn't return
any data.
So, when you call the main method, you don't need it to for example calculate anything for you, or print out
something for you, you just need it as an entry point to your application.
Now to explain this try-catch block a bit better,
I'm going to use an example. In this example we're trying to divide
num2, or rather the value of num2 with the value of num1.
They are both defined as integers right here,
and that's when we get into the try block. And then, this is obviously a comment.
And here would define num1 as zero and num2 as 62 divided by the value of num1, which in
this case is zero.
So if this could be done - if this was OK - if the value of num1 was one, then we would be able to see
this printout: "Try block message".
I could have put something that makes more sense here, but let's just go with it.
So if this was OK - if this line of code was all right, we would be able to see this instead.
Since this is a forbidden operation, we are thrown to the catch block. The catch block could be made to
be much more useful than this. For example, you could prompt the user to insert a different number or
something like that.
But the point of this example is just to show what happens in a try-catch block.
So in this case we have a print line which says "Error: don't divide in number by zero",
so we pretty much know that the only issue that could happen in this situation is if a user gives us
a zero.
So that's what we would get - this printout.
Obviously, we would skip this print out because this is the line that would cause us to go to the catch
block.
So, once we're out of the catch block, we're going to get another printout that says "I'm out of
try-catch block in Java."
So this is what our output in total would look like after going through this code.
Now, we didn't have an explanation like this in our catch block in our program.
So I'm gonna change something for example in the client.
I'm going to try to connect to a wrong port number to see what happens.
So we're going to see this printStackTrace() method in action.
I'm going to save this first and then we'll run the server - and then we see that the server is running - "Port
2020 is open", and now we're going to run the client and the first thing the client is going to do is
it's going to try to connect to the wrong port number.
We can see the printout in our console window.
I'm not going to go through all of these errors -
they're actually all related to the first one that says "Connection refused".
Now obviously, the connection is going to be refused because in our client we tried to target a port
that isn't open on our listening server. We can close these now and the server is still running because
it's never got out of this line.
So it's still in this line, waiting for a legitimate connection to come and to be accepted.
So I'm just going to stop the server and close all of these windows, and then I'm going to change the
port number back to 2020, and I'm going to save that.
All right.
We can move on now with our explanations. As I've mentioned before when we run our program,
the first thing it gets into is the main method.
Then we get into the try block where we can see "new Server()"
This is when we call this method right here - and then the first thing we do in this method is: we create
the server socket. Creating a server socket
pretty much means that we're going to reserve, or - we're going to open a certain port, that we're going
to use for this application.
Then we create a regular socket that we're going to use to communicate to the client.
We get this socket, or rather - this socket can be used as soon as we have an incoming connection that
we can accept.
So as I've mentioned earlier this line right here is blocking the rest of the program until an incoming
connection shows up.
This next line is I hope pretty intuitive, since this is just a printout.
It says a client has connected, and then we're pulling an IP address from the socket.
As I've said before, we use this socket method to create a socket object, and this socket object holds
all the important information that's relevant to our connection, which means we can pull the IP address
of the client from this object.
And that's exactly what we're doing in this line - or in this little part right here.
All right.
Onto the buffers.
Now obviously we have two streams of data.
We have the incoming stream of data and the outgoing stream of data.
We're going to use the in_socket for the incoming stream of data -
that is the data that is coming from the client to the server, and then the out_socket is going to be
used for the outgoing data which are all the messages that the server sends to the client.
You may be wondering why do we need three methods here for the socket.
Those methods would be BufferedReader, InputStreamReader, and getInputStream. For the getInputStream,
obviously we're looking at the same socket
in both cases. For incoming data and for the outgoing data, a socket is like a door - you can come in
and you can get out.
So by using getInputStream, we're selecting the data that we want to look at.
Now, this data is going to come in pretty much like gibberish. If you've ever used Wireshark,
you know what I'm talking about - it's going to be messed up.
That's why we need the InputStreamReader to switch that unreadable data into something a user - or a
human being
in this case - can understand.
So that's going to switch that to gibberish into readable text. And then, the BufferedReader -
we need that to put all of that data into a buffer.
I'm going to use an additional example to explain buffers,
and also this example should explain this "true" argument that we used in our PrintWriter methods
for the outgoing socket.
So this is the example, and this is the website if you want to go and check it out.
It simulates an application - or two applications - that are talking to each other. On the left,
we have the sender, and on the right, we have the receiver. The sender wants to send 6656
6656 bytes of data, but it's buffer size - and it's the same on the receiving end -
the buffer size is only 2048 bytes.
So we're going to have to send this data in three different batches, or let me check -
it's going to be even more than three batches.
Let's run it and see what happens.
I'm going to press "start" and then I'm going to press "pause" right away to explain what's going on. All
right,
So the sender puts 2048 bytes to buffer.
Now, sender's buffer is full. And we want to transmit that data to the receiving end.
Let's resume.
OK,
at this point, the receiving end has received all of this data.
So now the receiver buffer is full. Sender's buffer is also still full, and it's going to remain full
until it receives the acknowledgement that the receiver has received the sent data.
Also notice that the application hasn't read any data yet -
it's still in the buffer.
That's because the application reads the data in certain periods of time - so it's not always reading
the data.
It's not just flowing through the buffer - application is going to read the data from the buffer
only when the buffer is full, and when the application actually wants to read that data.
Let's see what happens next.
We're going to resume and I'm going to hit "pause" right here.
All right.
So in this moment right here, the sender received an acknowledgement that says we received your 2048
2048 bytes.
You can send the next batch now.
Now, I'm not going to discuss these "windows" right now - it just means that the receiving
buffer is full and that it can't receive any more data.
So this pretty much means "don't send any bytes yet".
So, let's just look at this moment as we have received the acknowledgement.
So now that the sender has received this acknowledgement, they can now delete
the data that was in the buffer and they can transfer another batch of data from the application to
the sending buffer.
So that's now in the sending buffer,
let's look at what happened in the receiving buffer. The app consumed the 2048 bytes.
So the buffer is now empty.
And then the next message is going to - it's going to say - "OK send the next batch and this time, I
have this many bytes available", or "I have this much space available in my buffer"
Now what happens next is we're going to transfer the next batch of data to the receiver, and then it's
buffer is going to become full again, and then the application is going to take over that data.
Let me pause here to see what's going on.
OK.
Application consumed the next batch.
So it's almost getting full. Now the sender is going to send the next batch - and this is actually
an important moment.
So the application tries to consume the data.
Excuse me.
So the application tried to consume the data, but there was not enough data to consume.
This means that you can't consume data from the buffer if the buffer is not full.
Same goes for the sender side.
So you can't really send the data if the buffer is not full yet, and there's still some data left in
the application.
So we're going to have to wait for the buffer to get fulfilled - or to to have the maximum number of bytes
that it's supposed to have for it to be able to send the data over to the other side of the application.
That's actually why we use this "true" argument here.
So because we would normally have to wait for the buffer to be filled in order for the data to be sent
to the client, we're going to say "true", which pretty much turns on the option of flushing.
So this says to the to the socket: doesn't matter if the buffer is full or not, flush the message - just
send it out.
And if you've ever used Wireshark, you can just capture some data and you're going to find some
TCP packets that have PSH flag on - that's the "push" flag.
It's the same thing as "auto flush"
Now the reason why we have to use "flush" in our program is because our messages are quite short. The server
sends a short "Welcome!", and then the client sends "Thanks!", or what was it -
- yeah it was "Thanks!"
So we would never fill out a buffer here.
So anytime we use out_socket, it's automatically going to turn on this push flag, and this packet - or this
message - is going to be pushed out of the buffer,
even though the buffer isn't filled. In the next part of our program, we are just following the protocol.
So "connect" - let's say this is the client -
so the client initiates the connection, and then the server sends "Welcome!" to the client using out_socket.
And then the client is supposed to say "Thanks!"
Back to the server.
So when we look at the server this is where we received this message.
So here we receive - I'm going to add a comment here -
Receive -
receive "Thanks!" - in this case we know in advance what is it that the client is going to say.
So, we're going to display what the client said. And then after that, we closed the socket ,both on the
server and on the client.
That's this last step.
Disconnect.
All right.
Let's look at the client then.
The only difference between the client and the server, apart from some small differences in the protocol
part, is the socket. So, for the server - we had the server socket, and the regular socket. For the client,
we only created the regular socket because that's all we need on the client side.
Here we are connecting to the server.
We gave the address of the server - and we can use 127.0.0.1 here too,
that's going to work also. And we need to make sure that we have typed in the right port number.
Let's run the application again to check if it works.
So we're going to run the server first. And then I'm going to run the client. And we saw that that works.
So this isn't much fun, since everything happens on its own. In the next lesson I'm going to add a prompt
for the client to type in its response to the server.
So it's going to be more interesting than this. But for now, let's just go through the code on the client
side.
So we've already talked about the socket.
This is pretty simple - hopefully it's clear enough.
And then this is just the protocol part, where we are talking with the server. We're first receiving
the message from the server that says "Welcome", and then we print that out in the console, and then in
the end, we are sending "Thanks" back to the server. After the communication is all finished,
we can close the socket.
That would be it for this exercise.
If there's anything else that maybe is still unclear, send me a message or post your question in the
Q&A area, and then in the next lesson we're going to make this simple program just a bit more complex.
It's not going to be much,
don't worry, but hopefully it will at least be more fun.