I am building a chat system in Java and I'm considering two different approaches for handling communications:
SocketChannel with a While Loop - The default way, from what I know not so efficient, but easy to build and read.
Event-Driven Model - From what I know, outperforming the default approach, but harder to implement and maintain.
Each chat server will host a maximum of 10 clients, so nothing really populated. I am trying to keep my chat system simple and functional and I am not sure if implementing the Event-Driven Model worth it.
My current server looks like this:
package Chat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class SimpleServerChannel {
private List<SocketChannel> clientChannels = new CopyOnWriteArrayList<>();
private List<Chat_Interface> clientInterfaces = new ArrayList<>();
public void start() {
try (CustomServerSocket serverChannel = new CustomServerSocket(SelectorProvider.provider(), clientChannels, clientInterfaces)) {
serverChannel.socket().bind(new InetSocketAddress(5000));
serverChannel.configureBlocking(false);
System.out.println("Server is listening on port " + serverChannel.socket().getLocalPort());
ByteBuffer buffer = ByteBuffer.allocate(1024);
clientChannels = serverChannel.getClientChannels();
clientInterfaces = serverChannel.getClientInterfaces();
while (true) {
SocketChannel clientChannel = serverChannel.accept();
Iterator<SocketChannel> iterator = clientChannels.iterator();
if (clientChannel != null) {
clientChannel.configureBlocking(false);
clientChannels.add(clientChannel);
System.out.printf("Client %s connected%n", clientChannel.socket().getRemoteSocketAddress());
}
while (iterator.hasNext()) {
SocketChannel client = iterator.next();
try {
buffer.clear();
int bytesRead = client.read(buffer);
if (bytesRead == -1) {
System.out.printf("Client %s disconnected%n", client.socket().getRemoteSocketAddress());
clientChannels.remove(client);
client.close();
} else if (bytesRead > 0) {
buffer.flip();
byte[] data = new byte[buffer.remaining()];
buffer.get(data);
String message = String.format("%s", new String((data), StandardCharsets.UTF_8));
for (SocketChannel otherClient : clientChannels) {
buffer.clear();
buffer.put((message).getBytes());
buffer.flip();
while (buffer.hasRemaining()) {
otherClient.write(buffer);
}
}
}
} catch (IOException e) {
System.out.printf("Client %s disconnected due to error%n", client.socket().getRemoteSocketAddress());
clientChannels.remove(client);
client.close();
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public List<SocketChannel> getClientChannels() {
return clientChannels;
}
public List<Chat_Interface> getClientInterfaces() {
return clientInterfaces;
}
}
My question is if I have to switch with the second approach, or for my needs(mentioned earlier), keeping this approach is enough for understanding low level network programming and build a small feature?
My question is if I have to switch with the second approach, or for my needs(mentioned earlier), keeping this approach is enough?
Without reviewing the code in detail, no, you do not have to change approach. By setting the channels to non-blocking, you avoid getting stuck trying to service a client that is unready while other clients have data waiting. Busy-looping through the clients, attempting to service each one in turn and being appropriately accepting of failures, can provide service to all clients. That for a certain number of clients and a certain data volume, which is probably more than you will saturate with 10 clients operated interactively by humans.
But that is dreadfully inefficient at the server, which is a consideration if the server has anything else to do at the same time (which yours apparently does). It also has latency issues that might become noticeable to human users even at modest load. And it scales poorly.
We can't judge in advance whether the system's performance will be good enough for you, both because
And we can't judge whether your approach is enough for your learning objectives, either, because, again, we don't know what those are, and, again, I bet you don't know exactly what those are either. Certainly I would expect you to learn more by implementing an approach based on Selector
s. I'm uncertain whether that's what you mean by an "event-driven model", but it is certainly among the better-performing, better-scaling, conventional approaches to I/O multiplexing such as you are trying to do.