Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                

Chapter 2: Java Sockets

Download as pdf or txt
Download as pdf or txt
You are on page 1of 5

page 1

Chapter 2: Java Sockets


As we saw in the last chapter Socket is an abstraction of an IP Port. Sockets are a concept that have been around
in programming languages for some time. They first appeared in early Unix systems in the 1970s and are now
the `standard' low-level communication primitive.
Actually, there are two kinds of sockets - connection-oriented sockets, almost always based on TCP, and
connectionless sockets, usually based on User Datagram Protocol (UDP). The difference is that TCP-type
sockets guarantee data arrives, and in the correct order whereas UDP-type ones do not.
In addition to distinguishing TCP-type and UDP-type sockets, we must also distinguish between client and
server sockets. It might seem obvious that `server sockets run on servers and client sockets on clients' but this is
not always true - servers can be both servers and clients, and clients can legitimately run server sockets. The real
difference is that server sockets wait for connections to be initiated by client sockets - the naming is not
particularly helpful.

2.1. Client Sockets


We will start off by looking at how we can create a simple client socket. The class that handles client sockets is
Socket in the java.net package. You would generally include the line:
import java.net.Socket;
in your code to allow you to use the `short' names of the package methods. A complete description of the
methods and constructors of Socket can be found here. As you can see, there are a range of constructors that
can be used to create a new [client] socket - the one we would use would depend on our particular circumstances.
Probably the simplest from our point of view is
Socket(String host, int port)
which simply connects to port on machine host. For example,
Socket s = new Socket("this.doesnt.exist.com", 1024);
Note that you must not use socket number less than 1024! These are reserved for well-known services (though if
you are actually trying to connect to a specific well-known service, then you should of course use the appropriate
port number). On systems with a well-developed notion of access priviledges (e.g. linux), `ordinary' users - i.e.
you - cannot create server sockets (below) on well-known ports (you need superuser - e.g. `root' - access).
How do you read and write from/to the socket? You simply use standard Java I/O methods. For example, the
following code opens a socket and attaches appropriate input/output streams to it:

import java.net.*;
import java.io.*;
Socket s = new Socket("this.doesnt.exist.com", 1024);
BufferedReader in = new BufferedReader(
new InputStreamReader(s.getInputStream()));
PrintStream out =
new PrintStream(s.getOutputStream());
(Lines of codes between the import statement and the Socket declaration have been omitted.) The key bits
are (a) s = new Socket(...), which creates a new socket s; (b) s.getInputStream(), which returns
the input stream associated with the socket s; and (c) s.getOutputStream() which returns the
corresponding output stream. The other stuff (BufferedReader, InputStreamReader,
PrintStream) are exactly the same methods we would use if we wished to do line-based IO from files: they
`wrap' the raw input stream with something more convenient and useable. So we can make socket-based
communication look more-or-less exactly like file-based communication.
We can now (say) input (inString = in.readLine();) and output (out.println("blah");) lines
of text in the normal way. (It has to be said though, that Java's stream-based IO, though very flexible and
capable, is not the easiest thing to come to grips with initially.)

2.2. Server Sockets


A key question you should have after the previous section is: who is listening to the other end of our socket? The
answer is, unless we set up a server socket or are connecting to a pre-existing service, nobody, and the
page 2

connection attempt will typically fail. (We must be a little careful when trying to open sockets because if there is
nobody listening, an exception can be thrown. We are generally required to catch and deal with such exceptions -
not catching most exceptions causes compilation errors). We will look at exceptions some more in the example.)
The class that deals with server sockets is (no suprise) ServerSocket and you can find API details here. The
principal constructor from our point of view is
ServerSocket(int port)
which creates a server socket that listens on the specified port. To set up a server socket and establish a
connection, we might use code like this:

import java.net.*;
SeverSocket sSoc = new ServerSocket(1024);
Socket in = sSoc.accept();
We create a new server socket called sSoc and then call its accept() method. At this point, our code will
wait until some other process (probably on another machine, though this does not have to be the case) connects
to the server socket, by automatically creating a new client socket. The accept method blocks - that is, any
program invoking it will wait until a communication attempt occurs (there are other versions of accept that
specify a maximum waiting time). Once again, we should not use socket numbers less than 1024 because they
are reserved for well-known services.
2.2.1. Simple Example
Here is an example program that repeatedly waits until it is contacted on its server socket. When it is, it starts up
a new thread that outputs the first 100 Fibonacci Numbers (if you don't know what a Fibonacci number is, it
doesn't particularly matter).

import java.net.*;
import java.io.*;
import java.lang.*;
public class Fib1 {
public static void main(String argv[]) {
try {
ServerSocket sSoc = new ServerSocket(2001);
while(true) {
Socket inSoc = sSoc.accept();
FibThread FibT = new FibThread(inSoc);
FibT.start();}
}
catch (Exception e) {
System.out.println("Oh Dear! " +
e.toString());
}
}
}
class FibThread extends Thread {
Socket threadSoc;
int F1 = 1;
int F2 = 1;
FibThread(Socket inSoc) {
threadSoc = inSoc;
}
public void run() {
try {
page 3

PrintStream FibOut = new


PrintStream(threadSoc.getOutputStream());
for (int i=0; i < 100; i++) {
int temp;
temp = F1;
FibOut.println(F1);
F1 = F2;
F2 = temp + F2;
}
}
catch (Exception e) {
System.out.println("Whoops! " +
e.toString());
}
try {
threadSoc.close();
}
catch (Exception e) {
System.out.println("Oh no! " +
e.toString());
}
}
}
There are a few things to note about this code.
· The chosen socket number. We have picked socket 2001 at random - in principle, it doesn't matter which
socket we pick, provided it is greater than 1023. Of course, there is a possibility that 2001 is in use already
- sockets above 1023 are free for anyone to use.
· Exceptions We have used try - catch to make sure that any exceptions that are thrown are caught and
handled. In this case, `handling' is a little basic - we have just specified that any exception should result in
a message being printed, which will include the actual details of the exception. In practice, we might want
several different catch clauses to deal with the various different classes of exception that can be thrown.
For example, both the server socket constructor ServerSocket and the method accept can throw
exceptions of class IOException, which is a sub-class of Exception, and which in turn has its own
subclasses - the most likely of which in this case is SocketException (which itself has subclasses).
Different sub-classes of exception may need to be handled in different ways - for example, it is likely that
not all exceptions will be terminal, and the catch clause can attempt some form of recovery. This might
happen if socket 2001 is in use - we could try another one and continue. However, this is too subtle for us
at the moment.
· Threads. When Fib1 actually receives some input, and hence creates a new socket, it passes off all
further work to a new thread called FibThread. It is FibThread that outputs the Fibonacci numbers.
Notice that FibThread also has to deal with exceptions - both getOutputStream (which returns an
output stream connected to a socket) and close (which closes a socket connection) can throw an
IOException. Why have we used a separate thread? Because our server then goes back to listening for
more communication attempts, and it is perfectly possible for it to serve multiple clients at once. That is
because each client will be served by a separate thread and socket, and multple sockets are able to `share' a
port.
page 4

2.3. Client-Side Code


Now we have seen what a server must do to communicate over sockets, we need to look at the client-side code.
The following is a simple client for Fib1.

import java.net.*;
import java.io.*;
public class FibReader {
Socket appSoc;
BufferedReader in;
String message;
public static void main(String argv[]) {
try {
appSoc = new Socket("wherever",2001);
in = new BufferedReader(new
InputStreamReader(appSoc.getInputStream()));
for (int i = 0; i < 100; i++) {
message = in.readLine();
System.out.println(message); }
}
catch (Exception e) {
System.out.println("Died... " +
e.toString());
}
}
This code creates a socket connection to some host (on which an appropriate server must be running), and sets
up an input stream connected to that socket. It then prints out 100 lines read from the server, using the socket.
Again, this is not particularly good code - for example, we are relying on the server to send us 100 lines: what if
it doesn't? However, it does illustrate the point.

2.4. Exercise
Here is a simple exercise you can try to illustrate that socket communication can work. The java source code file
for the server is here. Download it to your machine. Then download the client. You will probably have to edit
the hostname in the client code - try localhost to start with. Start up the server in a terminal, or command,
window. You might want to do this as a background task - on windows type:
start java Fib1
and on linux:
java Fib1 &
Then type:
java FibReader
and you should see Fibonacci numbers appearing (they will start to go horribly wrong eventually because of
arithmetic overflow).
For experiment two, try doing the same as above, but this time open an extra terminal/command window and
start the client up in both - you should see the Fibonacci numbers appearing more-or-less simultaneously on
both.
The next experiment to try is to use two machines. Start the server up on one, and the client on the other -
remember to edit the hostname in the client (or change the code so it takes command line parameters allowing
you to specify a host. How exactly you manage to use two machines will depend on your circumstances - if you
have access to the Linux laboratory, it is very straightforward. It might also be easy if you have access to
multiple machines at home (though beware that a firewall will almost certainly stop it working - in this case you
can either disable the firewall or open up the port you are using - the safest option).
If you have internet access from outside the university, try the experiment again. ( However, you will need to
start up the server process remotely, e.g. via TelNet, or better ssh - don't start it up in the lab and then go home!
page 5

This may seem obvious but it's been done.) You may find that it doesn't work this time. (I would like to say that
this is a consistent security policy by the University to protect machines from suspicious communication
attempts - but it appears to be purely random.)

You might also like