FAQ 5d
Updated: 1/19/00
>>>>>> How do I use a Socket Server?
====================================
Socket Servers communicate with their clients using the Berkely Sockets API.
These API's have been adopted by both WinSock (Windows Sockets) and Java.
I'm going to tell you in a general way how sockets work. I will describe it from
the Server viewpoint, then the Client viewpoint. Then I'll show you sample code
that does exactly what I described. Here we go:
The Client will create a Socket object. It then contacts the Server. Once contact
is established, the Socket can recieve messages from the Server and send messages
to the Server. Once the Client is finished (or the connection is somehow broken),
the Socket is disconnected and discarded.
The Server will create a ServerSocket object. The ServerSocket will wait for a Client
to contact the Server. It then creates a Socket object that is connected to the Client.
This Socket is placed into a list of Sockets. Each Socket object will listen for
messages from its Client, and can be used to send messages to its Client. Once the
Client is finished (or the connection is somehow broken), this Socket is disconnected
and discarded.
:::::: Client Code Example
==========================
Look at this file for an example Client: ClientSession.java
When you create a ClientSession object you need to know the host and port.
host is a string indicating the network address of the Socket Server. This can
be either a domain name (like "www.fiends.com") or an IP address (like "209.125.223.66").
Applets in a browser can get their server name with this line:
server = getCodeBase().getHost();
port is an integer value in the range 0 to 65535. Numbers below 1024 are reserved for
use by the system. The Socket Server will be listening for messages sent to a specific
port number. You have to pick this number when designing your applications (it can
be any number you like).
The ClientSession class extends Thread (see FAQ_3f). This lets the class continuously check
for incoming messages. This has to be done since reading messages blocks processing.
This means that the program will get stuck at the read() function until a message
arrives from the Server.
Notice that I send an integer 'size' value before each message. When you are reading messages
from a DataInputStream, it looks like one giant string. You need some way to break this string
into useful messages. I like to insert 'size' values so that I can read my messages based on
their length.
:::::: Server Code Example
==========================
Look at this file for an example Server: ServerSession.java
This example has two classes, NetServer and ServerSession. NetServer is a thread that
waits for any Client contact. ServerSession holds a Socket object and is in contact with
one specific Client.
When you create a NetServer object you need to know the port.
port is an integer value in the range 0 to 65535. Numbers below 1024 are reserved for
use by the system. The Socket Server will be listening for messages sent to a specific
port number. You have to pick this number when designing your applications (it can be
any number you like).
The NetServer class extends Thread (see FAQ_3f). This lets it continuously check for new
Client connections. This has to be done since the accept() function will block
processing. This means that the program will get stuck here until a Client connects.
Notice that the ServerSession class extends Thread (see FAQ_3f). It does this for the exact
same reasons that ClientSession extends Thread (see above).
You will see that I put all newly created sessions into a list (Vector). You HAVE TO DO THIS.
In JAVA, objects only exist as long as some other object holds its pointer. If I did not put
the sessions into the list, then they would vanish just as soon as the 'session' variable was
given a new value.
:::::: Message Handling
=======================
Your Session thread will read a message then respond to it (the respond() function).
Since the Session thread operates independently from your operations thread,
responding directly inside the Session thread might cause problems.
If your Session thread happens to edit a variable just as the operations thread is
updating the game, then the operations thread might become confused. This can ruin
your game state.
To avoid this I recommend that you save your Session messages into a list.
Then the operations thread can read the messages from the list.
This way it can respond to the messages itself, preventing problems.
Here is an example of saving the messages into a list (Vector):
// changes to 'ClientSession' or 'ServerSession'
class Session extends Thread {
void respond(byte buf[]){
GameApplet.pushMessageQueue(buf);
}
}
// 'GameApplet' code file
import java.applet.*;
import java.util.*;
class GameApplet extends Thread {
static Vector messageQueue;
GameApplet(){
messageList = new Vector();
}
public void run(){
while (true){
updateGameState();
repaint();
handleMessageQueue();
}
}
static pushMessageQueue(byte buf[]){messageQueue.addElement(buf);}
void handleMessageQueue(){
byte[] buf;
while (true) {
buf = messageQueue.firstElement();
if (buf==null) break;
messageQueue.removeElement(buf);
handleMessage(buf);
}
}
}
:::::: static value
===================
static will share a value with all instances of the class. That means you can
access that shared function or variable using the class name.
In our example, we access the pushMessageQueue() function using the class name GameApplet.
This can only work because the messageQueue is also static (shared by the entire class).