Java NIO - SocketChannel.write() 在服务器上多次但客户端只收到一次结果

Java NIO - SocketChannel.write() on server many times but Client only receive result once

我正在做一个练习,需要使用 Java Non-Blocking IO 制作一个 服务器 - 客户端聊天程序 。目前,程序的工作方式很简单:当客户端向服务器发送消息时,服务器(已经跟踪所有客户端)echo 将消息返回给所有客户。

这是我的服务器端代码的一部分:

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 配合良好)向服务器发送消息时,客户端仅获得 return 它发送的第一个 消息。服务器接下来的所有响应都是空的(服务器确实发回了,但只是一个空消息)。

所以我的调试尝试包括:

希望大家能帮帮我。

  1. 这个:

    if(inputStream.available() > 0){
    

    去掉这个测试。有了它,您的客户正在抽 CPU。没有它,它将按照上帝的意愿阻塞 readLine()

  2. 您确定您的服务器仍在发送线路吗?用行终止符?如果不是,readLine() 将永远阻止寻找一个,直到流结束或发生异常。

我参考了 EJP 的代码解释 link Java NIO Server/Client Chat App - sending data only by closing the socket

它解决了我的问题。使用此代码 </p> <pre><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);