Java Sockets

  

So far, you have seen how networks work; in this section you learn about the interface that programs use to communicate with the network. The operating system uses network drivers to communicate with the Network Interface Card (NIC), and the device driver may provide other Dynamic Link Libraries, or native libraries, for other applications and libraries to communicate to the operating system. These native libraries act as wrappers around the device driver to provide an interface that abstracts the low-level details of developing the TCP/IP and UDP/IP packets. The native libraries themselves can take time to implement a protocol like FTP and Telnet to production. The quickness from design to production is one of the reasons Java has become so popular.

Java Sockets were also developed to implement protocols like FTP in a quick and non-complicated manner. One of the features that I liked about Java Sockets when they were first introduced was their capability to use a buffered stream that would pass a stream of information through a socket instead of breaking up pieces into individual records. Other enterprise products, such as Enterprise JavaBeans (EJB) and CORBA, have far surpassed the Socket programming interface by providing additional features and services out of the box. However, for simple tasks and implementing enterprise products, knowing how Sockets work is crucial. Figure 21-19 shows Sockets in the OSI model.

click to expand
Figure 21-19: The Sockets in the OSI model

The obvious way to identify a client or a server is by the IP address and port number; this address can be associated with a logical name identified by the host name. An example of a host name is security.richware.com . The java.net.InetAddress class is used to wrap, or encapsulate, the IP address, host name, and port number information. The InetAddress class does not contain a constructor but can be created by calling a static method on the InetAddress class itself. For example, the function InetAddress.getAllByName() returns an array of InetAddresses that contains all the IP addresses of the host (by its name) passed in as the parameter. Another example is the InetAddress.getLocalHost() , which returns the local host.

To create a connection between two network endpoints, an interface is established. The interface hides the complexity of the devices and the MAC frames and allows multiple protocols, such as IPX, to be used with just a change in the devices. In Java the interface is established with the java.net.Socket class for the client, and the java.net.ServerSocket for the server. An example of a client connecting to a server is included in Listing 21-7.

Listing 21-7: The Client_Socket class: An example of a client for Listing 21-8
start example
 package com.richware.chap21;     import java.io.*; import java.net.*; import java.util.*;     /**  * Class Client_Socket  * Description: An example client  *  * Copyright:    Copyright (c) 2002 Wiley Publishing, Inc.  * @author Rich Helton <rhelton@richware.com>  * @version 1.0    * DISCLAIMER: Please refer to the disclaimer at the beginning of this book.  */ public class Client_Socket{     public static void main(String args[]) {       try {         /*          * Create a socket on the local machine using port 9000          */         System.out.println("Starting Client_Server....");         Socket m_socket = new Socket(InetAddress.getLocalHost(), 9000);             /*          * Serialize the Credit Card Number, Example only, no security          */         OutputStream out = m_socket.getOutputStream();         ObjectOutput cc = new ObjectOutputStream(out);                 System.out.println("Sending Credit Card Number....");         cc.writeObject("My Credit Card Number");         cc.writeObject("4444-4444-4444-4444");         cc.flush();         cc.close();         /*          *  Catches          */       } catch (Exception e) {         e.printStackTrace();         System.exit(1);       }   } } 
end example
 

The connection is established by the creation of the Socket instance for a waiting ServerSocket . The accepting ServerSocket will return the Socket object of the client. Both the client and server can get the BufferedInputStream and BufferedOutputStream objects from the Socket object. The client output looks like the following:

 >java com.richware.chap21.Client_Socket Starting Client_Server.... Sending Credit Card Number.... 

and the matching server output is as follows :

 >java com.richware.chap21.Server_Socket Starting Server_Socket.... My Credit Card Number 4444-4444-4444-4444 

Using these InputStreams , the client and server can read and write data to each other. They can read files, mpegs, and other formats. The running application programs, which execute the Socket objects, are responsible for the formatting and unformatting of the data. The ServerSocket is initialized to listen on a specific port for the client connection. The accept() function waits until the client establishes a connection. Listing 21-8 shows the Server_Socket class that matches the Client_Socket class in Listing 21-7.

Listing 21-8: The Server_Socket class: An example of a server for Listing 21-7
start example
 package com.richware.chap21;     import java.io.*; import java.net.*; import java.util.*;     /**  * Class Server_Socket  * Description: An example server  *  * Copyright:    Copyright (c) 2002 Wiley Publishing, Inc.  * @author Rich Helton rhelton@richware.com  * @version 1.0    * DISCLAIMER: Please refer to the disclaimer at the beginning of this book.  */ public class Server_Socket {         /**      * The main driver, no args used.      */     public static void main(String args[]) {       System.out.println("Starting Server_Socket....");       ServerSocket  m_server = null;       Socket  m_socket = null;       try {       /*        * Listen to port 9000.          */             m_server = new ServerSocket(9000);       /*        * This will wait for a connection is made from the client.        * It returns the client socket.          */             m_socket = m_server.accept();       InputStream in = m_socket.getInputStream();       ObjectInput cc = new ObjectInputStream(in);       String m_header = (String) cc.readObject();       String m_number = (String) cc.readObject();       cc.close();              // print out what has been  just received       System.out.println(m_header);       System.out.println(m_number);       /*        * Catches        */       } catch (Exception e) {                e.printStackTrace();         System.exit(1);       }     } } 
end example
 

As with any typical client/server scenario, for everything to work there must be a matching server for each client. In addition, both client and server information, as well as data formats, need to match each other. However, besides the port and address, much of the connection work can be hidden. In the BSD sockets, a bind and listen function also has to be defined on the server side; the bind sets up the address, and the listen prepares the socket to listen so that the accept() function works appropriately. Much of this work is now hidden and buffered so that streaming can be implemented easily, as described in Figure 21-20.

click to expand
Figure 21-20: Streaming implementation

When a Socket is constructed , it installs a default SocketImpl . The default instance of the SocketImpl is created though the PlainSocketImpl :

 Socket(String host, int port) {                       ...               } 

If the default SocketImpl is not desired, an extended implementation of the SocketImpl can be passed in, as a parameter, to the constructor to be defined in the socket class:

 protected Socket(SocketImpl impl) {           this.impl = impl;         } 

It is a protected constructor because it does create the connection of the socket. The SocketImpl class is the protocol implementation of the Socket class. A SocketImpl can be passed in to support IPX; then the Socket class can be used, as previously discussed, except that instead of supporting TCP/IP, it supports IPX. The SocketImpl class is extremely helpful when extended to support transparent proxy connections like SOCKS. In addition, a SocksSocketImpl can be passed in the same way to support SOCKS, and change the Socket interface into a protocol to support the proxying of firewalls. The SocksSocketImpl class gives Java applications the capability to support most firewalls, satisfying the capability to implement the SOCKS protocol for firewalls.

Note  

The SocketImpl class only affects a subclass of the Socket class so that much of the original Socket function and functionality remains the same.

The ServerSocket must also provide support for the SocketImpl class. Functionality is needed for the support of the server accepting the client connection, which is implementing the SocketImpl class. In order to do this, the ServerSocket must create an instance of its own SocketImpl during the ServerSocket construction. Since most of the Socket functionality remains the same, only the accept() function needs overwriting. Sockets that don't implement the SocketImpl on the client need not implement the implAccept() on the server end, as shown in the following lines of code:

 public class ServerSocket {         ...         protected final void implAccept(Socket s) throws IOException {            ...            // on return from this call s will be connected to a client         }         ... 

Most of the discussion has been dedicated to connection-oriented Sockets , normally TCP/IP. Connection-oriented means that a client Socket is fully guaranteed a connection to the server Socket . When there is an interruption in the network connection, both client and server are aware of it. Recall that a TCP connection also guarantees delivery because there are flags in the packet to set an acknowledgement . Since the Socket interface supports the connection-oriented protocol, a separate interface needs to support the connectionless protocol.

The reason for a separate interface is that the means of connection needs to be kept separate; to keep them separate, the connectionless protocol (UDP) can be implemented with the java.net.DatagramSocket . The DatagramSocket is used for sending UDP packets. Each packet is individually addressed and routed, and can be received by a machine in any order. There are many similarities between a Socket and DatagramSocket . For instance, the connections of the server and client communicate on the same port, and if on a different machine, the address must be specified. If the port is in use by another application, another port has to be found in order to communicate. The following line establishes the port in the DatagramSocket :

 socket = new DatagramSocket(9000); 

However, there are many differences between the two protocols. The Socket class can support a stream because it can receive bytes in order, because it is connection-oriented and establishes a connection to send and receive on. The DatagramSocket can only receive or send packets from the class java.net.DatagramPacket and is bound to a port; the port is normally on the local machine and just receives packets. In the DatagramSocket scenario, there is no server listening for a connection like a ServerSocket . There are simply two DatagramSockets; one might use the send() function passing in the packet, and the other one might use a receive() function to receive the packet. A receiving packet may look like the following:

 byte[] buf = new byte[256]; DatagramPacket packet = new DatagramPacket(buf, buf.length); socket.receive(packet); 

In this example, the receiving socket, previously defined on port 9000, is receiving up to 256 bytes worth of data. It cannot receive more than that in the packet. The getLength() function of the DatagramPacket returns how much data was received. The packet is a class created and filled in by the receive method. In order to fill the data from the receive method, there must be a corresponding send method initiated as follows:

 InetAddress address = Socket.getLocalAddress(); int port = 9000; packet = new DatagramPacket(buf, buf.length, address, port); socket.send(packet); 

The difference between a send and receive is that send always is routed to the machine based solely on the port and address defined in the DatagramPacket . When the packet is received, the information can be retrieved with the following two lines:

 InetAddress address = packet.getAddress(); int port = packet.getPort(); 

The getAddress and getPort functions return the host that sent the message. The port and address are always associated with the remote host when dealing with the DatagramPacket . The remote host is the one to send to or to receive from. There are many issues to take into consideration when working with the DatagramSocket; the biggest one is that if two messages are sent one after the other, the receiver can receive the messages in any order. However, the DatagramSocket has many advantages for sending a broadcast to many machines or sending messages for which no guarantee is warranted; the reason is that the DatagramSocket requires no synchronizing between a client and server. It is simply an application sending messages and another receiving them. Examples of applications include a mail application, a stock ticker, and broadcasting log information on a system whose purpose is to apprise an administrator about the health of another system. DatagramSocket buys simplicity and speed in moving packets around.

Java SOCKS

One of the implementations needed to proxy through firewalls is SSL, and the SOCKS protocol needs to also proxy through firewalls. The Java Sockets support the v4 and v5 SOCKS for TCP proxy mechanisms. The system properties that set the SOCKS proxy in the Socket and ServerSocket are the socksProxyHost and the socksProxyPort . To specify the location of the proxy server, the socksProxyHost can be an IP address or host name. The socksProxyPort specifies the port number to establish the connection; the default port is 1080. SOCKS cannot proxy without valid authentication of the username. The SOCKS protocol needs authentication to tunnel and uses the Java network authentication to authenticate the user and password.

The java.net.Authenticator class is used to obtain authentication for a network connection. When authentication is required, the system will invoke the method on the subclass getPasswordAuthentication() . The support is completely for supporting client connections to a Java proxy server; there are several Java proxy servers on the market today. Since the definitions are at the Socket level, applications like FTP and Telnet (built on top of the Socket level) can take advantage of the SOCKS protocol by the setting of the system properties. When the properties are set, the factory is set from the SocketImpl; and it becomes a SocksSocketImpl used as a proxy connection.

Channel

The concept of a channel represents an open connection for hardware devices, files, socket connections, or program components that perform reading and writing. The channel is either open or closed; it is open on creation, and when it is closed, it remains closed. The isOpen() function checks whether the channel is open or closed.

There is little difference between a Socket and the blocking, or synchronous, SocketChannel , but the non-blocking , or asynchronous mode of SocketChannel has no equivalent in the Socket class. To implement the synchronous SocketChannel, use the following code:

 SocketChannel channel = SocketChannel.open(); channel.connect(socketAddress); 

The java.nio.channels.SocketChannel can be created by using the static SocketChannel.open() function. The open() function will subsequently create a corresponding Socket instance. The channel handles all the I/O operations; but the protocol-specific information, such as port numbers , host names , and the connection, is the responsibility of the Socket implementation. The SocketChannel , which makes up part of the Socket class, can be a returned Socket instance or the ServerSocket instance with the getChannel() method.

It is not possible to associate the channel to a Socket that already exists. If the Socket is not connected, invoking an I/O operation on the channel causes an exception because the Socket is not yet connected. The SocketChannel can invoke the connect() method, which establishes a client connection to the server. The finishConnect() method is used to finish the connection later. An isConnected() function can be called to determine if the client is already connected, and an isConnectionPending() is called if the connection operation is in progress. Unlike the other Socket classes, the SocketChannel requires multiple steps to get a connection to the server. The following lines show how to establish the asynchronous SocketChannel :

 String host = "www.richware.com";                InetSocketAddress socketAddress =                                new InetSocketAddress(host, 80);                channel = SocketChannel.open();                channel.configureBlocking(false);                channel.connect(socketAddress); 

The SocketChannel can be either asynchronous or synchronous. Passing in a false in the configureBlocking(false) function sets the SocketChannel to non-blocking. If the channel is non-blocking or asynchronous, it can continue operation without waiting for a response from the server. If it is blocking, or synchronous, it must wait for a response from the server before continuing. When a SocketChannel is in blocking mode, it can do a timeout on the read operations by calling a setSoTimeout(int) to specify the timeout period. It cannot do a timeout during non-blocking, or asynchronous mode, because the read is not waiting for a corresponding write operation.

Unlike the Socket, which can retrieve the InputStream and OutputStream at any time, the SocketChannel can only open and close once. The beginning of the SocketChannel is when the channel is open with input and output, and it is closed when the stream is closed; in contrast, the life cycle of Socket is usually open when the connection starts and ends when the connections ends. The SocketChannel shuts down the input side of the channel with the shutdownInput() function and the output side of the channel with the shutdownOutput() function. The SocketChannel supports every function asynchronously, even shutdown. An example of an asynchronous shutdown is a read (or write) that could be in progress during a shutdown, and once these functions have been notified of a shutdown in progress, they will stop reading (or writing) and return that there is nothing to read (or write).

Note  

SocketChannels are safe for use across multiple concurrent threads.

The SocketChannel is extended from the AsbstractSelectableChannel that gives the SocketChannel the capability to be a SelectableChannel . A SelectableChannel can register itself to a selector in the following way:

 Selector selector = Selector.open();                channel.register(selector, SelectionKey.OP_CONNECT); 

The SelectableChannel works with a Selector . The purpose of a Selector is to notify the channel when an event has occurred. The event is based on the SelectionKey . The SelectionKey defines the keys that have a selected interest. To find out the available keys, the SocketChannel can use the method validOps(), which returns the keys OP_READ , for reading data; OP_WRITE , for writing data; and OP_CONNECT , for a connection interest.

The isRegistered() function can be used to return true to check if the channel is registered, as the following line shows.

 channel.isRegistered(); 

After the selector is registered to the channel, the select() method of the selector is called to see if one of the specified operations has occurred. The select() methods are blocking, meaning that when called, the thread of execution will wait until there is a selection key available, a wakeup call from a another thread, or a timeout in the select(int timeout) method. The keys() or selectedKeys() functions will return a set of selection keys when they are available. Instead of comparing if the key is SelectionKey.OP_CONNECT , the isConnectable() function on the key will return if it is indeed a connection operation. Listing 21-9 shows the completion of a connection using the selection key of a SocketChannel .

Listing 21-9: SocketChannel connection completion
start example
 if (selector.select() > 0){       // If the selector has a registered key in queue       for (Iterator i = sel.selectedKeys().iterator(); i.hasNext();) {             //Retrieve the next key and remove it from the set             SelectionKey sk = (SelectionKey)i.next();             i.remove();             // Retrieve the target and the channel             Target t = (Target)sk.attachment();             SocketChannel sc = (SocketChannel)sk.channel();             // Attempt to complete the connection sequence if Connecting                  if(sk.isConnectable()){                 try {                     if (sc.finishConnect()) {                      sk.cancel();                      t.connectFinish = System.currentTimeMillis();                      sc.close();                      printer.add(t);                     }                 } catch (IOException x) {                     sc.close();                     t.failure = x;                     printer.add(t);                 }                }       } } 
end example
 

The New I/O (NIO) API performs asynchronous operations like never before. Each routine can be broken down, such as the connect() and finishconnect() , to fine-tune when operations should be executing. It might appear overly complicated to perform simple tasks such as communicating from a client to a server, but for very complex business logic, it is a must.

An example is a client logging in and starting to perform some tasks. While the client is in midstream of reading some of the data, the server must perform another task, such as a scheduled update of many of the client's records. After the server performs those operations, it finds out that the client's subscription has expired . At this point, the server can respond to the client with an error on his current subscription. The purpose of asynchronous communication and operations is to give the applications the capability to accomplish multiple tasks without waiting to finish a specific task.

  


Java Security Solutions
Java Security Solutions
ISBN: 0764549286
EAN: 2147483647
Year: 2001
Pages: 222

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net