I've been working on a NIO-based
chat application of quite trivial logic: any message sent by any client should be visible to the rest of the users.
Right now, I'm sort of in the middle of the work, I've got pretty complete classes of the clients (and their GUI
part) and the server but I've stumbled on a problem I couldn't find any solution on anywhere. Namely, if I run an instance of the server and one instance of the client, in my consoles (one for client, one for the server) I see a nice, expected conversation. However, after adding additional client, this newly created client doesn't get responses from the server - the first still has a valid connection.
I'm not thinking about broadcasting messages to all the clients yet, now I'd like to solve the problem of the lack of proper communication between each of my clients and the server since, I think that broadcasting shouldn't be so big a deal if the communication is fine.
I'd like to also add that I've tried many other ways of instantiating the clients: in one thread, firstly instantiating the clients then applying methods on them, I've event tried using invokeLater
from SwingUtilities
, since that's the proper way to boot up GUI
. Sadly, neither worked.
What should I change to achieve proper communication between clients and the server? What am I doing wrong?
This is the log from client console:
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
And the log from server side console:
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.
The console output clearly shows that the server receives acceptable keys from both clients but it suggest also that only one SocketChannel
has a SelectionKey
of readable type, but I've got no clue why. Moreover, I think that the order of creating the clients doesn't matter because as I tested: the client that talks properly with the server is always the one that starts communication as first.
Below I'm posting my Server
and Client
classes code, hoping You'll Guys help me sort it out.
Firstly, Server
class:
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);
}
}
}
and the Client
class:
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();
}
}
I'll apprecaite any help from You Guys.
EDIT:
the second version of writeResp
method attempting to broadcast echo to all the clients produces such log:
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
It seems like there are two clients and I'm wondering why they don't get proper reply from the server.