Socket Channels
The SocketChannel class provides network input and output. With a few exceptions, most network protocols are read/write, and the same channel object is used for both reading and writing. However, you'll probably use different buffers for reading and writing.
SocketChannel implements both ScatteringByteChannel and GatheringByteChannel. You read or write it using the same methods and patterns you use to read or write a FileChannel. However, a few key differences between files and sockets are exposed at the level of the API:
- Sockets must be explicitly connected.
- Sockets can be disconnected.
- Sockets can be selected.
- Sockets support nonblocking I/O.
The last two points will be the subject of the next chapter. For now, let's explore the simple blocking style of socket I/O.
There are no constructors in the SocketChannel class. Instead, a new SocketChannel object is returned by one of the two static open( ) methods:
public static SocketChannel open( ) throws IOException public static SocketChannel open(SocketAddress remote) throws IOException
For example, this statement creates a new SocketChannel that is not yet connected to anything:
SocketChannel channel = SocketChannel.open( );
To connect to a remote site, you pass a java.net.SocketAddress (in practice, a java.net.InetSocketAddress) for the remote site to the channel's connect( ) method:
public abstract boolean connect(SocketAddress remote) throws IOException
For example:
SocketAddress remote = new InetSocketAddress("www.google.com", 80); channel.connect(remote);
To connect immediately, just pass the address directly to the open( ) method:
SocketAddress remote = new InetSocketAddress("www.google.com", 80); SocketChannel channel = SocketChannel.open(remote);
However, as often as not, you're going to want to configure the channel after opening it but before connecting it.
You can check whether a channel is currently connected with the isConnected( ) method:
public abstract boolean isConnected( )
This returns true while the channel is connected and false at other times. Of course, the SocketChannel also inherits all the usual methods of any channel, such as isOpen( ) and close( ).
There are a few more methods in this class, but they're all related to nonblocking I/O, which I'll take up in the next chapter. In the meantime, this is all you need to write simple network clients. For instance, it's easy to write a program that downloads the data from any given http URL (including the HTTP response header) and stores it in a file. The procedure is:
- Read the URL and the filename from the command line.
- Open a FileChannel to the file.
- Open a SocketChannel to the remote server.
- Connect the channel.
- Write the HTTP request header over the socket channel.
- Transfer the response from the server into the file.
Example 15-5 demonstrates.
Example 15-5. The HTTPGrab program
import java.net.*; import java.nio.*; import java.nio.channels.*; import java.io.*; public class HTTPGrab { public static void main(String[] args) throws IOException { if (args.length != 2) { System.err.println("Usage: java HTTPGrab url filename"); return; } URL u = new URL(args[0]); if (!u.getProtocol( ).equalsIgnoreCase("http")) { System.err.println("Sorry, " + u.getProtocol( ) + " is not supported"); return; } String host = u.getHost( ); int port = u.getPort( ); String file = u.getFile( ); if (file == null) file = "/"; if (port <= 0) port = 80; SocketAddress remote = new InetSocketAddress(host, port); SocketChannel channel = SocketChannel.open(remote); FileOutputStream out = new FileOutputStream(args[1]); FileChannel localFile = out.getChannel( ); String request = "GET " + file + " HTTP/1.1 " + "User-Agent: HTTPGrab " + "Accept: text/* " + "Connection: close " + "Host: " + host + " " + " "; ByteBuffer header = ByteBuffer.wrap(request.getBytes("US-ASCII")); channel.write(header); ByteBuffer buffer = ByteBuffer.allocate(8192); while (channel.read(buffer) != -1) { buffer.flip( ); localFile.write(buffer); buffer.clear( ); } localFile.close( ); channel.close( ); } } |