Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Java sockets 101.pdf
Скачиваний:
22
Добавлен:
24.05.2014
Размер:
207.01 Кб
Скачать

Presented by developerWorks, your source for great tutorials

ibm.com/developerWorks

FileReader fileReader = new FileReader(new File(streamReader.readLine()));

First, we make use of our BufferedReader on the Socket'sInputStream. We should be getting a valid file path, so we construct a new File using that path name. We make a new FileReader to handle reading the file.

BufferedReader bufferedFileReader = new BufferedReader(fileReader);

Here we wrap our FileReader in a BufferedReader to let us read the file line by line.

Next, we call readLine() on our BufferedReader. This call will block until bytes come in. When we get some bytes, we put them in our local line variable, and then write them out to the client. When we're done reading and writing, we close the open streams.

Note that we closed streamWriter and streamReader after we were done reading from the Socket. You might ask why we didn't closestreamReader immediately after reading in the file name. The reason is that when you do that, your client won't get any data. If you close the streamReader before you close streamWriter, you can write to the Socket all you want but no data will make it across the channel (it's closed).

Wrapping up the server

Before we move on to another, more practical example, let's review the steps to create and use a ServerSocket:

1.Instantiate a ServerSocket with a port on which you want it to listen for incoming client connections (throws an Exception if there's a problem).

2.Call accept() on the ServerSocket to block while waiting for connection.

3.Get the streams on that underlying Socket for reading and writing.

4.Wrap the streams as necessary to simplify your life.

5.Read from and write to the Socket.

6.Close your open streams (and remember, never close your Reader before your Writer).

You can find the complete code listing for RemoteFileServer at Code listing for RemoteFileServer on page 34.

Java sockets 101

Page 17 of 38

Presented by developerWorks, your source for great tutorials

ibm.com/developerWorks

Section 5. A multithreaded example

Introduction

The previous example gives you the basics, but that won't take you very far. If you stopped here, you could handle only one client at a time. The reason is that handleConnection() is a blocking method. Only when it has completed its dealings with the current connection can the server accept another client. Most of the time, you will want (and need) a multithreaded server.

There aren't too many changes you need to make toRemoteFileServer to begin handling multiple clients simultaneously. As a matter of fact, had we discussed backlogging earlier, we would have just one method to change, although we'll need to create something new to handle the incoming connections. We will show you here also how ServerSocket handles lots of clients waiting (backing up) to use our server. This example illustrates an inefficient use of threads, so be patient.

Accepting (too many?) connections

Here we implement the revised acceptConnections() method, which will create a ServerSocket that can handle a backlog of requests, and tell it to accept connections:

public void acceptConnections() { try {

ServerSocket server = new ServerSocket(listenPort, 5);

Socket incomingConnection = null; while (true) {

incomingConnection = server.accept(); handleConnection(incomingConnection);

}

} catch (BindException e) {

System.out.println("Unable to bind to port " + listenPort); } catch (IOException e) {

System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);

}

}

Our new server still needs to acceptConnections() so this code is virtually identical. The highlighted line indicates the one significant difference. For this multithreaded version, we now specify the maximum number of client requests that can backlog when instantiating the ServerSocket. If we don't specify the max number of client requests, the default value of 50 is assumed.

Here's how it works. Suppose we specify a backlog of 5 and that five clients request connections to our server. Our server will start processing the first connection, but it takes a long time to process that connection. Since our backlog is 5, we can have up to five requests in the queue at one time. We're processing one, so that means we can have five others waiting. That's a total of six either waiting or being processed. If a seventh client asks for a connection while our server is still busy accepting connection one (remember that 2-6 are still in queue), that seventh client will be refused. We will illustrate limiting the number of clients that can be connected simultaneously in our pooled server example.

Java sockets 101

Page 18 of 38

Presented by developerWorks, your source for great tutorials

ibm.com/developerWorks

Handling connections: Part 1

Here we'll talk about the structure of thehandleConnection() method, which spawns a new Thread to handle each connection. We'll discuss this in two parts. We'll focus on the method itself in this panel, and then examine the structure of the ConnectionHandler helper class used by this method in the next panel.

public void handleConnection(Socket connectionToHandle) {

new Thread(new ConnectionHandler(connectionToHandle)).start();

}

This method represents the big change to our RemoteFileServer. We still call handleConnection() after the server accepts a connection, but now we pass that Socket to an instance of ConnectionHandler, which is Runnable. We create a new

Thread with our ConnectionHandler and start it up. The ConnectionHandler'srun() method contains the Socket reading/writing and File reading code that used to be in handleConnection() on RemoteFileServer.

Handling connections: Part 2

Here is the structure for the ConnectionHandler class:

import java.io.*; import java.net.*;

public class ConnectionHandler implements Runnable{ Socket socketToHandle;

public ConnectionHandler(Socket aSocketToHandle) { socketToHandle = aSocketToHandle;

}

public void run() {

}

}

This helper class is pretty simple. As with our other classes so far, we import java.net and java.io. The class has a single instance variable, socketToHandle, that holds the Socket handled by the instance.

The constructor for our class takes a Socket instance and assigns it to socketToHandle.

Notice that the class implements the Runnable interface. Classes that implement this interface must implement the run() method, which our class does. We'll go into the details of run() later. For now, just know that it will actually process the connection using code identical to what we saw before in our RemoteFileServer class.

Implementing run()

Here we implement the run() method, which will grab the streams on our connection, use them to read from and write to the connection, and close them when we are done:

public void run() { try {

Java sockets 101

Page 19 of 38

Presented by developerWorks, your source for great tutorials

ibm.com/developerWorks

PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream( BufferedReader streamReader =

new BufferedReader(new InputStreamReader(socketToHandle.getInputStream String fileToRead = streamReader.readLine();

BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead)) String line = null;

while ((line = fileReader.readLine()) != null) streamWriter.println(line);

fileReader.close();

streamWriter.close();

streamReader.close(); } catch (Exception e) {

System.out.println("Error handling a client: " + e);

}

}

The run() method on ConnectionHandler does what handleConnection() on RemoteFileServer did. First, we wrap the InputStream and OutputStream in a BufferedReader and a PrintWriter, respectively (using getOutputStream() and getInputStream() on the Socket). Then we read the target file line by line with this code:

FileReader fileReader = new FileReader(new File(streamReader.readLine())); BufferedReader bufferedFileReader = new BufferedReader(fileReader); String line = null;

while ((line = bufferedFileReader.readLine()) != null) { streamWriter.println(line);

}

Remember that we should be getting a valid file path from the client, so we construct a new File using that path name, wrap it in a FileReader to handle reading the file, and then wrap that in a BufferedReader to let us read the file line by line. We call readLine() on our BufferedReader in a while loop until we have no more lines to read. Remember that the call to readLine() will block until bytes come in. When we get some bytes, we put them in our local line variable, and then write them out to the client. When we're done reading and writing, we close the open streams.

Wrapping up the multithreaded server

Our multithreaded server is done. Before we move on to the pooled example, let's review the steps to create and use a multithreaded version of the server:

1.Modify acceptConnections() to instantiate a ServerSocket with a default 50-connection backlog (or whatever specific number you want, greater than 1).

2.Modify handleConnection() on the ServerSocket to spawn a new Thread with an instance of ConnectionHandler.

3.Implement the ConnectionHandler class, borrowing code from the handleConnection() method on RemoteFileServer.

You can find the complete code listing for MultithreadedRemoteFileServer at Code listing for MultithreadedRemoteFileServer on page 35, and the complete code listing for ConnectionHandler at Code listing for ConnectionHandler on page 35.

Java sockets 101

Page 20 of 38