Welcome. Now that you've learned about MapReduce programming on distributed computers, let's learn about something a little more general, but also a lower level. And this is referred to as client-server programming. So as we've seen we're now talking about distributed computers where you may have computer zero, computer one, and so on. All connected to some network. And for a Java environment, all code runs in a Java Virtual Machine. Each Java Virtual Machine is a separate process, so we can have JVM A over here, JVM B over here, and it's actually perfectly fine, and often happens in practice, to have multiple JVMs on a computer. Now, with MapReduce, we learned about a framework that runs above all this entire system stack, and gives you a very high level abstraction of distributed memory parallelism. Now, we are going to ask the question, how can we get some communication going on between two particular JVMs? And the approach that has been around in low level systems software for a long time is to use what's called a socket. And the socket is the endpoint, so let's say that JVM A is going to play a client role, so we're going to have a client socket over here, and JVM B will play a server role, so it will have a server socket. So what exactly needs to happen for these two JVMs to talk to each other? Well, fortunately, Java has some very convenient APIs to abstract a lot of the low level system behavior. So what the server will need to do is create a new ServerSocket. So it could say bSocket = new ServerSocket(), And this is initializing this end point here on JVM B by one of the threads that executes this code. Then after that, it turns out that under the covers, this socket operation does a number of things. It does a create, bind, port. And in fact, usually you specify a port parameter when you create a server socket, which says that this process is opening up this port as a socket for communication with potential clients. So these things in the comments are what you would normally do if you were a lower level systems programmer, at say the UNIX level. In Java you just initialize the socket. Then what you have to do is you have to listen and wait to see if any clients want to connect to the socket that you've opened up. It's kind of like advertising your telephone number and waiting to see if anyone will call. And the way you wait for a call is that you say, let's say A = bSocket.accept(). So you're ready to accept communications on the socket. And this, A, will be a reference essentially to the client socket from JVM A, we'll see what needs to be done on the client side to make this work. But the convenient thing is once you've set this up this way, you have two convenient helpers. You have A.getInputStream(), And A.getOutputStream(). And essentially the server can use the input stream for reads. And the output stream for writes. Just like you use input and output streams for file I/O in Java or any other form of communication. So once you're at this level, you can then communicate at the socket level through input and output streams. Now, what does the client need to do? The client needs to somehow connect with the server. So if we go to this end and see what happens on the client side. You will have aSocket is a new Socket. So earlier we had ServerSocket for the server, for the client it's just a regular Socket. And then again you can have, aSocket.getInputStream() and aSocket.getOutputStream(). And again, JVM A can use the input stream for reads and the output stream for writes. So that's the basic machinery. Once you've got this all set up, there is a slight asymmetry in the initialization. But once it's set up, JVM A and B can talk to each other almost in a symmetric way because they can use the input and output streams of the sockets to do reads and writes. Now what happens is those reads and writes actually get translated to messages that flow across the network. Here I've just shown the connection logically. But what actually happens physically is that the message has to go through the network from the client to the server and the server to the client. And once you have this capability, you can use parallelism, of course, because you can be doing different computations in the client and the server JVMs. And you can be using multiple JVMs. What MapReduce accomplishes is very conveniently uses large numbers of nodes, thousands even, whereas here with sockets, it's a little lower level. So it's probably more appropriate to program at this level, when you are managing a smaller number of nodes. So with this, you're on the first step to true distributed programming. And you can try out examples that run on multiple JVMs, whether they're on the same computer or on different computers.