1

I'm doing an exercise requires making a server - client chat program using Java Non-Blocking IO. At the moment, the way the program works is simple: when a client send a message to the server, the server (which already keep track of all the clients) echo the message back to all the clients.

This is my some parts of my server-side code:

public static ByteBuffer str_to_bb(String msg) {
    try {
        return encoder.encode(CharBuffer.wrap(msg));
    } catch(Exception e) {
        e.printStackTrace();
    }
    return null;
}

private static void broadcastMessage(String nickname, String message) {
    System.out.println(">clientSocketChannels size " + clientSocketChannels.size());
    Iterator clientSocketChannelsIterator = clientSocketChannels.iterator();
    while (clientSocketChannelsIterator.hasNext()) {
        SocketChannel sc = (SocketChannel) clientSocketChannelsIterator.next();
        try {
            ByteBuffer bb = str_to_bb(message);
            System.out.println("bufferRemaining: " + bb.remaining()); // returns 2048
            int writeResult = sc.write(bb);
            System.out.println("writeResult: " + writeResult); // returns 2048
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

The following is my client-side code:

import javax.swing.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

/**
 * Created by ThaiSon on 7/6/2015.
 */
public class ChatRoomClientGUI {
    private JTextArea textAreaMessages;
    private JTextField textFieldMessage;
    private JButton buttonSendMsg;
    private JPanel jPanel1;
    private JLabel txtFieldInfo;

    private static InetAddress inetAddress;
    private static final int PORT = 1234;
    private static Socket socket = null;
    private static Scanner input = null;
    private static PrintWriter output = null;

    private static ChatRoomClientGUI singleton;

    public ChatRoomClientGUI() {
        singleton = this;
        buttonSendMsg.addMouseListener(new MouseAdapter() {
            @Override
            public void mouseClicked(MouseEvent e) {
                super.mouseClicked(e);
                if (e.getButton() == MouseEvent.BUTTON1) {
                    String message = textFieldMessage.getText();
                    output.println(message);
                    textFieldMessage.setText("");
                }
            }
        });
    }

    public static void main(String[] args) {
        JFrame promptFrame = new JFrame();
        Object nickname = JOptionPane.showInputDialog(promptFrame, "Enter your nickname:");
        promptFrame.dispose();

        JFrame frame = new JFrame("ChatRoomClientGUI");
        frame.setContentPane(new ChatRoomClientGUI().jPanel1);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);

        System.out.println("> Client with nickname " + nickname);

        try {
            inetAddress = InetAddress.getLocalHost();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        accessServer(nickname.toString());
    }

    private static void accessServer(String nickname) {
        try {
            socket = new Socket(inetAddress, PORT);
            input = new Scanner(socket.getInputStream());
            output = new PrintWriter(socket.getOutputStream(), true);
            output.println(nickname); // Register nickname with the server

            //TODO update the txtFieldInfo content

            // Create a new thread to listen to InputStream event
            InputStreamEvent inputStreamEvent = new InputStreamEvent(socket);
            inputStreamEvent.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static void handleInputStream(){
        String response = input.nextLine();
        System.out.println("TODO " + response);
        singleton.textAreaMessages.append(response + "\n");
    }

    static class InputStreamEvent extends Thread{
        Socket socket;
        public InputStreamEvent(Socket socket){
            this.socket = socket;
        }
        public void run(){
            try {
                InputStream inputStream = socket.getInputStream();
                byte[] buffer = new byte[2048];
                int read;
                while (true){
                    if(inputStream.available() > 0){
                        handleInputStream();
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

The problem I'm facing with now is that when I use a client (which works well with the old multithreaded server) to send message to the server, the client only get return the first message it sends. All the next responses from the server is empty (the server does send back, but only an empty message).

So my attempts to debug includes:

  • check if the messages from the client has reach the server or not. They does.
  • log the buffer.remaining() and socketChannel.write(buffer) result as shown above, all the log result seems to be normal to me.

Hope you guys can help me with this.

sonlexqt
  • 6,011
  • 5
  • 42
  • 58
  • are you sure you are reading correctly on the client? – WorldSEnder Jul 06 '15 at 17:18
  • The same client works well with the old multithreaded server, and the client can display exactly the first response from the server :/ – sonlexqt Jul 06 '15 at 17:25
  • What is `encoder` and `clientSocketChannels`? – WorldSEnder Jul 06 '15 at 17:26
  • `encoder` is here: from `public static Charset charset = Charset.forName("UTF-8"); public static CharsetEncoder encoder = charset.newEncoder(); public static CharsetDecoder decoder = charset.newDecoder();` (I copy it from this question: http://stackoverflow.com/questions/1252468/java-converting-string-to-and-from-bytebuffer-and-associated-problems) and clientSocketChannels is an ArrayList of all SocketChannels (each SocketChannel is of one client that is connecting to the server) – sonlexqt Jul 06 '15 at 17:30
  • There is no such thing as an empty message in TCP. The code you've posted indicates that 2048 bytes were written. *Ergo* the problem is in the client. Post the code. – user207421 Jul 06 '15 at 19:43
  • @EJP Thanks, I've just udated my client-side code. Please have a look :) – sonlexqt Jul 06 '15 at 20:25
  • Have you tried flushing the message after each write? `sc.write(bb); sc.flush();` – Zymus Jul 06 '15 at 20:29
  • @Zymus there's no method flush for a SocketChannel object – sonlexqt Jul 06 '15 at 20:47
  • 1
    I believe it's because the scanner is looking for the `nextLine` but the message you're sending does not have a line separator at the end. Try doing `CharBuffer.wrap(msg + "\r\n")` in your `str_to_bb` method. – Zymus Jul 06 '15 at 21:17

2 Answers2

2
  1. This:

    if(inputStream.available() > 0){
    

    Get rid of this test. With it, your client is smoking the CPU. Without it, it will block in readLine() as God intended.

  2. Are you sure your server is still sending lines? with line terminators? If it isn't, readLine() will block forever looking for one, until end of stream or an exception occurs.

user207421
  • 305,947
  • 44
  • 307
  • 483
0

I referred the code explain by EJP on this link Java NIO Server/Client Chat App - sending data only by closing the socket

it solves my problem. use this code

import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.ByteBuffer;
import java.io.IOException;
import java.util.Scanner;
import java.nio.channels.SelectionKey;
import java.net.InetSocketAddress;
public class Client {
    public static void main(String args[]) {
        try {
            ByteBuffer buf = ByteBuffer.allocate(200);
            Scanner scanner = new Scanner(System.in);
            Selector selector = Selector.open();
            SocketChannel socketChannel = SocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.register(selector, SelectionKey.OP_CONNECT|SelectionKey.OP_READ|SelectionKey.OP_WRITE);
            boolean isConnected = socketChannel.connect(new InetSocketAddress("localhost", 5000));
            if(isConnected) {
                System.out.println("Connected, de-registering OP_CONNECT");
            }
            new Thread(new Runnable(){
                private SocketChannel socketChannel;
                private Selector selector;
                public Runnable init(SocketChannel socketChannel, Selector selector) {
                    this.socketChannel = socketChannel;
                    this.selector = selector;
                    return this;
                }
                public void run() {
                    try {
                        ByteBuffer buf = ByteBuffer.allocate(200);
                        while(!Thread.interrupted()) {
                            int keys = selector.select();
                            if(keys > 0) { 
                                for(SelectionKey key : selector.selectedKeys()) {
                                    if(key.isConnectable()) {
                                        boolean finishConnectResult = socketChannel.finishConnect();
                                        socketChannel.register(this.selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);
                                        System.out.println("Finished Connect : " + finishConnectResult);
                                    }
                                    if(key.isReadable()) {
                                        int bytesRead = 0;
                                        while((bytesRead = socketChannel.read(buf)) > 0) {
                                            buf.flip();
                                            while(buf.hasRemaining()) {
                                                System.out.print((char)buf.get());
                                            }
                                            buf.clear();
                                        }
                                        if(bytesRead == -1) {
                                            key.channel().close();
                                        }
                                    }
                                }
                            }
                            Thread.sleep(10);
                        }
                    } catch(IOException e) {
                        e.printStackTrace();
                    } catch(InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }.init(socketChannel, selector)).start();

            while(true) {
                while(scanner.hasNextLine()) {
                    buf.clear();
                    buf.put(scanner.nextLine().getBytes());
                    buf.flip();
                    socketChannel.write(buf);
                    buf.flip();
                }
            }
        } catch(IOException e) {
            e.printStackTrace();
        }
    }
}

I have done the mistake setting this flag key.interestOps(SelectionKey.OP_READ); ) instead of below. use this socketChannel.register(this.selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);