Java NIO聊天应用程序无法为多个客户端正常工作
我一直在开发一个基于NIO的聊天应用程序,其逻辑非常简单:任何客户端发送的任何消息都应该对其他用户可见Java NIO聊天应用程序无法为多个客户端正常工作,java,nio,socketchannel,Java,Nio,Socketchannel,我一直在开发一个基于NIO的聊天应用程序,其逻辑非常简单:任何客户端发送的任何消息都应该对其他用户可见 现在,我在工作的中间,我有相当完整的客户端类(以及他们的代码> GUI 部分)和服务器,但是我遇到了一个问题,我在任何地方都找不到解决方案。也就是说,如果我运行一个服务器实例和一个客户端实例,在我的控制台(一个用于客户端,一个用于服务器)中,我会看到一个很好的预期对话。但是,在添加额外的客户端之后,这个新创建的客户端不会从服务器获得响应——第一个客户端仍然具有有效的连接 我还没有考虑向所有客户
现在,我在工作的中间,我有相当完整的客户端类(以及他们的代码> GUI <代码>部分)和服务器,但是我遇到了一个问题,我在任何地方都找不到解决方案。也就是说,如果我运行一个服务器实例和一个客户端实例,在我的控制台(一个用于客户端,一个用于服务器)中,我会看到一个很好的预期对话。但是,在添加额外的客户端之后,这个新创建的客户端不会从服务器获得响应——第一个客户端仍然具有有效的连接
我还没有考虑向所有客户机广播消息,现在我想解决我的每个客户机和服务器之间缺乏适当通信的问题,因为我认为如果通信良好,广播不应该有什么大不了的 我还想补充一点,我已经尝试了许多其他实例化客户机的方法:在一个线程中,首先实例化客户机,然后在客户机上应用方法,我尝试了使用invokeLater
fromSwingUtilities
,因为这是启动GUI
的正确方法。不幸的是,这两种方法都不奏效
我应该改变什么来实现客户端和服务器之间的正确通信?我做错了什么
这是来自客户端控制台的日志:
Awaiting message from: client2...
Awaiting message from: client1...
after creating the clients - before any action
1 Message: client1 :: simpleMess1
2 started pushing message from: client1
3 Server response on client side: ECHO RESPONSE: client1 :: simpleMess1
4 Message: client2 :: simpleMessage from c2
5 started pushing message from: client2
6
7 -- No response from client2. AND next try from client2 shows no log at all (!)
8
9 Message: client1 :: simple mess2 from c1
10 started pushing message from: client1
11 Server response on client side: ECHO RESPONSE: client1 :: simpleMess1
以及服务器端控制台中的日志:
1 Server started...
2 S: Key is acceptable
3 S: Key is acceptable
4
5 -- after creating the clients before any action
6 S: Key is readable.
控制台输出清楚地显示服务器从两个客户端接收可接受的密钥,但它也表明只有一个SocketChannel
具有可读类型的SelectionKey
,但我不知道为什么。此外,我认为创建客户机的顺序并不重要,因为正如我测试的那样:与服务器正常对话的客户机总是首先开始通信的客户机。
下面我发布了我的服务器
和客户端
类代码,希望你们能帮我整理一下
首先,服务器
类:
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
public class Server {
private ServerSocketChannel serverSocketChannel = null;
private Selector selector = null;
private StringBuffer messageResponse = new StringBuffer();
private static Charset charset = Charset.forName("ISO-8859-2");
private static final int BSIZE = 1024;
private ByteBuffer byteBuffer = ByteBuffer.allocate(BSIZE);
private StringBuffer incomingClientMessage = new StringBuffer();
Set<SocketChannel> clientsSet = new HashSet<>();
public Server(String host, int port) {
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(host, port));
selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
catch (Exception exc) {
exc.printStackTrace();
System.exit(1);
}
System.out.println("Server started...");
serviceConnections();
}
private void serviceConnections() {
boolean serverIsRunning = true;
while (serverIsRunning) {
try {
selector.select();
Set keys = selector.selectedKeys();
Iterator iter = keys.iterator();
while (iter.hasNext()) {
SelectionKey key = (SelectionKey) iter.next();
iter.remove();
if (key.isAcceptable()) {
System.out.println("\tS: Key is acceptable");
SocketChannel incomingSocketChannel = serverSocketChannel.accept();
incomingSocketChannel.configureBlocking(false);
incomingSocketChannel.register(selector, SelectionKey.OP_READ);
clientsSet.add(incomingSocketChannel);
continue;
}
if (key.isReadable()) {
System.out.println("\tS: Key is readable.");
SocketChannel incomingSocketChannel = (SocketChannel) key.channel();
serviceRequest(incomingSocketChannel);
continue;
}
}
}
catch (Exception exc) {
exc.printStackTrace();
continue;
}
}
}
private void serviceRequest(SocketChannel sc) {
if (!sc.isOpen()) return;
incomingClientMessage.setLength(0);
byteBuffer.clear();
try {
while (true) {
int n = sc.read(byteBuffer);
if (n > 0) {
byteBuffer.flip();
CharBuffer cbuf = charset.decode(byteBuffer);
while (cbuf.hasRemaining()) {
char c = cbuf.get();
if (c == '\r' || c == '\n') break;
incomingClientMessage.append(c);
}
}
writeResp(sc, "ECHO RESPONSE: " + incomingClientMessage.toString());
}
}
catch (Exception exc) {
exc.printStackTrace();
try {
sc.close();
sc.socket().close();
}
catch (Exception e) {
}
}
}
private void writeResp(SocketChannel sc, String addMsg)
throws IOException {
messageResponse.setLength(0);
messageResponse.append(addMsg);
messageResponse.append('\n');
ByteBuffer buf = charset.encode(CharBuffer.wrap(messageResponse));
sc.write(buf);
}
//second version - with an attempt to acomlish broadcasting
private void writeResp(SocketChannel sc, String addMsg)
throws IOException {
messageResponse.setLength(0);
messageResponse.append(addMsg);
messageResponse.append('\n');
ByteBuffer buf = charset.encode(CharBuffer.wrap(messageResponse));
System.out.println("clientsSet: " + clientsSet.size());
for (SocketChannel socketChannel : clientsSet) {
System.out.println("writing to: " + socketChannel.getRemoteAddress());
socketChannel.write(buf);
buf.rewind();
}
}
public static void main(String[] args) {
try {
final String HOST = "localhost";
final int PORT = 5000;
new Server(HOST, PORT);
}
catch (Exception exc) {
exc.printStackTrace();
System.exit(1);
}
}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class Client {
private ClientView clientView;
private String hostName;
private int port;
private String clientName;
private Socket socket = null;
private PrintWriter printWriterOUT = null;
private BufferedReader bufferedReaderIN = null;
public Client(String hostName, int port, String clientName) {
this.hostName = hostName;
this.port = port;
this.clientName = clientName;
initView();
}
public void handleConnection() {
try {
socket = new Socket(hostName, port);
printWriterOUT = new PrintWriter(socket.getOutputStream(), true);
bufferedReaderIN = new BufferedReader(new InputStreamReader(socket.getInputStream()));
waitForIncomingMessageFromClientView();
bufferedReaderIN.close();
printWriterOUT.close();
socket.close();
}
catch (UnknownHostException e) {
System.err.println("Unknown host: " + hostName);
System.exit(2);
}
catch (IOException e) {
System.err.println("I/O err dla");
System.exit(3);
}
catch (Exception exc) {
exc.printStackTrace();
System.exit(4);
}
}
public void initView() {
clientView = new ClientView(clientName);
}
public void waitForIncomingMessageFromClientView() {
System.out.println("Awaiting message from: " + clientName + "...");
while (true) {
if (clientView.isSent) {
System.out.println("Message: " + clientView.getOutgoingMessage());
pushClientViewMessageToServer();
clientView.setIsSent(false);
}
}
}
public void pushClientViewMessageToServer() {
String clientViewMessage = clientView.getOutgoingMessage();
System.out.println("started pushing message from: " + clientView.getClientName());
try {
printWriterOUT.println(clientViewMessage);
String resp = bufferedReaderIN.readLine();
System.out.println("Server response on client side: " + resp);
}
catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
Client c1 = new Client("localhost", 5000, "client1");
c1.handleConnection();
}
});
thread1.start();
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
Client c2 = new Client("localhost", 5000, "client2");
c2.handleConnection();
}
});
thread2.start();
}
}
我会感谢你们的帮助
编辑:
第二个版本的writeResp
方法试图向所有客户端广播echo,会生成这样的日志:
Server started...
clientsSet: 2
writing to: /127.0.0.1:63666
writing to: /127.0.0.1:63665
clientsSet: 2
writing to: /127.0.0.1:63666
writing to: /127.0.0.1:63665
似乎有两个客户端,我想知道为什么他们没有从服务器得到正确的回复
while (true) {
int n = sc.read(byteBuffer);
if (n > 0) {
byteBuffer.flip();
CharBuffer cbuf = charset.decode(byteBuffer);
while (cbuf.hasRemaining()) {
char c = cbuf.get();
if (c == '\r' || c == '\n') break;
incomingClientMessage.append(c);
}
}
这里有一个大问题。如果
read()
返回-1,你应该关闭SocketChannel
,如果它返回-1或零,你应该跳出循环。非常感谢你,所以在If
语句之后,我应该放置一个else
子句,像这样关闭频道:sc.close()
,是吗?但为什么我也要单独使用这个条件呢?{…?你们能告诉我它背后的逻辑的内部结构吗?叹气。若n<0
对等方已经关闭了连接,你们也应该这样做。若n<0
或n==0
,表示为n好的,若我理解正确:while(true){int n=sc.read(byteBuffer);if(n>0){byteBuffer.flip();CharBuffer cbuf=charset.decode(byteBuffer);while(cbuf.haslaining()){char c=cbuf.get();if(c='\r'| c='\n')break;incomingClientMessage.append(c);}else{/*如果n为(1)No,它将执行。请再次阅读我所写的内容。(2)您可以自己看到,在注释中发布的代码完全难以辨认。(2)很抱歉,我以前从未做过这件事,因为我认为不应该再做一次。(1)我已经读了好几遍了,我真的很抱歉,但是我没有理解正确:在while(true)
循环中,我应该添加两个条件来检查n
的大小,根据它的值,我应该执行sc.close()
还是break;
while
操作?