ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Introduction to the Peer-to-Peer Sockets Project

by Brad Neuberg
12/03/2003

The Peer-to-Peer (P2P) Sockets Project reimplements Java's standard Socket, ServerSocket, and InetAddress classes to work on the JXTA peer-to-peer network, rather than on the standard TCP/IP network. It also includes ports of many popular web packages, such as the Jetty web server, the Apache XML-RPC client and server libraries, and the Apache Jasper JSP engine, to run on the Peer-to-Peer Sockets framework.

By the end of this article, you will understand the motivation and need for the P2P Sockets package, how to configure and set up the P2P Socket libraries to run on your system, how to create and run P2P server and client sockets, how to work with the P2P InetAddress class, and security issues and limitations in the framework.

Motivation

The P2P Sockets project is designed for developers interested in:

The P2P Sockets project reimplements the standard java.net classes on top of the JXTA peer-to-peer network. "Aren't standard TCP/IP sockets and server sockets already peer-to-peer?" some might ask. Standard TCP/IP sockets and server sockets are theoretically peer-to-peer, but in practice are not, due to firewalls, NAT devices, and political and technical issues with the Domain Name System (DNS). First, many of the peers on the Internet are given dynamic IP addresses through DHCP, shared or filtered IP addresses through NAT devices, or IP addresses that are very hard to reach due to firewalls. Creating server sockets under such conditions is either impossible or requires elaborate application-level ruses to bypass these restrictions. Second, TCP/IP sockets and server sockets depend on DNS to resolve human-friendly host names into IP addresses. DNS is theoretically semi-decentralized, but on an administrative level, it is centralized under ICANN, an unresponsive, non-innovative organization. Further, standard DNS does not deal well with edge-peers that have filtered or dynamic IP addresses, and updates take too long to propagate and have no presence information. Developers who wish to create exciting, new applications that extend DNS into new directions, such as storing instant messaging usernames and presence info in DNS, are stymied by the DNS system's technical and political issues.

Related Reading

JXTA in a Nutshell
By Scott Oaks, Bernard Traversat, Li Gong

JXTA is an open source project that creates a peer-to-peer overlay network that sits on top of TCP/IP. Every peer on the network is given an IP-address-like number, even if they are behind a firewall or don't have a stable IP address. Super-peers on the JXTA network run application-level routers that store special information such as how to reach peers, how to join subgroups of peers, and what content peers are making available. JXTA application-level relays can proxy requests between peers that would not normally be able to communicate due to firewalls or NAT devices. Peers organize themselves into peer groups, which scope all search requests and act as natural security containers. Any peer can publish and create a peer group in a decentralized way, and other peers can search for and discover these peer groups using other super-peers. Peers communicate using pipes, which are very similar to Unix pipes. Pipes abstract the exact way in which two peers communicate, allowing peers to communicate using other peers as intermediaries if they normally would not be able to communicate due to network partitioning.

JXTA is an extremely powerful framework. However, it is not an easy framework to learn, and porting existing software to work on JXTA is not for the faint-of-heart. P2P Sockets effectively hides JXTA by creating a thin illusion that the peer-to-peer network is actually a standard TCP/IP network. If peers wish to become servers, they simply create a P2P server socket with the domain name they want, and the port other peers should use to contact them. P2P clients open socket connections to hosts that are running services on given ports. Hosts can be resolved either by domain name, such as www.nike.laborpolicy, or by IP address, such as 44.22.33.22. Behind the scenes, these resolve to JXTA primitives, rather than being resolved through DNS or TCP/IP. For example, the host name www.nike.laborpolicy is actually the NAME field of a JXTA peer group advertisement. P2P sockets and server sockets work exactly the same as normal TCP/IP sockets and server sockets. For the technically inclined and those who already understand JXTA, a table exists illustrating how standard TCP/IP concepts such as host name, IP address, etc., map to their JXTA equivalents.

The benefits of taking this approach are many-fold. First, programmers can easily leverage their knowledge of standard TCP/IP sockets and server sockets to work on the JXTA peer-to-peer network without having to learn about JXTA. Second, all of the P2P Sockets code subclasses standard java.net objects, such as java.net.Socket, so existing network applications can quickly be ported to work on a peer-to-peer network. The P2P Sockets project already includes a large amount of software ported to use the peer-to-peer network, including a web server (Jetty) that can receive requests and serve content over the peer-to-peer network; a servlet and JSP engine (Jetty and Jasper) that allows existing servlets and JSPs to serve P2P clients; an XML-RPC client and server (Apache XML-RPC) for accessing and exposing P2P XML-RPC endpoints; an HTTP/1.1 client (Apache Commons HTTP-Client) that can access P2P web servers; a gateway (Smart Cache) to make it possible for existing browsers to access P2P web sites; and a WikiWiki (JSPWiki) that can be used to host WikiWikis on your local machine that other peers can access and edit through the P2P network. Even better, all of this software works and looks exactly as it did before being ported. The P2P Sockets abstraction is so strong that porting each of these pieces of software took as little as 30 minutes to several hours. Everything included in the P2P sockets project is open source, mostly under BSD-type licenses, and cross-platform due, to being written in Java.

Because P2P Sockets are based on JXTA, they can easily do things that ordinary server sockets and sockets can't handle. First, creating server sockets that can fail-over and scale is easy with P2P Sockets. Many different peers can start server sockets for the same host name and port, such as www.nike.laborpolicy on port 80. When a client opens a P2P socket to www.nike.laborpolicy on port 80, it will randomly connect to one of the machines that is hosting this port. All of these server peers might be hosting the same web site, for example, making it very easy to partition client requests across different server peers or to recover from losing one server peer. This is analogous to DNS round-robin, where one host name will resolve to many different IP addresses to help with load balancing. Second, since P2P Sockets don't use the DNS system, host names can be whatever you wish. You can create your own fanciful endings, such as www.boobah.cat or www.cynthia.goddess, or application-specific host names, such as Brad GNUberg or Fidget666 for an instant messaging system. Third, the service ports for a given host name can be distributed across many different peers around the world. For example, imagine that you have a virtual host for www.nike.laborpolicy. One peer could be hosting port 80 to serve web pages, another could be hosting port 2000 for instant messaging, and a final peer could be hosting port 3000 for peers to subscribe to real-time RSS updates. Hosts now become decentralized coalitions of peers working together to serve requests.

Requirements and Configuration

You must download and install the following software to develop and work with P2P Sockets.

Install and configure the JDK and Ant, and make sure both are in your path so they can be run from the command line. Unzip P2PSockets-1.0-beta1.zip into the top level of your hard drive; spaces are not allowed in the directory names, or the P2P Sockets build files will not work correctly.

You must also add the JAR file p2psockets/lib/ant-contrib-0.5.jar to your CLASSPATH. On Windows this would look as follows:

set  CLASSPATH=%CLASSPATH%;c:\p2psockets\lib\ant-contrib-0.5.jar

The P2P Sockets directory already includes two different directories, test/clientpeer and test/serverpeer, that have JXTA configuration information already set up (these are in the hidden directories test/clientpeer/.JXTA and test/serverpeer/.JXTA, respectively). If you want to learn more about how to configure JXTA, read about the JXTA Configurator. The two test peers have already been configured for the worst possible case, which is that you are behind a firewall and a NAT device, which means you must use other intermediate peers to relay your requests; this configuration will work even if you are not under these conditions. One of the nice aspects about JXTA, though, is that this will all be transparent and hidden from you as you program and use the system.

When testing the examples in this tutorial, you must be connected to the Internet. This is for two reasons: first, the examples use a public JXTA server Sun has set up that helps bootstrap peers into the JXTA network; and second, on some operating systems (such as on Windows XP, by default), the network subsystem shuts down if you are not connected to the Internet, preventing client peers and server peers that are running on the same machine from finding or communicating with each other.

Creating a P2P Server Socket

Creating a P2P server socket is exactly like creating a normal java.net.ServerSocket:

// start a server socket for the domain
// "www.nike.laborpolicy" on port 100
java.net.ServerSocket server = new java.net.P2PServerSocket("www.nike.laborpolicy", 100);

This creates a server socket listening for client requests for the host www.nike.laborpolicy on port 100. Under the covers, the P2PServerSocket code searches for a JXTA peer group named www.nike.laborpolicy. If it finds this peer group, it joins it; otherwise it creates the group by publishing a JXTA peer group advertisement with the Name field formatted in a certain way. This field always has the format hostname/IP address, such as www.nike.laborpolicy/44.33.67.22. We search based on host name or IP address on JXTA rendezvous servers by using wildcards, such as www.nike.laborpolicy/* to search without having to know the IP address, or */44.33.67.22 to search for a peer group with the given IP address. Once we have found or created the host's peer group, we then publish a JXTA pipe advertisement into this peer group with the Name field set to the port number, such as <Name>80</Name>.

After creating this server socket, you can now treat it as an ordinary java.net.ServerSocket in your code, waiting for a client and then retrieving an InputStream and OutputStream from the client and communicating normally:

java.net.Socket client = server.accept();

// now communicate with this client
java.io.DataInputStream in = new DataInputStream(client.getInputStream());
java.io.DataOutputStream out = new DataOutputStream(client.getOutputStream());
out.writeUTF("Hello client world!");
String results = in.readUTF();
System.out.println(results);

Even though the client looks like a normal java.net.Socket, it is actually connecting and communicating to your server through the JXTA peer-to-peer network, with requests and responses possibly being relayed by other peers to traverse NATs and network partitions. All of this is hidden from you, however.

Unlike normal server sockets, we need to initialize and sign into our peer-to-peer network. Before you can create the P2PServerSocket, therefore, you must sign in:

// sign into the peer-to-peer network, using
// the username "serverpeer", the password "serverpeerpassword",
// and create/find a scoped peer-to-peer network named "TestNetwork"
java.net.P2PNetwork.signin("serverpeer", "serverpeerpassword", "TestNetwork");

The first two arguments are the username and password to use when signing into the peer-to-peer network. These are entered by the user when they set up the JXTA Configurator, a Swing dialog (which will pop up the first time you run the JXTA platform) that allows you to configure your peer on the P2P network. In your own application, you might retrieve these values either from the command line or from a GUI. The P2P Sockets package comes preconfigured with two JXTA peers already configured, located in test/clientpeer and test/serverpeer.

The final value (TestNetwork, in the above example) is a unique name that will be given to your peer-to-peer network. Different peer-to-peer networks that are based on P2P Sockets can co-exist without knowing about each other. Clients and servers create and resolve their server sockets and sockets in a specific peer-to-peer network. The final value is the name of your own private, application-specific peer-to-peer network. If you create your server socket in the peer-to-peer network named TestNetwork while a client signs into another peer-to-peer network named InstantMessagingNetwork, then they will not be able to find each other or communicate.

Here's what is happening inside of the P2PNetwork class. When you call the signin method, the network string (TestNetwork in the example given) is hashed into an MD5 peer group ID; this ensures that the application name given is globally unique if we search based on the peer group ID. We then search for this peer group, and create it if it does not exist. All domain name and IP address resolutions now occur inside of this application peer group. Note that the default JXTA configuration files included in test/clientpeer and test/serverpeer are configured to use the Sun rendezvous servers to bootstrap into the JXTA network; you should configure your own peers differently if you want them to use a different beginning rendezvous server when bootstrapping into the JXTA Net Peer Group.

As a programmer, you will choose your network name to go along with the name of your application or the type of network you are creating, such as MyApplicationsNetwork or AcmeCompanyInformationNetwork.

The final code looks as follows:

import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.net.Socket;
import java.net.ServerSocket;
import java.net.P2PServerSocket;
import java.net.P2PNetwork;

public class ExampleServerSocket {
   public static void main(String args[]) {
      try {
         // sign into the peer-to-peer network, 
         // using the username "serverpeer", the password "serverpeerpassword",
         // and create/find a scoped peer-to-peer network named "TestNetwork"
         System.out.println("Signing into the P2P network...");
         P2PNetwork.signin("serverpeer", "serverpeerpassword", "TestNetwork");

         // start a server socket for the domain
         // "www.nike.laborpolicy" on port 100
         System.out.println("Creating server socket for " + "www.nike.laborpolicy:100...");
         ServerSocket server = new P2PServerSocket("www.nike.laborpolicy", 100);
         
         // wait for a client
         System.out.println("Waiting for client...");
         Socket client = server.accept();
         System.out.println("Client Accepted.");
		 
         // now communicate with this client
         DataInputStream in = new DataInputStream(client.getInputStream());
         DataOutputStream out = new DataOutputStream(client.getOutputStream());
         out.writeUTF("Hello client world!");
         String results = in.readUTF();
         System.out.println("Message from client: " + results);
		 
         // shut everything down!
         client.close();
         server.close();
      }

      catch (Exception e) {
         e.printStackTrace();
         System.exit(1);
      }
   }
}

Using a P2P Socket to Contact a P2P Server Socket

Creating and using a P2P socket to connect to the P2P server socket you created is just as easy.

First, sign into your peer-to-peer network:

// sign into the peer-to-peer network,
// using the username "clientpeer", the password "clientpeerpassword",
// and find the peer-to-peer network named "TestNetwork"
java.net.P2PNetwork.signin("clientpeer", "clientpeerpassword", "TestNetwork");

Next, create your P2P socket with the host name and port to which you want to connect:

// create a socket to connect to the domain
// "www.nike.laborpolicy" on port 100
java.net.Socket socket = new java.net.P2PSocket("www.nike.laborpolicy", 100);

Now you can easily communicate with the server:

// now communicate with this server
java.io.DataInputStream in = new DataInputStream(socket.getInputStream());
java.io.DataOutputStream out = new DataOutputStream(socket.getOutputStream());
String results = in.readUTF();
System.out.println(results);
out.writeUTF("Hello server world!");

Here is the full source code for the client:

import java.io.InputStream;
import java.io.OutputStream;
import java.io.DataOutputStream;
import java.io.DataInputStream;
import java.net.Socket;
import java.net.P2PSocket;
import java.net.P2PNetwork;

public class ExampleClientSocket {
   public static void main(String args[]) {
      try {
         // sign into the peer-to-peer network,
         // using the username "clientpeer", the password "clientpeerpassword",
         // and find a network named "TestNetwork"
         System.out.println("Signing into the P2P network..");
         P2PNetwork.signin("clientpeer", "clientpeerpassword", "TestNetwork");
		
         // create a socket to connect to the
         // domain "www.nike.laborpolicy" on port 100
         System.out.println("Connecting to server socket! at " + "www.nike.laborpolicy:100...");
         Socket socket = new P2PSocket("www.nike.laborpolicy", 100);
		 System.out.println("Connected.");
		 
         // now communicate with this server
         DataInputStream in = new DataInputStream(socket.getInputStream());
         DataOutputStream out = new DataOutputStream(socket.getOutputStream());
         String results = in.readUTF();
         System.out.println("Message from server: " + results);
         out.writeUTF("Hello server world!");
		 
         // shut everything down
         socket.close();
      }
	  
      catch (Exception e) {
         e.printStackTrace();
         System.exit(1);
      }
   }
}

Running the P2P Server Socket and Socket Examples

Open two separate shell windows. In both windows, type the following to set some system variables needed by the Ant makefiles (modify the variables below to point to where you have installed P2P Sockets and your JDK, respectively):

set p2psockets_home=c:\p2psockets
set JAVA_HOME=c:\j2sdk1.4.1

In both windows, change to the p2psockets directory where you installed the P2P Sockets package. The example source files are in p2psockets/src/examples and are named ExampleClientSocket.java and ExampleServerSocket.java, respectively. Everything has already been compiled into the p2psockets/dist/examples directory, but if you want to compile them again, enter the following in one of the two shells:

ant clobber build jar

To run the client and server, in one window enter the following to start the example P2P server socket:

ant example-serversocket-run

You should see the following printed in the server peer window:

Buildfile: build.xml
example-serversocket-run:
Signing into the P2P network...
Using the application peer group name TestNetwork
Waiting for RendezVous Connection.........
Finished connecting to RendezVous.
Creating server socket for
www.nike.laborpolicy:100...
Waiting for client...

In the other window, enter the following to start the example P2P client socket:

ant example-clientsocket-run

You should see the following printed in the client peer window:

Buildfile: build.xml
example-clientsocket-run:
Signing into the P2P network..
Using the application peer group name TestNetwork
Waiting for RendezVous Connection......
Finished connecting to RendezVous.
Connecting to server socket at
www.nike.laborpolicy:100...
Connected.
Message from server: Hello client world!

In the server window, you should now see the following:

Buildfile: build.xml
example-serversocket-run:
Signing into the P2P network...
Using the application peer group name TestNetwork
Waiting for RendezVous Connection.........
Finished connecting to RendezVous.
Creating server socket for
www.nike.laborpolicy:100...
Waiting for client...
Client Accepted.
Message from client: Hello server world!

Congratulations! You've just created a simple peer-to-peer network with very little work or extra knowledge needed above understanding basic Java sockets and server sockets.

If you have trouble, ensure that you are connected to the Internet, that you are running Java 1.4+, and that you did not install the P2P Sockets package into a directory with spaces in its name (such as in Program Files/p2psockets). You might also have to delve into the JXTA Configurator if your machine is in an unusual network configuration. If you are attempting to run the client and server P2P socket examples across separate machines that are blocked by NAT devices, you might run into an open bug that P2P Sockets currently has.

Finding Out if a Host Name, IP Address, or Port is Taken

By default, the P2PInetAddress and P2PServerSocket classes don't check to make sure a given host name, IP address, or port is taken. The reason for this is so that application developers can use P2P server sockets in unique ways that are not possible with normal sockets. For example, different peers can start up P2P server sockets for the same host name and port in order to provide failover and scalability. If we were to throw an exception when a server socket was started because the address was already bound, then this possibility would be precluded.

However, methods are provided to check if a given host name, IP address, or port is already taken. These are static methods on the class java.net.P2PNameService. Examples are shown below:

boolean nameAvailable = P2PNameService.isHostNameTaken("www.nike.laborpolicy");
boolean portAvailable = P2PNameService.isPortTaken("www.nike.laborpolicy", 80);
boolean addressAvailable = P2PNameService.isIPAddressTaken("33.44.74.12");

Use these before creating your P2P server sockets if you want your server to be a unique instance (read the Limitations and Security Concerns section, though, on P2P domain-name spoofing).

You have now learned the bulk of working with P2P sockets and server sockets. The following sections detail technical information on how the P2P Sockets package provides full compatibility with standard TCP/IP sockets and server sockets. You don't necessarily need to know this information, but should know it if you will be working with the InetAddress class or the Loopback (127.0.0.1) or Any (0.0.0.0) addresses. If you skip these sections, please make sure to read the section Limitations and Security Concerns at the end of this article.

Working with the P2P InetAddress Class

The P2P Sockets package includes an implementation of java.net.InetAddress that works the same as InetAddress and that subclasses it.

The following examples show different ways in which to create a P2PInetAddress object.

// Create an InetAddress where we know the host
// name but not the IP address.
// This will not search the network to find the
// corresponding IP address.
InetAddress inetAddr = P2PInetAddress.getByAddress("www.nike.laborpolicy", null);

// Create an InetAddress where we know the IP
// address but not the host name.
// This will not search the network to find the
// corresponding host name.
InetAddress inetAddr = P2PInetAddress.getByAddress("55.32.77.34", null);

// Create an InetAddress where we know both the
// IP address and the host name.
// No searching will occur on the network
byte ipAddress[] = new byte[4];
ipAddress[0] = 55;
ipAddress[1] = 32;
ipAddress[2] = 77;
ipAddress[3] = 34;
InetAddress inetAddr = P2PInetAddress.getByAddress("www.nike.laborpolicy", ipAddress);

// Create an InetAddress object using the hostname. 
// The network will be searched for the corresponding IP address
InetAddress inetAddr = P2PInetAddress.getByName("www.boobah.cat");

// Create an InetAddress object using the hostname.  
// The network will be searched for the corresponding IP address
InetAddress inetAddress[] = P2PInetAddress.getAllByName("www.boobah.cat");

// Create an InetAddress object using the IP address.  
// The network will be searched for the corresponding host name 
byte ipAddress[] = new byte[4];
ipAddress[0] = 55;
ipAddress[1] = 32;
ipAddress[2] = 77;
ipAddress[3] = 34;
InetAddress inetAddr = P2PInetAddress.getByAddress(ipAddress);

// Get the host name and IP address for the local host
InetAddress inetAddr = P2PInetAddress.getLocalHost();

Once you have an P2PInetAddress object, you can treat it like a normal InetAddress object:

InetAddress inetAddr = P2PInetAddress.getByName("www.boobah.cat");
String hostName = inetAddr.getHostName();
String ipAddressString = inetAddr.getHostAddress();
byte ipAddress[] = inetAddr.getAddress();
boolean isLocalhost = inetAddr.isLoopbackAddress();

P2P server sockets have an interesting problem that standard server sockets do not have. Because the P2P Sockets system implements its own simple DNS system, we need a way to create an InetAddress for a host name that does not exist yet; we explicitly don't want to search the network to resolve a given host name into an IP address, or vice versa, because neither of them exist yet. We would then use this InetAddress object to instantiate a P2PServerSocket to bind a new host name and IP address. P2P Sockets currently overloads the standard getByAddress(String host, byte address[]) method to avoid resolving information that is not given. This is the recommended method to use to generate a P2PInetAddress object, since contacting the network to resolve the extra information is wasteful and not really needed. To check to see if a given host name or IP address is already taken, use the methods on the P2PNameService class, which were detailed in the section above:

InetAddress objects can be used to start P2P sockets or server sockets, just as in standard Java sockets and server sockets:

InetAddress inetAddr = P2PInetAddress.getByAddress("www.boobah.cat", null);
ServerSocket server = new P2PServerSocket(inetAddr, 80);
.......
.......
.......
InetAddress inetAddr = P2PInetAddress.getByAddress("www.boobah.cat", null);
Socket server = new P2PSocket(inetAddr, 80);

The Loopback (127.0.0.1) and Any (0.0.0.0) Addresses

The P2P Sockets package provides a simple implementation of the Any IP address, which is 0.0.0.0. In normal TCP/IP parlance for server sockets, the Any interface says "start a server socket with any of the available IP addresses available on this machine; I don't know which it is and don't care." In the context of P2P Server Sockets, hosts can not be "multi-homed;" i.e., have more than one IP address. Instead, they are given an automatic host name that is autogenerated from their JXTA peer names. For example, if a peer is named BradGNUberg, then the peer would be given the automatic host name www.BradGNUberg.peer. By default, the prefix is "www." and the suffix is ".peer". These values are currently not customizable, but future versions will expose this capability.

We use this automatic peer name to resolve the Any or Loopback address:

// In the following example, assume the peer's
// local JXTA name is BradGNUberg.
InetAddress inetAddr = P2PInetAddress.getLocalHost();
ServerSocket socket = new P2PServerSocket(inetAddr, 100);
// returns "www.BradGNUberg.peer"
String hostName = socket.getHostName();

Before this will work, however, you must start up a P2PServerSocket for the localhost on a given port.

P2P server sockets also provides support for another feature for compatibility with normal server sockets, though it will probably only rarely be used. Normal TCP/IP server sockets can be started with the any interface and no port specified. This would start the server socket on a random "private" port about 1024. The P2PServerSocket class supports the same thing; if you start it with no host name or the Any interface and no port, a random port number will be generated above 1024 but less than 65536. You could then retrieve this port number from the server socket and send it to client sockets over another channel to inform them of a private, random port that is available.

Limitations and Security Concerns

The P2P Sockets project currently has the following limitations and security issues:

License Information

P2P Sockets, including the source code in this article, is under the Sun Project JXTA Software License.

Resources

Questions? Comments?

See the P2P Sockets Homepage or contact Brad Neuberg at . Feel free to call him at 1 (510) 938-3263 (Pacific Standard Time, San Francisco) Monday through Friday, not including weekends. Also see his weblog, www.codinginparadise.org, for Mozilla, Java, JXTA, and P2P news.

Brad Neuberg has done extensive work in the open source community, contributing code to Mozilla, JXTA, the Jakarta Feed Parser, and more.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.