Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Professional Java.JDK.5.Edition (Wrox)

.pdf
Скачиваний:
31
Добавлен:
29.02.2016
Размер:
12.07 Mб
Скачать

Communicating between Java Components and Components of Other Platforms

Java is an ideal platform for server-side development. Many of the ongoing professional and open source Java development projects are for various server-side applications. J2EE dominates this Java server space, providing a strong open platform for many different types of server applications. One of the core principles and architectural themes in J2EE is the ability to segregate and distribute various components of the same software system to different machines. Remote communication between Java objects and components to other Java objects and components is at the heart of J2EE. Since J2EE is an open platform, it also defines how external objects and components in other applications (and even other programming languages) communicate with J2EE components. In today’s heterogeneous Internet-centric computing world, this communication is absolutely essential.

Components–Component is an ambiguous term that can mean many different things to many different developers. In the context of this chapter, component refers to any software object or collection of objects that are network-aware, either sending information to other components or receiving it from the latter. For example, a Web server could be considered a component. Web browsers and other client applications need to communicate with this component. Enterprise JavaBeans (EJBs; see Chapter 10, “Communicating between Java Components with RMI and EJB”) could also be thought of as components.

Chapter 11

In this chapter, you will investigate the general high-level design of component-to-component communication as well as some concrete examples for coding the actual communication. The java.net package will be looked at first for its socket’s API, since sockets are the basic building block for all other communication technologies. A brief discussion of Remote Method Invocation (RMI) and the Common Object Request Broker Architecture (CORBA) will follow. Concluding the chapter will be information on how best to utilize the latest and greatest craze in distributed software development, Web services.

Component Communication Scenarios

A few examples of where component-to-component communication takes place will aid the understanding of where sockets, CORBA, RMI, and Web services fit into a given application’s architecture. In each of the scenarios shown, almost any of these technologies could be used. Being equipped with more indepth knowledge of these technologies later on in the chapter will allow the software developer to weigh the pros and cons of each in their particular situation and pick the right technology for the job.

News Reader: Automated Web Browsing

Little software utilities can often eliminate tedious tasks such as constantly watching and monitoring particular Web sites. Software can be developed to automate these tasks as much as possible. Developing an application for monitoring Web sites would involve communicating with the remote Web server to check various news sites for new stories and information on topics of interest every ten minutes. Whenever a new story popped up, fitting your criteria, the user would be notified, eliminating the need to constantly check and refresh certain Web sites. Writing client components that monitor data sources for new information is a common task in distributed computing.

A Bank Application: An EJB/J2EE Client

Because of J2EE’s component-based nature, existing systems can often be extended by simply adding new software components, without destroying their existing infrastructure. Suppose a bank wants to modernize their client software that their tellers use to access the banking infrastructure. The terminals the bank tellers use daily are all running Microsoft Windows 2000 and the application must run on this existing infrastructure. The bank already has a J2EE-based back end to keep track of all banking data, and the application merely needs to interface with it. This J2EE system exposes a Web front end, which is good for personal use over the Internet by various members of the bank, but not for the heavy daily use necessary for tellers. A thick client is needed. The EJB components on the server will need to be accessed by the client. Writing client applications that access EJBs (or other J2EE components) is typical in professional Java development.

A Portal: Integrating Heterogeneous Data Sources and

Services

Many Web portals, such as Yahoo!, integrate various pieces of data such as stock tickers, sports scores, and news headlines. The software design of such a portal must be flexible enough to integrate many of these different pieces of data, oftentimes from many different locations. Many larger corporations have their own internal intranet portal. These portals need to access information from a variety of sources. Component-to-component communication is crucial to access the databases, files, and information from other software applications necessary for the functionality of the portal.

478

Communicating between Java Components and Components of Other Platforms

Over view of Interprocess Communication and Basic Network Architecture

In the development of these distributed software applications, it is often necessary for components running in one process to communicate with components running in another process. For instance, a database runs in one process on a server, and the client application that reads and writes information from and to this database runs in a separate process (and possibly on a different machine). There must be some mechanism through which these two processes communicate. Often, these other processes that your Java application must communicate with are not written in Java and are not running inside a virtual machine. Whether or not another process is running in a Java Virtual Machine, any communication between two processes must follow some sort of protocol. Protocols are the language two disparate components use to speak to one another. Your Web browser speaks the HyperText Transfer Protocol (HTTP) to Web servers to retrieve Web content to your local machine. Your instant messaging client speaks a certain protocol back to its server and potentially to other users of an instant messaging service. Peer-to- peer file-sharing services speak protocols to allow the searching and sharing of files (Gnutella is one popular example of a common protocol allowing many different file-sharing clients to communicate with each other.).

All of the applications and protocols mentioned can communicate over a network. They can also communicate to another process on the same machine. This is because these protocols have been abstracted from their transport. They could run locally, or over a TCP/IP network. In communicating between Java components and components of other platforms, you must always consider possible network transports. The Open Systems Interconnection (OSI) network architecture gives a high-level abstraction of some of the layers in any form of interprocess network communication. For the discussion in this chapter, you can think of an even higher-level architecture (derived from the OSI architecture) for understanding component-to-component communication. Figure 11-1 shows the derived architecture with three main layers: the application layer, the protocol layer, and the transport layer.

Application Layer (HTTP)

Protocol Layer (TCP/IP)

Transport Layer (Ethernet)

Application Layer (HTTP)

Protocol Layer (TCP/IP)

Transport Layer (Ethernet)

Network

Figure 11-1

479

Chapter 11

Two disparate components communicate by sending data through each of the layers as shown. The application layer represents high-level protocols such as HTTP or FTP. The protocol layer represents lower-level transport protocols such as TCP or UDP running over IP. The transport layer represents the actual physical transport, such as Ethernet, as its corresponding mechanisms for sending and retrieving data. For distributed components to communicate, they must speak the same protocol at the application level.

This chapter focuses on the application level; the lower-level hardware transport is out of the scope of this book. For most distributed application development, the application layer is most important to software developers. In Web applications, for example, HTTP is the application level protocol that dictates many of the application’s design decisions. HTTP does not support stateful connections, and therefore the state of any user’s session must be simulated by the use of session cookies or session identification parameters. Designing any network-aware application, or in other words, any application that must communicate between separate components, Java or non-Java, locally or remote, requires the knowledge of the limitations and features of the various application level and transport level protocols available to facilitate such communication.

Note: Threads are a critical aspect of designing any good I/O-intensive application, especially I/O over a network and between two disparate processes.

Sockets

Sockets are the basic mechanism for interprocess communication provided by the operating system. In most development projects, they will probably not have to be used explicitly, since they are fairly lowlevel. However, any type of interprocess communication is built on top of sockets, and in any type of network communication, sockets are used implicitly. Therefore, it would be prudent to understand just some simple background as to how they work. This section of the chapter will provide a broad overview of sockets for the purposes of better understanding RMI, CORBA, and Web services.

A socket is essentially a defined endpoint for communication between two processes. It provides a full duplex channel to two different parties (potentially more if it is multicasting) involved in communication — there are two separate data streams, one going in and one going out. There are two types of sockets:

User Datagram Protocol (UDP). Sockets using UDP provide a datagram service. They receive and send discrete packets of data. UDP is a connectionless protocol, meaning that there is no connection setup time as there is in TCP. However, UDP is unreliable — packets are not guaranteed to be sent or received in the right order. UDP is mainly used for applications such as multimedia streaming and online gaming, where not all data is necessary, for which the UDP’s best-effort service model is well suited.

Transmission Control Protocol (TCP). Sockets using TCP provide a reliable byte-stream service. TCP guarantees delivery of all packets sent and the reception of them in the correct order. TCP is a connection-oriented protocol, which allows it to provide the byte-stream service. TCP is best suited for applications that cannot allow data transmitted to be lost, such as for file transfer, Web browsing, or Telnet.

This section will only consider using TCP sockets, since UDP is more for advanced network applications that require the development of their own low-level protocols or multimedia streaming algorithms, which are out of the scope of this book. For the purposes of this text, sockets simply allow you an input and output stream to another process, either running locally or remotely.

480

Communicating between Java Components and Components of Other Platforms

The Java Socket API

The Java Socket API is the core Java interface to network programming. As such, all of the core socket classes are found in the java.net package. Java implements the two types of sockets: TCP sockets, which communicate using the Transmission Control Protocol, and UDP sockets, which communicate via the Universal Datagram Protocol. In addition to the normal UDP socket implementation, Java also provides a UDP multicast socket implementation, which is a socket that sends data to multiple clients simultaneously. Since Java was built from the ground up as an object-oriented language, you will find that the socket library interacts heavily with the Java I/O libraries (both java.io and java.nio). If you need a refresher on some of the aspects of Java I/O and serialization, see Chapter 5, “Persisting Your Application Using Files.” This section concentrates on TCP sockets throughout, because they are far more prevalent than UDP sockets in most client/server or distributed systems.

Key Classes

The following table shows the four major classes used for socket communication in Java. The Socket and DatagramSocket classes implement TCP and UDP, respectively. Both TCP and UDP use an IP address and port number as the demultiplexing key, or address, to another process. InetSocketAddress represents this address. Both Socket and DatagramSocket use an InetSocketAddress to locate the machine and process that should be the recipient of any data sent.

Class (From java.net)

Function

 

 

Socket

Class used to represent a client socket endpoint for sending and

 

receiving data over TCP connections.

DatagramSocket

Both client and server class for sending and receiving data sent

 

via UDP.

ServerSocket

Class used for TCP servers. Once a client connects, this class returns

 

a Socket class to actually send and receive data.

InetSocketAddress

Represents an IP address (or hostname) along with a port

 

number. For example, InetSocketAddress could represent

 

www.example.com:8080.

 

 

Client Programming

The Socket and InetSocketAddress classes are used by a client to connect to a server running in another process (whether remote or local). Once a connection is set up, all communication takes place utilizing normal Java I/O classes. There is a stream of data coming in, and a stream of data going out. To set up a connection, first create the address object that defines which server and port to connect:

InetSocketAddress address = new InetSocketAddress(“www.example.com”, 80);

InetSocketAddress objects can also be created with an IP address:

InetSocketAddress address = new InetSocketAddress(“127.0.0.1”, 80);

Once the address of the remote endpoint has been defined, a connection can be attempted. Be sure to catch java.io.IOException, as this exception will be thrown if there are any problems connecting

481

Chapter 11

(such as the network is down, the server is busy, the server cannot be located, and so on). In network programming, it is important to pay extra attention to error-handling details, as communication problems aren’t just a possibility — they are pretty much guaranteed to happen at some point. Now that you have defined an address, you can create a new Socket class to attempt a connection:

Socket socket = new Socket();

socket.connect(address);

If the connection succeeds, either Java I/O classes or NIO (java.nio) classes can be used to send and receive data. In these examples, you will use normal Java I/O because it is often easier to understand and provides better code readability. Once the socket is connected, both InputStream and OutputStream objects from the java.io package can be retrieved and communication can begin:

InputStream in = socket.getInputStream();

OutputStream out = socket.getOutputStream();

These objects are often wrapped around other higher-level and easier to use I/O classes just as they are in normal Java I/O programming. Suppose, for example, that all the communication you are going to send and receive over the socket is textual data. Java provides the BufferedReader and PrintWriter objects that can be wrapped around the input and output stream objects:

PrintWriter out = new PrintWriter(out);

BufferedReader br = new BufferedReader(new InputStreamReader(in)); out.println(“Hello, remote computer”);

out.flush();

String serverResponse = br.readLine();

Note: The call to flush() in the preceding code segment is important. PrintWriter and other I/O classes buffer data before writing them to their underlying output stream. To have the send take place immediately, you flush the underlying output stream so the data you have written to the PrintWriter is immediately written to the underlying output stream, in this case, the OutputStream from the Socket, which then sends the data over the network. PrintWriter can also be created to automatically flush any output written straight to the underlying output stream, at the disadvantage of losing the ability to buffer data before it is sent to optimize network performance.

That’s really all there is to sockets. The difficult aspect of sockets comes when determining and implementing the protocol by which two different processes agree to communicate. In the “Implementing a Protocol” section, the difficulties will be explored, and a small portion of HTTP will be implemented.

Server Programming

Programming server-side sockets with Java is just as easy as on the client-side. The ServerSocket class is used to initiate a passive TCP connection. A passive TCP connection monitors a particular port on the host machine and waits for a remote client to connect. Once a connection is initiated by a client, the ServerSocket class dispatches a Socket class, which in turn can be used to get the input and output streams associated with the connection (as well as the hostname and address of the client machine). Certain ports on computers are generally associated with certain protocols, port 80 is HTTP, 23 is Telnet, 25 is SMTP, and so on. When picking a port to use for your application, the general rule of thumb is to keep it above 1000, as most common server applications do not use ports in this range. If a ServerSocket is created on a port that is already in use, an exception will be thrown, and the server

482

Communicating between Java Components and Components of Other Platforms

socket will not be created. Only one application on a machine can use any given port at one time. The code below creates a ServerSocket and prepares it to accept incoming connections on port 1500:

ServerSocket serverSocket = new ServerSocket(1500);

Socket incomingClient = serverSocket.accept();

The accept() method blocks until a client connects. Once a client connects, a Socket instance is returned that represents the connection to the remote process. Input and output streams can be obtained to facilitate communication using the same mechanisms described in the preceding section. You do not have to call connect() on the incoming Socket though, since the connection setup has already occurred.

The previous code segment listed will accept one connection, and one connection only. Server-side applications generally need to service more than one client simultaneously however. Imagine if eBay or other popular Web sites could only serve one client at a time! The accept() method on the ServerSocket negotiates another port on the server for the client’s connection to move to, freeing up the original port the ServerSocket was created on for another incoming connection. You could call accept() again to wait for another connection. However convenient the behavior of accept() is though, it does not solve the problem of allowing multiple simultaneous connections. This is solved through the use of threads. The code below is a simple example of how a server could allow for multiple simultaneous connections:

boolean conditionToKeepRunning = true;

while (conditionToKeepRunning) {

Socket client = serverSocket.accept();

Thread clientServiceThread = new Thread(new ClassThatImplementsRunnable(client)); clientServiceThread.start();

}

Notice how every time your server receives a connection, it spawns off a worker thread to handle the incoming request. This allows the incoming request to be serviced while the server waits for another connection. Since each request receives its own thread, more than one request can also be processed at the same time.

Note: This model of one thread per request is not the most efficient solution; it is used here for simplicity. Creation and destruction of threads is an expensive operation, and a thread pool would be a better solution. Keeping a fixed number of active threads and using them as they become available can keep the server from being overloaded, as well as virtually eliminating the cost of thread creation and destruction.

Putting It All Together: An Echo Server

Writing a simple server application will demonstrate a full application using sockets. This cleverlynamed echo server will echo any text sent to it back to the client. Whenever a client connects, they will receive a welcome message, and after the message is sent, your server will simply begin its loop of echoing back to the client any text the client sends.

Our server class, SocketEcho, will implement java.lang.Runnable since every instance you create of SocketEcho will be running in a separate thread, allowing you to process multiple simultaneous connections. All of the server logic will reside in the SocketEcho.run() method (for the threading). In its constructor, SocketEcho is passed a Socket with which it conducts all communications with its client

483

Chapter 11

in the run() method. The run() method is shown below, and as you can see after the welcome message is printed, the application simply loops on receiving textual input from its client. Every time a new character is received, the server checks to see if it was the exit character (the ? in this case). If the exit character was received, the application breaks out of its loop and the socket is closed in the finally block.

Any other character besides the exit character is sent back to the client:

public void run() { try {

BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

PrintWriter out = new PrintWriter(socket.getOutputStream());

// print a welcome message

out.println(“Hello, you’ve contacted the Echo Server.”); out.println(“\tWhatever you type, I will type back to you...”); out.println(“\tPress ‘?’ to close the connection.”); out.println();

out.println();

out.flush();

int currChar = 0;

while ((currChar = br.read()) != -1) { char c = (char) currChar;

// if ‘?’ is typed, close the connection if (c == ‘?’)

break;

out.print(c);

out.flush();

}

}catch (IOException ioe) { ioe.printStackTrace();

}finally { try {

if (socket != null) { socket.close();

}

}catch (IOException ex) { ex.printStackTrace();

}

}

}

The main() function simply launches the server, using a ServerSocket. In here, the code for accepting client connections and spawning new threads is found. Every time a client connects, a new instance of SocketEcho is created with the client’s corresponding Socket instance, and a thread to run it is produced. Once this new thread is started, the control flow for the client that connected goes to the run() method in SocketEcho (which is in a different thread). While one or many clients are connected, the server can still wait for new connections, because the server handles each client in a separate thread:

try {

ServerSocket serverSocket = new ServerSocket(port);

System.out.println(“Echo Server Running...”);

484

Communicating between Java Components and Components of Other Platforms

int counter = 0; while (true) {

Socket client = serverSocket.accept();

System.out.println(“Accepted a connection from “ + client.getInetAddress().getHostName());

// use multiple threads to handle simultaneous connections Thread t = new Thread(new SocketEcho(client));

t.setName(client.getInetAddress().getHostName() + “:” + counter++); t.start(); // starts up the new thread and SocketEcho.run() is called

}

} catch (IOException ioe) { ioe.printStackTrace();

}

The full listing of the code for SocketEcho is found below:

package book;

import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket;

public class SocketEcho implements Runnable {

private Socket socket;

public SocketEcho(Socket socket) { this.socket = socket;

}

public void run() { try {

BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));

PrintWriter out = new PrintWriter(socket.getOutputStream());

// print a welcome message

out.println(“Hello, you’ve contacted the Echo Server.”); out.println(“\tWhatever you type, I will type back to you...”); out.println(“\tPress ‘?’ to close the connection.”); out.println();

out.println();

out.flush();

int currChar = 0;

while ((currChar = br.read()) != -1) { char c = (char) currChar;

// if ‘?’ is typed, close the connection if (c == ‘?’)

485

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]