Java NIO - SocketChannel.write() на сервере много раз, но клиент получает результат только один раз

Я выполняю упражнение, требующее создания программы чата между сервером и клиентом с использованием Java Non-Blocking IO. На данный момент программа работает просто: когда клиент отправляет сообщение на сервер, сервер (который уже отслеживает всех клиентов) отправляет сообщение обратно всем клиентам.

Это мои некоторые части моего кода на стороне сервера:

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();
        }
    }
}

Ниже приведен мой клиентский код:

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();
            }
        }
    }
}

Проблема, с которой я сталкиваюсь сейчас, заключается в том, что когда я использую клиент (который хорошо работает со старым multithreaded server) для отправки сообщения на сервер, клиент возвращает только первое сообщение, которое он отправляет. Все последующие ответы от сервера пустые (сервер отправляет обратно, но только пустое сообщение).

Итак, мои попытки отладки включают:

  • проверьте, доходят ли сообщения от клиента до сервера или нет. Они делают.
  • запишите результаты buffer.remaining() и socketChannel.write(buffer), как показано выше, все результаты журнала кажутся мне нормальными.

Надеюсь, вы, ребята, можете помочь мне с этим.


person sonlexqt    schedule 06.07.2015    source источник
comment
вы уверены, что правильно читаете на клиенте?   -  person WorldSEnder    schedule 06.07.2015
comment
Этот же клиент хорошо работает со старым многопоточным сервером, и клиент может отображать именно первый ответ от сервера :/   -  person sonlexqt    schedule 06.07.2015
comment
Что такое encoder и clientSocketChannels?   -  person WorldSEnder    schedule 06.07.2015
comment
encoder находится здесь: из public static Charset charset = Charset.forName("UTF-8"); public static CharsetEncoder encoder = charset.newEncoder(); public static CharsetDecoder decoder = charset.newDecoder(); (я копирую его из этого вопроса: stackoverflow.com/questions/1252468/), а clientSocketChannels - это ArrayList всех SocketChannels (каждый SocketChannel относится к одному клиенту, который подключается к серверу )   -  person sonlexqt    schedule 06.07.2015
comment
В TCP нет такого понятия, как пустое сообщение. В опубликованном вами коде указано, что было записано 2048 байт. Следовательно проблема в клиенте. Разместите код.   -  person user207421    schedule 06.07.2015
comment
@EJP Спасибо, я только что обновил свой клиентский код. Пожалуйста, посмотрите :)   -  person sonlexqt    schedule 06.07.2015
comment
Вы пробовали сбрасывать сообщение после каждой записи? sc.write(bb); sc.flush();   -  person Zymus    schedule 06.07.2015
comment
@Zymus нет метода очистки для объекта SocketChannel   -  person sonlexqt    schedule 06.07.2015
comment
Я полагаю, это потому, что сканер ищет nextLine, но сообщение, которое вы отправляете, не имеет разделителя строк в конце. Попробуйте сделать CharBuffer.wrap(msg + "\r\n") в своем методе str_to_bb.   -  person Zymus    schedule 07.07.2015


Ответы (2)


  1. Этот:

    if(inputStream.available() > 0){
    

    Избавьтесь от этого теста. С ним ваш клиент курит ЦП. Без него он заблокируется readLine(), как задумал Бог.

  2. Вы уверены, что ваш сервер все еще отправляет строки? с терминаторами строк? Если это не так, readLine() навсегда заблокирует его поиск, пока не закончится поток или не произойдет исключение.

person user207421    schedule 07.07.2015

Я сослался на код, объясняемый EJP, по этой ссылке -socket">Приложение Java NIO Server/Client Chat — отправка данных только при закрытии сокета

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();
        }
    }
}

Я сделал ошибку, установив этот флаг key.interestOps(SelectionKey.OP_READ); ) вместо ниже. используйте это socketChannel.register(this.selector, SelectionKey.OP_WRITE|SelectionKey.OP_READ);

person Mayur Lokare    schedule 20.07.2017