SocketChannel in Java sends data, but it doesn't get to destination application

Posted by Peterson on Stack Overflow See other posts from Stack Overflow or by Peterson
Published on 2009-07-09T20:30:37Z Indexed on 2010/06/14 21:02 UTC
Read the original article Hit count: 415

Filed under:
|
|

Hi Everybody,

I'm suffering a lot to create a simple ChatServer in Java, using the NIO libraries. Wonder if someone could help me. I am doing that by using SocketChannel and Selector to handle multiple clients in a single thread. The problem is: I am able to accept new connections and get it's data, but when I try to send data back, the SocketChannel simply doesn't work. In the method write(), it returns a integer that is the same size of the data i'm passing to it, but the client never receives that data. Strangely, when I close the server application, the client receives the data. It's like the socketchannel maintains a buffer, and it only get flushed when I close the application.

Here are some more details, to give you more information to help. I'm handling the events in this piece of code:

private void run() throws IOException {

	ServerSocketChannel ssc = ServerSocketChannel.open();

	// Set it to non-blocking, so we can use select
	ssc.configureBlocking( false );

	// Get the Socket connected to this channel, and bind it
	// to the listening port
	this.serverSocket = ssc.socket();
	InetSocketAddress isa = new InetSocketAddress( this.port );
	serverSocket.bind( isa );

	// Create a new Selector for selecting
	this.masterSelector = Selector.open();

	// Register the ServerSocketChannel, so we can
	// listen for incoming connections
	ssc.register( masterSelector, SelectionKey.OP_ACCEPT );

	while (true) {
		// See if we've had any activity -- either
		// an incoming connection, or incoming data on an
		// existing connection
		int num = masterSelector.select();

		// If we don't have any activity, loop around and wait
		// again
		if (num == 0) {
			continue;
		}

		// Get the keys corresponding to the activity
		// that has been detected, and process them
		// one by one
		Set keys = masterSelector.selectedKeys();
		Iterator it = keys.iterator();
		while (it.hasNext()) {
			// Get a key representing one of bits of I/O
			// activity
			SelectionKey key = (SelectionKey)it.next();

			// What kind of activity is it?
			if ((key.readyOps() & SelectionKey.OP_ACCEPT) ==
				SelectionKey.OP_ACCEPT) {

				// Aceita a conexão
				Socket s = serverSocket.accept();

				System.out.println( "LOG: Conexao TCP aceita de " + s.getInetAddress() + ":" + s.getPort() );

				// Make sure to make it non-blocking, so we can
				// use a selector on it.
				SocketChannel sc = s.getChannel();
				sc.configureBlocking( false );

				// Registra a conexao no seletor, apenas para leitura
				sc.register( masterSelector, SelectionKey.OP_READ );

			} else if ( key.isReadable() ) {
				SocketChannel sc = null;

				// It's incoming data on a connection, so
				// process it
				sc = (SocketChannel)key.channel();

				// Verifica se a conexão corresponde a um cliente já existente

				if((clientsMap.getClient(key)) != null){
					boolean closedConnection = !processIncomingClientData(key);
					if(closedConnection){
						int id = clientsMap.getClient(key);
						closeClient(id);
					}
				} else {
					boolean clientAccepted = processIncomingDataFromNewClient(key);
					if(!clientAccepted){
						// Se o cliente não foi aceito, sua conexão é simplesmente fechada
						sc.socket().close();
						sc.close();
						key.cancel();
					}
				}

			}
		}

		// We remove the selected keys, because we've dealt
		// with them.
		keys.clear();
	}
}

This piece of code is simply handles new clients that wants to connect to the chat. So, a client makes a TCP connection to the server, and once it gets accepted, it sends data to the server following a simply text protocol, informing his id and asking to get registrated to the server. I handle this in the method processIncomingDataFromNewClient(key). I'm also keeping a map of clients and its connections in a data structure similar to a hashtable. I? doing that because I need to recover a client Id from a connection and a connection from a client Id. This is can be shown in: clientsMap.getClient(key). But the problem itself resides in the method processIncomingDataFromNewClient(key). There, I simply read the data that the client sent to me, validate it, and if it's ok, I send a message back to the client to tell that it is connected to the chat server. Here is a similar piece of code:

private boolean processIncomingDataFromNewClient(SelectionKey key){
	SocketChannel sc = (SocketChannel) key.channel();
	String connectionOrigin = sc.socket().getInetAddress() + ":" + sc.socket().getPort();

	int id = 0; //id of the client

	buf.clear();
	int bytesRead = 0;
	try {
		bytesRead = sc.read(buf);
		if(bytesRead<=0){
			System.out.println("Conexão fechada pelo: " + connectionOrigin);
			return false;
		}
		System.out.println("LOG: " + bytesRead + " bytes lidos de " + connectionOrigin);

		String msg = new String(buf.array(),0,bytesRead);

		// Do validations with the client sent me here
		// gets the client id

			}catch (Exception e) {
				e.printStackTrace();
				System.out.println("LOG: Oops. Cliente não conhece o protocolo. Fechando a conexão: " + connectionOrigin);
				System.out.println("LOG: Primeiros 10 caracteres enviados pelo cliente: " + msg); 
				return false;
			}
		}

	} catch (IOException e) {
		System.out.println("LOG: Erro ao ler dados da conexao: " + connectionOrigin);
		System.out.println("LOG: "+ e.getLocalizedMessage());
		System.out.println("LOG: Fechando a conexão...");

		return false;
	}

	// If it gets to here, the protocol is ok and we can add the client
	boolean inserted = clientsMap.addClient(key, id);
	if(!inserted){
		System.out.println("LOG: Não foi possível adicionar o cliente. Ou ele já está conectado ou já têm clientes demais. Id: " + id);
		System.out.println("LOG: Fechando a conexão: " + connectionOrigin);
		return false;
	}
	System.out.println("LOG: Novo cliente conectado! Enviando mesnsagem de confirmação. Id: " + id + " Conexao: " + connectionOrigin);

	/* Here is the error */
	sendMessage(id, "Servidor pet: connection accepted");

	System.out.println("LOG: Novo cliente conectado! Id: " + id + " Conexao: " + connectionOrigin);
	return true;
}

And finally, the method sendMessage(SelectionKey key) looks like this:

private void sendMessage(int destId, String msg) {
	Charset charset = Charset.forName("ISO-8859-1");
    CharBuffer charBuffer = CharBuffer.wrap(msg, 0, msg.length());
    ByteBuffer bf = charset.encode(charBuffer);

    //bf.flip();

    int bytesSent = 0;
    SelectionKey key = clientsMap.getClient(destId);

    SocketChannel sc = (SocketChannel) key.channel();

    try {
    	/
    	int total_bytes_sent = 0;
    	while(total_bytes_sent < msg.length()){
    		bytesSent = sc.write(bf);
    		total_bytes_sent += bytesSent;

    	}

    	System.out.println("LOG: Bytes enviados para o cliente " + destId + ": "+ total_bytes_sent + " Tamanho da mensagem: " + msg.length());
    } catch (IOException e) {
    	System.out.println("LOG: Erro ao mandar mensagem para: " + destId);
    	System.out.println("LOG: " + e.getLocalizedMessage());
    }
}

So, what is happening is that the server, when send a message, prints something like this:

LOG: Bytes sent to the client: 28 Size of the message: 28

So, it tells that it sent the data, but the chat client keeps blocking, waiting in the recv() method. So, the data never gets to it. When I close the server application, though, all the data appears in the client. I wonder why.

It is important to say that the client is in C and the server JAVA, and I'm running both in the same machine, an Ubuntu Guest in virtualbox under windows. I also run both under windows host and under linuxes hosts, and keep getting the same strange problem.

I'm sorry for the great lenght of this question, but I already searched a lot of places for an answer, found a lot of tutorials and questions, including here at StackOverflow, but coundn't find a reasonable explanation. I am really not liking this Java NIO, and i saw a lot of people complaining about it too. I am thinking that if I had done that in C it would have been a lot easier :-D

So, if someone could help me and even discuss this behavor, it would be great! :-)

Thanks everybody in advance,

Péterson

© Stack Overflow or respective owner

Related posts about java

Related posts about socket-programming