Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Building Service Based Architectures with Jini


Challenging the client-server paradigm


Jini Puts RMI on Steroids


Client-server computing is an entrenched part of our daily lives. A client connects to its server, sends requests, receives responses, and goes about its business. Even the name "client-server" suggests a model that is tied to its implementation. While this model has served us well, it is being challenged by object-oriented schemes that offer significant advantages in flexibility. Among these are Service Based Architectures (SBA) built on Sun Microsystems's Jini framework for distributed services.

In a lecture at the 1999 Colorado Software Summit (http://www.software.summit .com/), David Moskowitz painted a more abstract view of client-server computing: "Clients do not seek servers. They seek services." The details of where and how those services are provided in a networked environment are usually immaterial to the client. The service may run on the same host as the client, or on remote hosts. There may be more than one service available on the network that can provide the service the client requires. Service implementations may move around on the network for load-balancing or fail-over reasons. Furthermore, clients often do not need constant contact with only one particular service. They may need occasional contact with a set of services to perform their work.

Rather than defining a server as a set of services behind a single, static interface, an SBA is a set of virtualized service implementations dynamically offering their services to any and all clients. Figure 1 illustrates a Jini implementation of this architecture. A loose collection of services distributed across a network assist clients in performing their work. In a Jini-based SBA, this is enabled by the infrastructure provided by the rmid activation daemon that starts Jini services when they are needed, a classfile server to serve Java bytecode, and a lookup service that lets servers advertise their presence to clients. In this model, the distinction between clients and services is blurred, since services may themselves be clients to other services.

An added benefit of an SBA is that it is a better fit to the object-oriented (OO) programming paradigm. Once you have a reference to a service object, the method calls you make to it are dependent only on the interface the service object exposes. How it implements this interface is immaterial, in theory. Furthermore, the protocol between the client and server may be specified by the contract defined by the interface. This contract can be enforced (to some extent) at compile time.

My team has recently cleaved a client-server split between the model and view layers of a monolithic network management application. A fundamental question that arose during our design was the nature of the interface between the client and server. We had experience designing Remote Method Invocation (RMI) in Java systems in which there was a single or small set of remote Server objects that would receive client requests, vector them off to handlers, and then channel the responses back to the requesting clients. The interfaces of these objects were narrow, and marshalled into the request was information about which operation the client needed to perform. The server objects also required close knowledge of the handlers since it acted as a middleman. As often happens in commerce, we began to ask ourselves why this middleman was necessary.

Several years ago in the article "Where the Objects Run" (Java Report Online, January 1998), Jim Secan and I looked at the same issue and decided that, for practical implementation reasons, a granular service architecture with many objects offering remote interfaces could not be easily and robustly implemented in Java with RMI alone. What changed my mind this time was Jini, although these concepts can also be applied using CORBA or more Microsoft-centric mechanisms. In fact, Microsoft's .NET initiative appears to be an SBA at the network operating-system level. We chose to implement our client-server interaction as an SBA built with Jini. In this article, I'll describe our experiences from design to implementation.

A Jini Overview

Jini is a framework that provides robust virtualization of services to Java clients with support for additional mechanisms such as asynchronous events, transactions, and persistence. A distributed Jini system (djinn) revolves around the existence of one or more lookup services, themselves Jini services. Jini services are objects that implement well-defined interfaces. The classic example of a Jini service is a process running on a printer that exposes a network API that lets clients use the printer to print documents.

Jini services advertise themselves by finding and registering with lookup services. The Discovery/Join protocol describes how services joining a djinn find lookup services and register themselves. IP multicast provides the bootstrap mechanism for two networked software entities that did not have prior knowledge of each other's existence to find and use each other. Upon registration, services provide a list of the interfaces they expose as well as Entry attributes that can distinguish them from other instances of services exporting the same interfaces. This list is referred to as a ServiceItem. For example, a printer could specify that it is a color printer located next to a particular office.

Clients in search of the existence of these services also use the Discovery protocol to find LookupServices. Once they have found them, they form ServiceTemplates to describe to the lookup service the kind of service they are looking for. The lookup service then matches the ServiceTemplate to its list of registered ServiceItems and returns to the client a set of references to services matching the specified criteria.

Jini's designers had a good deal of experience with the flaws of previous distributed systems and they created mechanisms in Jini to deal with these difficulties. For example, we know that computers and networks can come up and go down. To aid in the graceful handling of these failures, Jini defines a distributed leasing specification. Leases are used by services to indicate to the lookup service that they are available to provide a service for a given amount of time. Clients can also request leases on service-owned resources for a time. These leases must be renewed prior to expiration or the Landlord that granted the lease is free to release the leased resources. For example, references to a printer will no longer be given to clients by the lookup service if the printer fails to renew its lease. The printer might also use leases to protect its memory cache from clients who start print jobs but fail to complete them.

All client-service interaction is, of course, made platform independent by Java and its Virtual Machine (JVM). Jini services can execute locally or remotely. In the remote situation, input and output results are passed between client and remote service. Local service code can be downloaded and executed in the client's JVM space through Java's ability to remotely load platform-independent bytecode. This can be made transparent to the client. Imagine a printer with a platform-independent device driver that does not need to be manually installed in its clients. In this way, Jini synergistically builds on Java's greatest strengths.

Although Jini is a software architecture that aids in developing distributed, object-oriented systems, its highly touted killer app has been in the creation of impromptu networks containing such things as workstations, cell phones, printers, scanners, and cameras that require no configuration to work together. Here I focus on Jini's use in a more general software architecture context.

A Foundation for SBAs

The Jini lookup service lets server suites be composed of lots of granular service objects, each supporting its own interface that contains methods specific to that service. The rules of good object-oriented software design may be applied to server design. There is no need for an OO architecture to hide behind procedural interfaces. Service methods can be set up to execute either in the remote JVM in which the service object is instantiated or their code may be downloaded and executed in the client's JVM. Whether or not service code should execute locally can be determined according to performance and resource availability considerations.

Services can be highly dynamic, coming and going and moving from JVM to JVM. The lookup service ensures that services can be found by clients wherever they reside. If a service is not available when a client needs it, the client can register as a listener to be notified when a service matching its needs becomes available. Combined with Jini's extensible administration APIs, this enables desirable characteristics such as load balancing, fail-over and redundancy, and hot patches and upgrades.

The lookup service allows this kind of mobility for services that clients have not yet found, but Jini's leasing mechanism allows robust treatment by clients of services that are already in use. Service objects about to undergo maintenance or a move can wean their clients by shortening the leases they give out dynamically until no clients remain.

Leasing can also be used to protect services from client failures and clients from service failures when the clients become callback listeners. It provides a heartbeat mechanism that lets services protect themselves in the event a client is never heard from again. If a client fails to renew its lease, the service is within its rights to assume that the client has failed and resources allocated for it may be released. Jini support for transactions performs a similar function (consistency even in the event of failure of one of the distributed components) allowing coordination of actions across distributed components with ACID (atomicity, consistency, isolation, durability) properties.

Other elements of Jini support the SBA. The remote event specification provides standardization of event source and listener interfaces. This helps Jini service components work together and pass messages to each other in a standard way.

Finally, not specific to Jini but contributing to the robustness and scalability of Jini is the Java 2 Java Activation framework for remote objects. Activation lets services be created on an as-needed basis. If a request for an Activatable service reference is made by a client and that service is not currently running, either because it has not been started or because it has crashed, the activation daemon, rmid, can start a JVM to run the service and service the client request.

In short, RMI in Java 2 and Jini have added the mechanisms necessary to make the implementation of an SBA robust enough to operate reliably in real-world environments where host and network failures happen (also see the accompanying text box entitled "Jini Puts RMI on Steroids").

Designing a Jini-Enabled SBA

My team encountered a number of issues when designing our SBA. First of all, how do you start an SBA server? In addition to the Jini infrastructure processes that are necessary (rmid, the lookup service, and a classfile server), there must be some mechanism for starting other services. In our first release, our services would run in the same JVM, so we created a ServiceManager service to start this static set of services. A more dynamic approach would have been to place service classfiles in a directory and let the ServiceManager find and start them. Alternatively, distributed services can be started through Activation on the hosts on which they run. Although it poses some deployment challenges, this is an attractive solution because rmid can automatically restart services as needed if they stop for some reason. How or wherever services are started, you will probably want a centralized manager you can go to for starting and stopping the SBA.

A central point of contact can also be useful in helping to define what it means for the server to be up. Because the server is now a granular, distributed entity, its definition of up becomes more difficult. Clients, however, need this definition to prevent them from trying to function against an incomplete set of services.

Each client needs to define its own invariant of what services it requires for successful operation. Because it replaces the one-to-many cardinality of the traditional client-server with a many-to-many cardinality, the SBA approach is both a blessing and a curse. The fact that the server can partially function is good for those clients who do not need the failed services and bad for those clients with direct or indirect dependencies on them. Distributing and granularizing services creates a complex multiplicity of failure modes.

Another blessing/curse is the idea of local/remote transparency. One benefit of distributed-object programming is that once an object reference is obtained, local and remote method calls look about the same. However, this provides a false sense of simplicity because calls made on stub (local proxies for objects in a remote JVM) objects — even if they resolve to objects within the same JVM — are much slower than local method calls. In addition, be aware that RMI can turn passes by reference into passes by value. In some ways, the integration RMI provides between application code and networking code is too seamless for its own good. It loads the gun and assumes you will be smart enough not to point it at your foot and pull the trigger. IBM's Simon Nash offered the best advice I've heard concerning this difficulty: Method calls that are potentially remote should be treated as remote calls, and then if they end up running locally, all the better.

You must also be aware of JVM boundaries. For example, you can't rely on class variables to be global in scope once the application is distributed. Changes in these variables are not felt across JVM boundaries, and event and replication mechanisms must be created. Even if you decide to deal with this lack of global variables by always retrieving them from a central source, you must be willing to take the performance hit of making these remote calls.

Another interesting issue arises in the use of Factory methods as defined in Design Patterns by Erich Gamma et al. (Addison-Wesley, 1995). It is often a good design practice to encapsulate the creation of objects in a call to a central factory. But care should be taken whether the factory should be a local or remote service. Does the factory need to access resources (such as an object database) on a remote system and to have its products serialized or, for performance reasons, should the factory be a locally executed service that constructs objects in the client JVM? These issues should be considered when designing your factory services.

An RMI layer between clients and services can also alter the threading and synchronization models of your design. Local calls that used to be handled on a single thread's context become candidates for asynchronous threads when they become remote calls. Once asynchronousness is introduced, queuing mechanisms may be required to ensure that operations occur in the correct order. MultiJVM deadlocks can be particularly difficult to track down when your stack traces start and stop at JVM boundaries. Normal debugging techniques become much more complex when you can't trace a thread across the remote gap.

Finally, when designing a Jini-enabled SBA, know that you do not need to use all the features of Jini right away to realize its benefit. We currently have no need for the Transaction service or JavaSpaces technology, but we know they are there if we need them.

Implementing a Jini-Enabled SBA

Once your distributed system has been designed, there are still practical implementation issues to deal with. Because RMI uses object serialization to pass objects by value, version synchronization between clients and services can be a serious deployment issue. To communicate, not only must both sides agree on the definition of the service interfaces but also on the classes that are passed back and forth by value as parameters and return values.

Listing One illustrates these points using a Jini lookup service to help the client find the server service. Imagine that you have a RemoteServer and RemoteClient that communicate by RMI through calls to a well-defined RemoteServer interface. Even the parameters that are passed back and forth are treated through the interfaces ParameterValue and ReturnValue. Ideally, as long as these interfaces are constant and compatible between client and server, version incompatibility should not be an issue.

The problem comes in the way the underlying concrete objects implementing these interfaces are serialized by RMI. When one side receives an object sent by the other, the JVM first tries to reconstitute this object using the code in its local classpath. If this class exists, it is used to deserialize the object being received and, if the version of the class is different from the version of the class that was sent, an UnmarshallException is thrown and the operation fails. As indicated by commented code in the listings, I did several experiments adding and removing even private and protected fields and methods of ConcreteParameterValue and ConcreteReturnValue on one side without changing the code on the other side. Even when these fields and methods are not part of the defined interfaces, UnmarshallExceptions result. This makes the implementation of client-server communication under RMI quite fragile with respect to class versioning both forward and backward.

RMI aficionados will argue that this problem can be solved by having the receiver load the ConcreteReturnValue class from the rmi.server.codebase passed in its serialization stream. This works fine and appears to be the solution Jini's developers had in mind. However, this technique cannot be used if receivers use the class internally for purposes other than communication, and hence, need to be able to load it from their local classpath.

This is a shortcoming of the Jini SBA approach compared to more traditional client-server interfaces where the protocol version is assigned independently of the implementations on either side. By definition, an SBA makes it easy to change services granularly. The result is that many small protocol versions must be kept synchronized between clients and servers. Tracking changes that introduce version incompatibilities becomes much more complex.

There are ways of mitigating this problem.

  • Give up and require that versions between clients and services always be synchronized. This may or may not be practical in your customer's environment. It will certainly not endear you to system administrators who must deal with these deployment issues.
  • Run your clients and servers from the same codebase. This might be accomplished through the use of RMI class-loading mechanisms, shared filesystems, applet clients, or using a technology like Sun's WebStart (http://java.sun.com/products/javawebstart/). If your client always loads the classes it receives from the server's codebase, there is no possibility of version mismatch.

  • Become clever in the way you serialize the objects that are passed as part of remote method calls. This might involve deliberate tampering with the serialVersionUID of the classes involved (not recommended, but try playing with this in Listing One) or writing your own serialization mechanisms (in which case, you forgo the transparent marshalling that RMI buys you).

  • Maintain a clear distinction between the classes that are used as part of the internal client and server infrastructure and those classes that are part of the client-service interfaces. Remember to include the concrete classes that are serialized as parameter and return values. If you distinguish the classes that are part of the protocols and closely track when any of these classes changes, at least you will know when you have introduced a version incompatibility between your services and their clients.

Versioning issues became painfully clear to us when we first started using multicast Discovery/Join in our development team. Code running in one developer's sandbox became confused as it found services in another developer's sandbox running from a different codebase. Even if you are planning to deploy your application in a multicast environment, we recommend that developers use their own classfile server and lookup service and run Jini Discovery/Join protocols in unicast mode.

Another observation we made during development was that there is a temporal component to RemoteExceptions that should weigh into the design of your threading model. It is commonly known that remote method calls that fail due to host, process, or network failures, will throw a RemoteException. What is not widely known is how long the calling thread will have to wait before this RemoteException is thrown. Consider Listing Two, which uses a plain rmiregistry lookup. On Windows NT 4.0 running Java 1.2.2 and the client and service running in two different JVMs, a normal call to server.doIt() takes under 10 milliseconds. If the server process is killed, however, it takes 1.5 seconds before the RemoteException is thrown. Now place the client and server on different machines and bring the server's host offline. The time to RemoteException jumps to about 45 seconds, when the underlying socket mechanisms timeout. Depending on what else the thread that called this method is responsible for doing, a 45-second wait for the method to complete or fail may or may not be acceptable.

These issues have led me to put the idea of local/remote transparency in the same category as the misconception that Java programmers no longer need to worry about memory leaks (see "Porting Visual Basic Applications to Java," which I coauthored with Adam Levin-Delson, Java Report, February 2000). While Jini and SBAs offer some attractive capabilities, there are many issues that you need to understand and account for in your designs to use this technology effectively.

Conclusion

Jini is not just a technology. Like Java, it presents and enables a conceptual shift in software architecture design. Whereas Java, in theory, has made platforms irrelevant; Jini, in theory, makes the location of services irrelevant. Where theory meets reality is where it gets interesting. The Jini is out of the bottle.

DDJ

Listing One

 --  RemoteServer.java   --------------------------  
import java.rmi.*;
public interface RemoteServer extends Remote {
  public ReturnValue takeThisAndGiveMeThat(ParameterValue myThis) 
         throws RemoteException;
  // public ReturnValue anotherMethodInTheInterface(ParameterValue myThis) 
  //        throws RemoteException;
}
 --  ParameterValue.java   --------------------------  

import java.io.Serializable;
public interface ParameterValue 
       extends   Serializable   {
   public int get();
}
 --  ReturnValue.java   --------------------------  
import java.io.Serializable;
public interface ReturnValue 
       extends   Serializable  {
   public int get();
}
 --  ConcreteParameterValue.java   --------------------------  
public class ConcreteParameterValue 
       implements ParameterValue     {
  public int get() {
    return 8;
  }
  //  protected void anUnrelatedMethod() {
  //  }
}
 --  ConcreteReturnValue.java   --------------------------  
public class ConcreteReturnValue 
       implements ReturnValue     {
  // static final long serialVersionUID = 6679126200670664714L;
  // private int unusedPrivateField = 5;
  public int get() {
    return 7;
  }
  protected int someInternallyUsedMethod() {
    return 7;
  }
  // public int anotherInternallyUsedMethod() {
  //   return 12;
  // }
}
 --  RemoteClient.java   --------------------------  
import java.rmi.*;
public class RemoteClient {
 private RemoteServer server = null;
 public RemoteClient() throws Exception {
    System.setSecurityManager(new RMISecurityManager());
    server = lookupServer(); 
 }
 private RemoteServer lookupServer() throws Exception {
    RemoteServer aServer;
    JiniUtilities utils = new JiniUtilities("jini://127.0.0.1");
    aServer = (RemoteServer) utils.findService("RemoteServer");
    return aServer;
 }
 public void exchangeThisAndThatWithServer() {
    ParameterValue myThis;
    ReturnValue    myThat;
    try {
       myThis = new ConcreteParameterValue();
       myThat = server.takeThisAndGiveMeThat(myThis);

       System.out.println("I gave this: " +
                          Integer.toString(myThis.get()) +
                          " I got that: "  +
                          Integer.toString(myThat.get())
                          );
       // System.out.println("Calling another method.");
       // myThat = server.anotherMethodInTheInterface(myThis);
    } catch (Exception e) {
       System.err.println(e.toString());
    }
 }
 public static void main(String[] args) {
   try { 
     RemoteClient client = new RemoteClient();
     client.exchangeThisAndThatWithServer();
   } catch (Exception e) {
     System.err.println("RemoteClientFailed " + e.toString());
   }
 }
} // End of RemoteClient.
 --  RemoteServerImpl.java   --------------------------  
import java.rmi.*;
import java.rmi.server.*;
public class      RemoteServerImpl 
       extends    UnicastRemoteObject 
       implements RemoteServer         {
  private static final int KEEPALIVE_LOOP_INTERVAL = 1000000;
  public RemoteServerImpl() throws Exception {
     System.setSecurityManager(new RMISecurityManager());
     (new JiniUtilities("jini://127.0.0.1/")).registerService(this); 
  }
  public ReturnValue takeThisAndGiveMeThat(ParameterValue myThis) 
         throws RemoteException 
  {
      myThis.get();
      ReturnValue that = new ConcreteReturnValue();
      return that;
  }
  public static void main(String[] args) {
   try {
      RemoteServer myServer = new RemoteServerImpl();
      while (true) {
         Thread.sleep(KEEPALIVE_LOOP_INTERVAL);
      }
   } catch (Exception e) {
      System.err.println("RemoteServer failed " + e.toString());
   }
  }
} // End of RemoteServerImpl.

 --  JiniUtilities.java   --------------------------  
import java.rmi.*;
import net.jini.core.lookup.*;
import net.jini.core.lease.*;
import net.jini.core.discovery.*;

import com.sun.jini.lease.*;

/** An a minimalist utility class to encapsulate registration
 *  and lookup of Jini services against a single lookup service
 *  found by unicast discovery.  This code is for illustration  only.
 */
public class JiniUtilities
       implements LeaseListener  
{
    /* A cached handle to the lookup service.  */
    public ServiceRegistrar registrar;
    /* A utility that will take care of periodically renewing
     * our service's leases with the lookup service.
     */
    public LeaseRenewalManager leaseManager;
    /* JiniUtilities(String) instantiates a JiniUtilities
     *  locating the lookup service via a unicast "discovery." 
     */
    public JiniUtilities(String requestedURL)
        throws Exception
    {
        registrar = unicastDiscover(requestedURL);
        leaseManager = new LeaseRenewalManager();
    }
    /* This discovers the lookup service that we register and retrieve 
     * services from via a unicast discovery method.
     */
    private ServiceRegistrar unicastDiscover(String url)
        throws Exception
    {
        LookupLocator lookup;
        ServiceRegistrar registrar = null;
        try {
            lookup = new LookupLocator (url);
            registrar  = lookup.getRegistrar();
        } catch (Exception e) {
            System.err.println("JiniUtils: Unicast search for " + 
                               url + " failed: " + e.toString());
            throw e; 
        }
        return registrar;
    }
    /* Register the specified service and, in this case,
     * set it up to renew its lease "forever."
     */
    public void registerService(Remote service)
        throws RemoteException
    {
        ServiceID savedServiceID = getServiceID(service);
        ServiceItem item = new ServiceItem(savedServiceID, service, null);
        ServiceRegistration reg = null;
        try {
            reg = registrar.register(item, Lease.FOREVER);
            leaseManager.renewUntil(reg.getLease(), Lease.FOREVER, this);
        } catch(java.rmi.RemoteException e) {
            System.err.println("JiniUtilities: Register exception: " + 
                               e.toString());
            throw e;
        }
    }
    /* Ensures that services always register with the same serviceID. If 
     * multiple services were involved, these serviceIDs would be handed 
     * out by the lookup service and persisted in a file. For this 
     * experiment simply return a constant ServiceID.
     */
    private ServiceID getServiceID(Remote service) {
        return new ServiceID(7L,7L);
    }
    /* Find the first available service implementing specified interface. */
    public Object findService(String classname)
        throws Exception
    {
        Object foundService = null;
        try {
            Class [] classes = new Class[] {Class.forName(classname)};
            ServiceTemplate template = new ServiceTemplate(null   , 
                                                           classes,
                                                           null      );
            foundService = (Object) registrar.lookup(template);
        } catch (java.rmi.RemoteException e) {
            System.err.println(
                   "JiniUtilities: exception when finding service!"
                              );
            throw e;
        }
        return foundService;
    }
    /* This is called when a LeaseRenewelManager fails to renew a
     * service lease.  See LeaseListener for more details.  
     */
    public void notify(LeaseRenewalEvent evt) {
        System.out.println("JiniUtils: Lease expired: " + evt.toString());
    }
} // End of JiniUtilities.

Back to Article

Listing Two

 --  RemoteClient.java   --------------------------  
import java.rmi.*;
import java.util.Date;
public class RemoteClient {
 private RemoteServer server = null;
 public RemoteClient() throws Exception {
    server = lookupServer(); 
 }
 public RemoteServer lookupServer() throws Exception {
    RemoteServer aServer;
    aServer = (RemoteServer) Naming.lookup("RemoteServer");
    return aServer;
 }
 public long timeRemoteCall() {
    Date start, finish;
    long delta;
    start = new Date();
    try {
       server.doIt();
    } catch (RemoteException re) {
       System.err.println("Hit a RemoteException");
    } catch (Exception e) {
       System.err.println("Hit a different Exception");
    } finally {
       finish = new Date();
    }
    delta = finish.getTime() - start.getTime();
    return delta;
 }
 public static void main(String[] args) {
   try { 
    RemoteClient client = new RemoteClient();
    while(true) {
       long elapsedTime = client.timeRemoteCall();
       System.out.println("Remote call time was "   + 
                          Long.toString(elapsedTime)  );
    }
   } catch (Exception e) {
     System.err.println("RemoteClientFailed " + e.toString());
   }
 }
} // End of RemoteClient.

 --  RemoteServer.java   --------------------------  
import java.rmi.*;
public interface RemoteServer extends Remote {
  public void doIt() throws RemoteException;
}

 --  RemoteServerImpl.java   --------------------------  
import java.rmi.*;
import java.rmi.server.*;

public class      RemoteServerImpl 
       extends    UnicastRemoteObject 
       implements RemoteServer         {
  public RemoteServerImpl() throws Exception {
     Naming.rebind("RemoteServer", this);
  }
  public void doIt() throws RemoteException {
      // do nothing.
  }
  public static void main(String[] args) {
   try {
      new RemoteServerImpl();
   } catch (Exception e) {
      System.err.println("RemoteServer failed " + e.toString());
   }
 }
} // End of RemoteServerImpl.





Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.