NIO HOW to send many requests to a site, using only one SocketChannel

Hello everybody,

I begin to work with java.nio. Here is description about my program: The client open one connection to a site, using NIO ( SocketChannel, configureBlocking(false) ).

After finish connection, the client send request to server and wait for read data. After reading (read to the end of file transfer) the client try to send the other request to server. The first request is successful but the second is not. The client cannot send more than one request on the same connection. (I verified that the server didn't receive any other request, although the client had sent it)

What's wrong here? It means to send a sequence of request to the same server, we need close the connection after the previous request, and than open again, and continue by that way? I think it's wrong because it wastes time and resource to open/close socket (SocketChannel) to the sam site.

Please tell me HOW to send many requests to a site, using only one SocketChannel, needn't close/open each time of request.

Thank you very much.

[1064 byte] By [nightingalea] at [2007-11-15]
# 1

> HOW to send many requests to a site

If you mean they are parallel/concurrent, then use different local port for each socket.

And, your scenario doesn't utilize java.nio's advantage over ordinary multi-threaded solution.

Java nio is useful for a very busy server implementation, not client.

And, you must read the book: http://www.telekinesis.com.au/wipv3_6/page2/show.jsp?db=Entries&id=230165

hiwaa at 2007-7-12 > top of java,Core,Core APIs...
# 2
If you can't send more than one request you have a bug in your code. Show us.
ejpa at 2007-7-12 > top of java,Core,Core APIs...
# 3

Thank you very much for your reply!

I tell more detail about the program: in fact it's a web-crawler, so at first I test if it can download a lot of web pages from one server. I use one SocketChannel, connect to server.

The first step: wait for connection:

====== socketChannel.register(selector, SelectionKey.OP_CONNECT);

When the connection is finished --> wait for writing request :

====== key.interestOps(SelectionKey.OP_WRITE);

Then write the first request to server when the channel isWritable.

Next step: wait for reading (wait for response from server)

====== key.interestOps(SelectionKey.OP_READ);

When finish reading from server (read to EOF), wait for writing the next request:

====== key.interestOps(SelectionKey.OP_WRITE);

And then wait for reading again. The loop goes by that way.

For the first request, everything is ok. Send the second request, I found that the channel is readable at once, after sending request.; but when reading, the stream is always empty ( I receive only EOF). I try to wait for some second before reading but the result is the same too.

I'm out of my mind because of this problem. Would you help me please?

nightingalea at 2007-7-12 > top of java,Core,Core APIs...
# 4

By the way I show you the code : it's just an example, you can run it with one argument is hostname:

package test.examples;

import java.io.IOException;

import java.net.*;

import java.nio.*;

import java.nio.channels.*;

import java.util.*;

/**

* Created by IntelliJ IDEA.

*

* @author : Thanh Huyen

* Date: May 11, 2007

* Time: 4:52:12 PM

*/

public class NIOTest extends Thread {

private String hostname;

private Vector<String> poolRequests;

private Vector<String> poolPageDownload;

private SocketChannel socketChannel;

private Selectorselector;

private ByteBufferreadByteBuffer;

private StringBuffertmpPageDownload; // to save temporary download content

public NIOTest(String hostname){

this.hostname = hostname;

poolRequests= new Vector();

poolPageDownload = new Vector();

}

/**

* @param hostname : dns name of server that we will send request to.

* @param pagePath : path of page request on server.

* @return return a standard HTTP GET Request to server.

*/

private String generateGetRequest(String hostname, String pagePath) {

return "GET " + pagePath + " HTTP/1.1 \n"

+ "Host: " + hostname + "\n"

+ "Accept: text/* \n"

+ "Connection: Keep-Alive \n"

+ "Accept-Encoding: gzip,deflate\n"

+ "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\n"

+ "\n";

}

/**

* Initialize all the SocketChannel, Selector and read buffer, write buffer

* poolRequest.

*/

private void initialize()

{

//Open Selector:

try {

selector = Selector.open();

} catch (IOException e) {

e.printStackTrace();

}

readByteBuffer= ByteBuffer.allocate(500);

tmpPageDownload = new StringBuffer();

//Open SocketChannel, register channel to selector

try{

socketChannel = SocketChannel.open();

socketChannel.configureBlocking(false);

SocketAddress socketAddress = new InetSocketAddress(hostname,80);

socketChannel.connect(socketAddress);

socketChannel.register(selector, SelectionKey.OP_CONNECT);

socketChannel.socket().setKeepAlive(true);

}

catch (IOException e) {

e.printStackTrace();

}

}

/**

* ConnectionThread run! Open many connection, wait for write, read...

*/

public void run() {

//Initialize

initialize();

// Finish Connection

try{

socketChannel.finishConnect();

}

catch (IOException e )

{

e.printStackTrace();//TODO

}

//Waiting for connect, write and read

int countActiveChannel = 0;

Set readyKeys;

Iterator iterator;

while (true) {

try{

countActiveChannel = selector.select();

}catch(IOException e){

e.printStackTrace();

}

if (countActiveChannel == 0)

continue;

System.out.println("Selector active " + countActiveChannel + " channel");

readyKeys = selector.selectedKeys();

iterator = readyKeys.iterator();

while (iterator.hasNext()) {

SelectionKey key = (SelectionKey) (iterator.next());

// Remove key from set so we don't process it twice

iterator.remove();

if (!key.isValid()) {

System.out.println("invalid key");

continue;

}

// operate on the channel...

//switch to wait to send request status (OP_WRITE)

if(key.isConnectable())

{

try{

if(((SocketChannel)key.channel()).finishConnect()){

key.interestOps(SelectionKey.OP_WRITE);

System.out.println("Connected, waiting for writing");

}

else {

System.out.println("Not connected yet!");

}

}catch(IOException e)

{

e.printStackTrace();

continue;

}

continue;

}

//read data from server

if (key.isReadable()) {

System.out.println("Readable channel ");

readData(key);

continue;

}

//Write/send request to server

if (key.isWritable()) {

System.out.println("writable channel ");

writeRequest(key);

}

}

}

}

/**

* Send HTTP GET request a page to server, according to SocketChannel that is active to write.

* @param key : SelectionKey: to determine which channel, correspondent to

* this key is active for writing request.

* @return 0 if request is written, -1 if out of request, 1 if the key not correspondent with any socketchannel

*/

private int writeRequest(SelectionKey key) {

//the channel is ready to write request to server

if (poolRequests == null || poolRequests.size() == 0) {

System.out.println("Request empty!");

this.stop();

return -1;

}

//Get page path from pool of requests.

String pagePath = poolRequests.remove(0);

//Generate request

String request = generateGetRequest(hostname, pagePath);

ByteBuffer buf = ByteBuffer.wrap(request.getBytes());

try {

while(buf.hasRemaining()){

socketChannel.write(buf);

}

System.out.println("We have sent a request: ");

System.out.println(request);

//Change status of waiting: waiting for reading

key.interestOps(SelectionKey.OP_READ);

//The first line in pageDownload is pagePath

tmpPageDownload.append(pagePath + "\n");

} catch (IOException e) {

e.printStackTrace();

return -2;

}

return 0;

}

/**

* Read data response from server, correspondent with SocketChannel of 'key' .

* All data read in each time will be appended in an temporary buffer.

* When reach to end of file, all data from temporary buffer will be put to

* poolData, to transfer to DataProcessingThread.

* @param key : SelectionKey, to determine which SocketChannel is active to read.

*/

private void readData(SelectionKey key)

{

int n= 0;

if (((SocketChannel) key.channel()).socket().isClosed()) {

System.out.println("Socket closed");

this.stop();

}

//the channel i is ready to read data from server

try {

n = socketChannel.read(readByteBuffer);

if (n > 0) {

readByteBuffer.flip();

String bufContent = new String(readByteBuffer.array(), 0, n);

tmpPageDownload.append(bufContent);

readByteBuffer.clear();

System.out.println("Read a part ..." + n + " bytes");

}

else if (n == -1) //end of data transfer

{

System.out.println("End of stream data");

System.out.println("We have receive from server: ");

System.out.println(tmpPageDownload);

poolPageDownload.add(tmpPageDownload.toString());

//clear tmpPageDownload to receive new page download.

tmpPageDownload.delete(0, tmpPageDownload.length() + 1);

//Change status of waiting: wait for write new request

key.interestOps(SelectionKey.OP_WRITE);

}

} catch (IOException e) {

e.printStackTrace();//TODO

}

}

public void addRequest(String pagePath)

{

poolRequests.add(pagePath);

}

public static void main(String[] args) {

String hostname;

if(args.length >0 )

hostname = args[0];

else hostname = "yahoo.com";

NIOTest nio = new NIOTest(hostname);

//add some page paths to request pool

//You can add more request here!

nio.addRequest("/");

nio.addRequest("/");

nio.start();

}

}

nightingalea at 2007-7-12 > top of java,Core,Core APIs...
# 5

I have another simpler question.

I tried to use raw Socket to send more than one request to a server (HTTP GET request) on one connection (open only one Socket) , of course the requests are sent in order. After reading the response for the first request, I sent the second , wait ... and wait for a long time but I didn't receive any response from server, although in GET request, I keep : Connection:Keep-Alive.

What's wrong here? Could you please show me any examples, in that the client can send more than 1 request to server and receive response, using only one Socket (open/close only once) . Thank you in advance!

nightingalea at 2007-7-12 > top of java,Core,Core APIs...
# 6

I saw an example in the book you recommended, a nio part . In 3 examples : NIOSocketClient, NIOSocketServer, NewIO_UDP_EchoServer, I found that the connection (channel) is closed after reading and writing only once. So it cannottransfer data many times in one connection. Please tell me how could I do?

(the author is ejp, is it your example ejp )

nightingalea at 2007-7-12 > top of java,Core,Core APIs...
# 7

You have several problems here.

First, you are calling finishConnect() in two places. Given the structure of your program it would make more sense to do the connect in blocking mode and then just select for OP_READ and OP_WRITE when appropriate and delete both occurrences of finishConnect() along with the CONNECT registration for OP_CONNECT. If you want to process OP_CONNECT asynchronously you must (I) register for OP_CONNECT and nothing else, (ii) call finishConnect() when it fires, (iii) test the result of finishConnect(), and (iv) if it is 'true' deregister OP_CONNECT and register OP_READ and/or OP_WRITE. The way you have it will not work.

Second, if you are getting -1 on a read it means the other end has closed the connection. There is no more data to be read, it isn't missing, you just have a misconception about the application protocol: the other end will only handle one request. Nothing to do with NIO here. If this is HTTP you need to send a Connection: keep-alive header and use the HTTP 1.1 protocol to get the server to keep the connection open.

ejpa at 2007-7-12 > top of java,Core,Core APIs...
# 8

Thank you for the answer, but there are something that I don't understand.

What do you mean " deregister OP_CONNECT and register OP_READ and/or OP_WRITE"? How can we "deregister". I use only one command

-- key.interestOps(SelectionKey.OP_READ);

I think this method will change the key to the status of waiting for reading. Do not need to do command else.

I did as you said. I call finishConnect() twice, one is for finish connection and one is just for test the result of connection, then I change the key to OP_WRITE, and then OP_READ. I keep the Connection: Keep-Alive in the HTTP GET Request. In the header of response for the first request from server, I saw that Connection:Keep-Alive, too. So it means server still keep-alive my connection.

Everything is ok for the first request. I receive data. Send the second request, change the key to OP_READ again, but I found that the channel is readable right away after I send the second request. It' s strange. And although the channel is Readable, I always receive -1. If the connection is close from server, so and exception must be thrown: An established connection has been aborted by your host machine. So maybe the reason of (-1) is not from closed connection (because connection already keep alive).

I tried a lot of test, but still not successful. Thank you very much for your help.

nightingalea at 2007-7-12 > top of java,Core,Core APIs...
# 9

> I think this method will change the key to the

> status of waiting for reading.

Correct.

> I did as you said. I call finishConnect()

> twice, one is for finish connection and one is

> just for test the result of connection

This makes no sense. Why execute the first one? What is accomplished? And there's no point in calling it at all until OP_CONNECT has fired. Conversely if you really want to call it straight way why not connect in blocking mode, and switch to non-blocking mode when connect() returns? (without throwing an exception).

> Everything is ok for the first request. I receive

> data. Send the second request, change the key to

> OP_READ again, but I found that the channel is

> readable right away after I send the second

> request. It' s strange. And although the channel is

> Readable, I always receive -1.

That means the server has closed the connection. What is 'readable' is the EOS condition.

> If the connection is

> close from server, so and exception must be thrown:

> An established connection has been aborted by your

> host machine.

That exception means that you have written to a connection which is already closed at the other end.

If the server is closing the connection despite your keep-alive header, there's nothing much you can do about it except close your own channnel (when you get the -1) and open another one. Equally, when you get any IOException or subclass exception from a Socket or SocketChannel there's nothing to be done except close it.

I would check the syntax of the keep-alive header you are sending. Also make sure your HTTP header says 1.1 not 1.0 otherwise keep-alive will be ignored.

ejpa at 2007-7-12 > top of java,Core,Core APIs...
# 10

Thank you very much ejp. Eventually I fixed the bug! Now I have realize a HttpClient using NIO.

Before I thought that we always receive -1 when server finished any request (Http Request)(oh fool). It's right only when the connection:close. For persistent connection, we need to determine where is end of response by using Content-length or \n0\n (chunked). Thank you again!

nightingalea at 2007-7-12 > top of java,Core,Core APIs...