|
Comments
Did you read today's front page stories & breaking news?
SYS-CON.TV
|
General Java JT Router: Let Your Clients Tunnel Their Way Across The Internet
JT Router: Let Your Clients Tunnel Their Way Across The Internet
By: Ajit Sagar
Oct. 1, 1997 12:00 AM
Consider an Internet client that wants to connect to a site which allows access only to trusted clients. Consider a trusted client that has access to the site. Wouldn't it be great if the trusted client could relay the Internet client's data to the restricted-access site? In other words, it could act as a "channel", or a "router", for a restricted site. This article describes JTRouter - a multi-threaded Java program that acts as a tunnel for socket communication between an Internet client and a remote server. JTRouter allows a machine to initiate as well as accept Internet connections in independent threads. It is a unique implementation in that both the client and the server roles are implemented in the same process. JDJ has published several articles that provide an excellent introduction to client/server programming in Java using sockets and threads and there are several texts that cover these topics in detail. This article won't focus on re-introducing these concepts. I assume here that you are familiar with basic networking and threading in Java. A table of the main Java API classes used in this program is shown in Table 1. In subsequent sections, we will examine the JTRouter program which uses these classes to develop a practical application. Later, we will see how to run the program and what kind of applications it may be used in. Before diving into the nuts and bolts of JTRouter, I would like to mention how much easier it has been to develop this program in Java than in a language like C or C++. The entire JTRouter program consists of less than 350 lines of code! Some of its original error-checking capabilities and functionality were stripped down to a version that could fit in this article, but even the full-fledged program has less than 500 lines of code. I shudder to think of how many painful hours of debugging I would have spent if I had to develop JTRouter in a language that did not have the rich support for threading and networking that Java does.
Program Description The "server" does little more than just play the role of a traditional server in a client/server paradigm. It also initiates connections to the customer site. In that sense, it is playing the role of a client. To avoid confusion, let us define the entities in this network. The Internet client is referred to as the Client. The intermediate server is the JTRouter and the customer is the Remote Host. Thus, JTRouter is a server for the Client but acts as a client for Remote host. The relationship between the three entities is illustrated in Figure 1. JTRouter has a ServerSocket that listens for connection requests. Client 1 and Client 2 make connection requests to JTRouter. JTRouter provides a Java utility for "piping" the output of one socket to the input of the other socket and vice versa. This is called a JTunnel. The JTRouter ServerSocket accepts the connections and creates JTunnels for routing information to and from the Remote Host. JTRouter is implemented as a single process that listens for Internet connection requests from clients and, upon receiving one, opens a socket connection with the Remote Host. It then routes the data from the client to the Remote Host and back from the Remote Host to the client. At the same time, it continues to listen for other Internet requests. JTRouter is also responsible for detecting lost or disconnected channels and cleaning up after them The code will break out of the wait if 100 ms expire or if it receives a notify() from the JTListener thread. In either case, the run() method goes on to call processQueueConnections() which opens a new JTunnel for any queued requests. This is followed by a call to cleanJTunnels(), which removes any JTunnels that are no longer active. The control then returns to the top of the while loop. processQueuedConnections() copies the elements of jtPending Vector into newJTunnels. It does this in a block that is synchronized on jtPending (which is shared between the JTServer and JTListener threads). Synchronizing on jtPending ensures that the JTListener thread will not alter the contents of jtPending while the JTServer thread is using it. JTListener also synchronizes on jtPending when accessing it as shown in Listing 4: synchronized(jts.jtPending) { jts.jtPending.addElement(clientSocket); } The use of jtPending is further explained in the next section. The last step in the synchronized block is to remove the entries from jtPending Vector. For each socket in newJTunnels, a connection to the Remote Host is opened: Socket remoteHostSocket = connectToRemoteHost(); The method connectToRemoteHost() (which is the next method in Listing 3) obtains and returns a socket for the Remote Host: sock = new Socket(remoteHostAddr, remoteHostPort); Now that both the Client and Remote Host sockets are available, a new JTunnel is instantiated and opened. This is added to the JTunnels Vector and the number of activeConections is incremented. The last method in Listing 3, cleanJTunnels(), checks the JTunnels Vector to see if any JTunnels are inactive (by calling the method isActive()). If so, it removes the JTunnel entry from the vector.
JTListener
The constructor for JTListener takes a JTServer argument, in_jts. It copies this to a local variable, jts. Then it creates a new ServerSocket on jts.clientPort to listen for connection requests from the Client. JTListener implements the Runnable interface (recall that it is started as a thread by JTServer). The run() method listens for connection requests in a while loop:
In order to use the program, you will need the Client and the Remote Host. However, there is an easier way to test the program on a single machine. The source for a tester program, called JTunnelTester, is available on SYS-CON Interactive/JDJ On-line. The files you will need to run this program are: JTunnelTester will simulate a Remote Host. The simplest way to simulate the Client is to use the telnet program. Windows NT comes with a telnet daemon. The steps to run the program are available on SYS-CON Interactive/JDJ On-line. The screen shots are given in Figures 3-6. Figure 3 shows the output of the JTServer program. Figure 4 shows the output of the JTunnelTester program. Figures 5 and 6 show the data sent from a telnet window to the JTunnelTester window. The top of the split window in Figure 5 shows a line of input from the user. This line is displayed in Figure 6 (the telnet window). The bottom of the split window in Figure 5 shows a line of text received by JTunnelTester from the telnet window associated with Figure 6.
Conclusion
Some applications that JTRouter can be used in are: JTRouter defines four Java classes to achieve its purpose. These are PipedSocketStream, JTunnel. JTServer, and JTListener. Detailed descriptions of these classes and the JTRouter program follow.
PipedSocketStream The Socket class in Java uses two methods, getInputStream() and getOutputStream(), that provide the basic objects for stream-based socket communications. The Socket reads from one stream (e.g., InputStream returned by getInputStream()) and writes out via the other stream (e.g., OutputStream returned by getOutputStream()). The PipedSocketStream class combines the functionality of the "piped-stream" classes and the Socket class. Listing 1 shows PipedSocketStream. The constructor takes two Socket arguments. It obtains an input stream from s1 and an output stream from s2 as shown in the code snippet below:
dis = new DataInputStream(s1.getInputStream()); Notice that PipedSocketStream implements the Runnable interface. So it can start as an independent thread that relays data in one direction. The run() method in Listing 1 has a simple while loop that calls readAndWrite(). The method readAndWrite() reads a character from the Socket s1's input stream (dis) and writes it out to Socket s2's output stream. readAndWrite() returns a true if it successfully completes both operations and a false if it fails. On getting a false, the run() method returns; this ends the life of the PipedSocketStream Thread. Another way in which the thread could break out of the loop is if dis.read(), dos.write() or dos.flush() return an IOException. Once a PipedSocketStream thread is started, it relays data from s1 to s2. It dies only if it loses the connection (or the other side disconnects) or it gets an Exception due to a failure in a system call.
JTunnel JTunnel has three methods. jtopen() creates the PipedSocketStream threads:
clientServerPipe = new Thread(new PipedSocketStream(cSocket, sSocket)); The isActive() method returns a boolean value that is true if both the threads are alive and false if either is dead. It does this by calling the Thread isAlive() method. jTclose() makes sure that both threads are stopped and the associated sockets are closed. JTunnel uses two one-way pipes to simulate a full duplex connection. That is why when one thread dies, it has to kill the other and cleans up by closing the associated sockets. A one-way socket connection does not make any sense in this scenario.
JTServer Let us look at the main() routine. When JTServer is started by the Java interpreter, it uses the System DataInputStream to get three values from the user - in_clientPort, in_remoteHostPort and in_remoteHostAddr. These are passed to the JTServer constructor for creating the JTServer object. JTServer implements the Runnable interface and is started as a thread. This gives the JTServer program the flexibility to spawn multiple JTServer threads. Thus, you could have several JTServers, each acting as a router and all this in a single process! In fact, that is the way JTServer has been implemented in one application. The constructor for the JTServer takes three arguments - in_clientPort (the socket for Internet Client requests), in_remoteHostPort (the socket port on which JTServer sends out data to the Remote Host) and in_remoteHostAddr (the Internet address of the Remote Host). These are copied to the local variables clientPort, remoteHostPort and remoteHostAddr respectively. Let us now look at the run() method for JTServer. The first thing the run() method does is to create and start a JTListener thread. JTListener listens for connections on the Client port. Note that JTServer passes itself to JTListener as the "this" argument. The boolean jtChanged is set to false, indicating that no JTunnel has recently changed state. The run() method now waits for 100 ms. Note that the JTServer object waits on itself (it calls this.wait()). That is why the run() method is synchronized.
while (true) { Reader Feedback: Page 1 of 1
Latest Cloud Developer Stories
Subscribe to the World's Most Powerful Newsletters
Subscribe to Our Rss Feeds & Get Your SYS-CON News Live!
|
SYS-CON Featured Whitepapers
Most Read This Week
Breaking Cloud Computing News
|
|||||||||||||||||||||||||||||||||||||||||||||||||