VidWorks Entertainment New Developer Training Program

Tutorial #8

Networking

 

            Now, before we get started talking about networks, I'd just like to briefly mention that there are generally two transport protocols that are in use today. (For those who are already familiar with networks, you can go ahead and skip to the actual tutorial sections.) There's TCP (Transmission Control Protocol) and UDP (User Datagram Protocol, also sometimes jokingly called Unreliable Datagram Protocol). Today, we'll show you how to make server and client pairs that use TCP and UDP.

            In general, TCP is more reliable--it guarantees that all packets you send out arrive at the destination, and that they arrive in the correct order. However, because of these guarantees (and what TCP has to do in order to honor these guarantees), TCP has decreased performance. TCP is usually used when the content of every packet is important, like in chat programs, web browsing, or file transfers. UDP, on the other hand, sends its packets willy-nilly and doesn't care whether or not they arrive or if they get thrown out of order. UDP, however, while unreliable, generally has better performance and is usually used in applications where performance is more important than the data, such is in streaming video or audio or in real-time online games.

            You might have heard of IP (Internet Protocol). This is a network protocol. Both TCP and UDP run on top of this (hence the popular term, TCP/IP). I'd go into more detail, but that's outside the scope of this tutorial.

 

 

TCP Connections - Sockets and ServerSockets

 

            In Java, all TCP connections are handled with objects called Sockets. When two computers are connected, they each have their own Socket, and these two Sockets are exclusively connected to each other. If one of the computers wants to connect to another computer, it will need to open a new Socket.

            In order to send messages down a Socket, you'll need its OutputStream (usually wrapped in a PrintWriter). You can use this code to get the PrintWriter for a Socket:

 

new PrintWriter(socket.getOutputStream(), true)

 

where the true is for auto-flushing the buffer on a print() or println().

            In order to receive messages from a Socket, you'll need its InputStream (usually wrapped in an InputStreamReader wrapped in a BufferedReader). You can use this code to get the BufferedReader for a Socket:

 

new BufferedReader(new InputStreamReader(socket.getInputStream())

 

            Now, one method of creating a Socket is to use it's constructor:

 

Socket(string host, int port)

 

            Unfortunately, this only works when the host is already listening for a connection request. If you want your machine to listen for connection requests (we'll call this machine a server, because that generally describes a server), we'll need to use a special kind of socket, called a ServerSocket.

            Here's the ServerSocket constructor that we're interested in:

 

ServerSocket(int port)

 

            Then, in order to listen for a connection, you can use this ServerSocket member method:

 

Socket accept()

 

            accept() listens for an incoming connection request, such as the one generated when you call the Socket constructor. It then creates a new Socket, connects the new Socket to the one generating the request over the network, and returns the Socket. This is what allows us to connect Sockets together.

            Also, after accepting a connection, the ServerSocket is still available to accept connections, so you only need one ServerSocket to handle as many clients as you like. (Note that in our example, we still only accept one connection.)

            Anyways, you now know how to create Sockets, connect them together, and communicate with them. It's time for our example:

 

/* TCPServer.java */

 

import java.io.*;

import java.net.*;

 

public class TCPServer {

      public static void main(String[] args) {

            // set default server port

            int serverPort = 13000;

 

            // if specified, get server port from command-line

            if (args.length >= 1) {

                  try {

                        int temp = Integer.parseInt(args[0]);

                        serverPort = temp;

                  } catch (NumberFormatException e) {

                        System.out.println("Invalid port number!");

                        System.out.println("Setting port number to default 13000.");

                  }

            }

 

            try {

                  // open a socket and wait for a connection

                  ServerSocket server = new ServerSocket(serverPort);

                  Socket connection = server.accept();

                  System.out.println("Client connected.");

 

                  // create readers and writers

                  BufferedReader netin =

                        new BufferedReader(new InputStreamReader(connection.getInputStream()));

                  PrintWriter netout =

                        new PrintWriter(connection.getOutputStream(), true);

 

                  // read from the socket and echo back to the client

                  String str = null;

                  while ((str = netin.readLine()) != null) {

                        System.out.println("Received: " + str);

                        str = "You said, \"" + str + "\"";

                        System.out.println("Sending: " + str);

                        netout.println(str);

                  }

 

                  // this means the client closed the connection; quit

                  System.out.println("Client has disconnected.");

                  System.out.println("Shutting down server.");

                  netin.close();

                  netout.close();

                  connection.close();

                  server.close();

                  System.out.println("Goodbye!");

            } catch (Exception e) {

                  System.out.println(e.getMessage());

            }

      }

}

 

/* TCPClient.java */

 

import java.io.*;

import java.net.*;

 

public class TCPClient {

      public static void main(String[] args) {

            // set default hostname and portnum

            String host = "localhost";

            int port = 13000;

 

            // if specified, get hostname and portnum from command-line

            if (args.length >= 2) {

                  host = args[0];

 

                  try {

                        int temp = Integer.parseInt(args[1]);

                        port = temp;

                  } catch (NumberFormatException e) {

                        System.out.println("Invalid port number!");

                        System.out.println("Setting port number to default 13000.");

                  }

            }

 

            try {

                  // connect to the server

                  Socket socket = new Socket(host, port);

                  System.out.println("Connected to server.");

 

                  // create readers and writers

                  BufferedReader netin =

                        new BufferedReader(new InputStreamReader(socket.getInputStream()));

                  PrintWriter netout =

                        new PrintWriter(socket.getOutputStream(), true);

                  BufferedReader keyin =

                        new BufferedReader(new InputStreamReader(System.in));

 

                  String str = null;

                  while (true) {

                        // read from the keyboard

                        System.out.flush();

                        if ((str = keyin.readLine()).toLowerCase().equals("quit")) {

                              break;

                        }

 

                        // send keyboard input to server

                        netout.println(str);

 

                        // read from server

                        System.out.println("Received: " + netin.readLine());

                  }

 

                  // disconnect from server

                  System.out.println("Disconnecting from server.");

                  netin.close();

                  netout.close();

                  socket.close();

                  keyin.close();

                  System.out.println("Goodbye!");

            } catch (Exception e) {

                  System.out.println(e.getMessage());

            }

      }

}

 

            Note, run the server before you run the client. You can run these two on the same machine by specifying the server as "localhost" on the client. Also, note that you have command-line options:

 

java TCPServer [port num]

java TCPClient [host name] [port num]

 

            In this example, the client connects to the server. Then, the user types stuff in, which the client sends to the server, and the server echoes it back. The user can continue this as many times as he or she likes, until the user types "quit". Then, the client will disconnect and the server will shut down.

            Anyways, here's some sample output:

 

Server output:

>java TCPServer

Client connected.

Received: This is a test.

Sending: You said, "This is a test."

Received: Hurray! It works!

Sending: You said, "Hurray! It works!"

Received: Now, I'm going to log out.

Sending: You said, "Now, I'm going to log out."

Client has disconnected.

Shutting down server.

Goodbye!

 

>_

 

Client output:

>java TCPClient

Connected to server.

This is a test.

Received: You said, "This is a test."

Hurray! It works!

Received: You said, "Hurray! It works!"

Now, I'm going to log out.

Received: You said, "Now, I'm going to log out."

quit

Disconnecting from server.

Goodbye!

 

>_

 

 

UDP Connections - DatagramSockets and DatagramPackets

 

            UDP Connections follow a similar paradigm. Like TCP, they use sockets to communicate to each other, except in Java's UDP implementation, they are called DatagramSockets. Unlike TCP, no connection is actually made between the two communicating machines. Each machine only needs one DatagramSocket that can send messages to any other machine and can receive messages from any other machine.

            "How does the DatagramSocket know where to send the data to?" you might ask. Well, that's because the DatagramPacket, the unit of information in UDP, contains the address and port of the sender and recipient, so in a way, the Datagrams route themselves.

            Anyways, here's how to construct a DatagramPacket:

 

DatagramPacket(byte[] buf, int length) - this method is useful for creating black DatagramPackets to receive data with

DatagramPacket(byte[] buf, int length, InetAddress address, int port) - use this constructor for when you want to send a Datagram to the given address and port

 

            Also, DatagramPacket has these useful member methods:

 

InetAddress getAddress() - if the Datagram is being sent, it returns the recipient; if it's being received, it returns the sender; in short, it always returns the other machine

int getPort() - like getAddress(), this method always returns the port number of the other machine

byte[] getData()

int getOffset()

int getLength()

 

            The last three methods are quite useful for converting a byte array to a string, because strings have the following constructor:

 

String(byte[] data, int offset, int length)

 

            And a string can be converted back into a byte array using the String member method:

 

byte[] getBytes()

 

            Another thing you might be wondering about are those InetAddresses. These are basically a Java representation of Internet addresses. You can use the static InetAddress method:

 

static InetAddress getByName(String host)

 

where the string "host" is either the IP address of the host or the domain name (such as java.sun.com).

            Enough about DatagramPackets. Let's talk about DatagramSockets. There's no point in having packets if you don't have a socket to send them on.

            DatagramSockets have the following constructors:

 

DatagramSocket() - opens a DatagramSocket on any available port

DatagramSocket(int port) - a better idea, since it's generally a good idea to have a well-known port number so that people can connect to you

 

            DatagramSockets also have these useful member methods:

 

void send(DatagramPacket p) - sends a DatagramPacket p

void receive(DatagramPacket p) - receives a datagram and stores it in the blank DatagramPacket p

void close() - closes the DatagramSocket

 

            Anyways, you know now enough about Java UDP programming to understand an example:

 

/* UDPServer.java */

 

import java.io.*;

import java.net.*;

 

public class UDPServer {

      public static void main(String[] args) {

            // set default server port

            int serverPort = 13010;

 

            // if specified, get server port from command-line

            if (args.length >= 1) {

                  try {

                        int temp = Integer.parseInt(args[0]);

                        serverPort = temp;

                  } catch (NumberFormatException e) {

                        System.out.println("Invalid port number!");

                        System.out.println("Setting port number to default 13010.");

                  }

            }

 

            try {

                  // open a datagram socket

                  DatagramSocket socket = new DatagramSocket(serverPort);

                  System.out.println("Server started, waiting for message.");

 

                  // create a buffer to receive data

                  byte[] buffer = new byte[256];

                  DatagramPacket packet = new DatagramPacket(buffer, buffer.length);

 

                  String str = null;

                  InetAddress addy = null;

                  int port = 0;

                  byte[] output = null;

                  while (true) {

                        // receive the packet

                        socket.receive(packet);

                        str = new String(packet.getData(),

                                    packet.getOffset(),

                                    packet.getLength());

                        System.out.println("Received: " + str);

 

                        // echo back to sender

                        str = "You said, \"" + str + "\"";

                        System.out.println("Sending: " + str);

                        output = str.getBytes();

                        addy = packet.getAddress();

                        port = packet.getPort();

                        packet = new DatagramPacket(output,

                                          output.length,

                                          addy,

                                          port);

                        socket.send(packet);

                  }

            } catch (Exception e) {

                  System.out.println(e.getMessage());

            }

      }

}

 

/* UDPClient.java */

 

import java.io.*;

import java.net.*;

 

public class UDPClient {

      public static void main(String[] args) {

            // set default hostname and portnum

            String host = "localhost";

            int serverPort = 13010;

            int myPort = 13020;

 

            // if specified, get hostname and portnum from command-line

            if (args.length >= 3) {

                  host = args[0];

 

                  try {

                        int temp = Integer.parseInt(args[1]);

                        serverPort = temp;

                  } catch (NumberFormatException e) {

                        System.out.println("Invalid server port number!");

                        System.out.println("Setting server port number to default 13010.");

                  }

 

                  try {

                        int temp = Integer.parseInt(args[2]);

                        myPort = temp;

                  } catch (NumberFormatException e) {

                        System.out.println("Invalid client port number!");

                        System.out.println("Setting client port number to default 13020.");

                  }

            }

 

            try {

                  // get the InetAddress of the server

                  InetAddress serverAddy = InetAddress.getByName(host);

 

                  // open a datagram socket

                  DatagramSocket socket = new DatagramSocket(myPort);

 

                  // create a buffer to receive data

                  byte[] buffer = new byte[256];

 

                  // create standard input reader

                  BufferedReader keyin =

                        new BufferedReader(new InputStreamReader(System.in));

 

                  String str = null;

                  byte[] output = null;

                  DatagramPacket packet = null;

                  while (true) {

                        // read from the keyboard

                        System.out.flush();

                        if ((str = keyin.readLine()).toLowerCase().equals("quit")) {

                              break;

                        }

 

                        // send keyboard input to server

                        output = str.getBytes();

                        packet = new DatagramPacket(output,

                                          output.length,

                                          serverAddy,

                                          serverPort);

                        socket.send(packet);

 

                        // receive from server

                        packet = new DatagramPacket(buffer, buffer.length);

                        socket.receive(packet);

                        str = new String(packet.getData(),

                                    packet.getOffset(),

                                    packet.getLength());

                        System.out.println("Received: " + str);

                  }

 

                  // quit (no need to disconnect)

                  socket.close();

                  keyin.close();

                  System.out.println("Goodbye!");

            } catch (Exception e) {

                  System.out.println(e.getMessage());

            }

      }

}

 

            This actually has the same behavior as the TCP example with a few differences. First of all, since the server has no concept of connections, any number of clients may send to the server and receive the echo back. Second of all, since the server has no concept of connections, it doesn't know if the clients are disconnected or if they're all just silent. Consequently, this server never shuts down (at least until the user hits Ctrl-C). Third of all, because this is UDP, you'll see packets getting dropped sometimes (depending on network activity and congestion).

            Also, the command-line options are slightly different:

 

java UDPServer [port num]

java UDPClient [server name] [server port num] [client port num]

 

            Finally, some sample output:

Server output:

>java UDPServer

Server started, waiting for message.

Received: Hi, I'm Client #1.

Sending: You said, "Hi, I'm Client #1."

Received:

Sending: You said, ""

Received: Wow, my pack

Sending: You said, "Wow, my pack"

Received: This time, a partial pac

Sending: You said, "This time, a partial pac"

Received: Haha, Client #2's packets are gettin

Sending: You said, "Haha, Client #2's packets are gettin"

Received: D'oh! Mine are getting dropped, too.

Sending: You said, "D'oh! Mine are getting dropped, too."

Received: Now who's la

Sending: You said, "Now who's la"

^C

 

>_

 

Client #1 output:

>java UDPClient

Hi, I'm Client #1.

Received: You said, "Hi, I'm Client #1."

Haha, Client #2's packets are getting dropped!

Received: You said, "Haha, Client #2's packets are gettin"

D'oh! Mine are getting dropped, too.

Received: You said, "D'oh! Mine are getting dropped, too."

quit

Goodbye!

 

>_

 

Client #2 output:

>java UDPClient localhost 13010 13030

Hi, I'm Client #2.

Received: You said, ""

Wow, my packet got dropped!

Received: You said, "Wow, my pack"

This time, a partial packet got dropped.

Received: You said, "This time, a partial pac"

Now who's laughing?

Received: You said, "Now who's la"

quit

Goodbye!

 

>_

 

            Remember how I had said before that in UDP, each machine only needs on socket. This is actually very powerful, because as you add machines, the number of sockets you need grows linearly (one for each machine). On the other hand, with TCP, as you add machines, the number of sockets grows quadratically (each machine has a socket for every other machine). This means that UDP scales a lot better for networks of interconnected machines.

            The alternative solution for TCP is (and this is the tradition TCP solution) to have one server that all machines connects to and communicate with, so we still have a linear number of sockets. However, the problem with this solution is that if the server goes down, your whole network is unusable. With UDP, you can have decentralized networks. Even if machines go down, if it's small compared to the entire network, you're network is still functional. No single machine is critical to the performance of the network. (Of course, you'll still need to know a machine or two to bootstrap the process and get yourself into the network.)

            However, the scalability and non-centrality of UDP comes at a cost: UDP is, once again, unreliable, and machines running on UDP have no way of telling if a machine is still on the network or not other than by listening to it and assuming that silence means inactivity. Thus, when writing a UDP network, you have to build your own error checking (assuming it's important to you).

 

 

Conclusion

 

            Anyways, enough chitchat about TCP and UDP and networking with Java. I've taught you all you need to know to do basic networking over Java.

            Next time: Reflection!

            For those of you who don't already know, Reflection is a way to get a class at run-time. This is a way to make programs extensible or to create user specified modules (like AI or plug-ins) without having to re-compile your program every single time. It's also the last tutorial of the series. (Awww...)

 

 

Homework Assignment!

 

            Write your own online chat program. (Actually, this is more of a project than a homework assignment, but it's something that everyone does when they first learn how to write networking software. Either that or P2P file-sharing.) Bonus points if it uses UI and has sound effects (like AIM).