patternjavaMinor
Simple character-by-character peer-to-peer chat application
Viewed 0 times
simplechatapplicationcharacterpeer
Problem
I built this simple application to facilitate communication between two or more machines on a local area network. As each client starts up, it broadcasts and receives broadcast packets; allowing for the omission of inputting a peer's IP address. It then creates a new connection to each discovered peer (even to itself), and characters are updated on-the-fly on each client's interface. I am posting this to get constructive feedback on the coding style, modularization, and its usability.
LANChat.java
```
package lanchat;
import java.awt.Font;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class LANChat extends Frame implements Runnable {
// text output of all connections
private final TextArea textArea;
// broadcast and receive of UDP; used for TCP connection(s) to peer(s)
private final Broadcasts broadcasts;
// list of all sockets for TCP output
private final ArrayList sockets;
// storage for text data
private StringBuilder lines;
// continue running application?
private boolean run = true;
public LANChat() {
// create field objects
sockets = new ArrayList<>();
lines = new StringBuilder();
textArea = new TextArea(20, 80);
// set focusable to false to ensure keys are captured by frame
textArea.setFocusable(false);
// monospace ftw
textArea.setFont(Font.decode("monospaced"));
// the only gui object is the text area
add(textArea);
pack();
// start socket server to accept incoming connections
new Thread(this).start();
// instantiate and assign window listener and key listener to frame
FrameListener frameListe
LANChat.java
```
package lanchat;
import java.awt.Font;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
public class LANChat extends Frame implements Runnable {
// text output of all connections
private final TextArea textArea;
// broadcast and receive of UDP; used for TCP connection(s) to peer(s)
private final Broadcasts broadcasts;
// list of all sockets for TCP output
private final ArrayList sockets;
// storage for text data
private StringBuilder lines;
// continue running application?
private boolean run = true;
public LANChat() {
// create field objects
sockets = new ArrayList<>();
lines = new StringBuilder();
textArea = new TextArea(20, 80);
// set focusable to false to ensure keys are captured by frame
textArea.setFocusable(false);
// monospace ftw
textArea.setFont(Font.decode("monospaced"));
// the only gui object is the text area
add(textArea);
pack();
// start socket server to accept incoming connections
new Thread(this).start();
// instantiate and assign window listener and key listener to frame
FrameListener frameListe
Solution
Separation of responsibilities
It would be better if
Currently it's a GUI, and it also manages its peers (the sockets).
It would be better to split these responsibilities to multiple classes.
Don't
It's not cool to
It would be better to reorganize the code in a way that when your main logic is finished,
and you cleaned up all resources (close sockets, file handles),
the program would naturally exit.
Bug: skipping sockets
There's a bug here:
When you iterate over the indexes, for example 1, 2, 3, 4, 5,
and at
but the loop counter still advances to
The original item-4 is now item-3 since the original item-3 was removed.
In other words, the socket after a removed socket will be skipped.
Here's one way to fix that:
Avoid magic numbers
Although the comment (sort of) explains that 8 is the backspace character,
it would be better to use the more idiomatic
If
you could even define a
It would be better if
LANChat was in charge of less stuff.public class LANChat extends Frame implements Runnable {Currently it's a GUI, and it also manages its peers (the sockets).
It would be better to split these responsibilities to multiple classes.
Don't
System.exitIt's not cool to
System.exit.It would be better to reorganize the code in a way that when your main logic is finished,
and you cleaned up all resources (close sockets, file handles),
the program would naturally exit.
Bug: skipping sockets
There's a bug here:
for (i = 0; i < sockets.size(); i++) {
try {
Socket s = sockets.get(i);
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.print(String.valueOf(ke.getKeyChar()));
pw.flush();
} catch (IOException ex) {
// remove socket, continue to any next if exception occurs
// (socket closed)
ex.printStackTrace();
sockets.remove(i);
continue;
}
}When you iterate over the indexes, for example 1, 2, 3, 4, 5,
and at
i=3 you remove a socket (sockets.remove(3)),but the loop counter still advances to
i=4.The original item-4 is now item-3 since the original item-3 was removed.
In other words, the socket after a removed socket will be skipped.
Here's one way to fix that:
List toRemove = new LinkedList<>();
for (Socket s : sockets) {
try {
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.print(String.valueOf(ke.getKeyChar()));
pw.flush();
} catch (IOException ex) {
ex.printStackTrace();
toRemove.add(s);
}
}
sockets.removeAll(toRemove);Avoid magic numbers
// check for backspace and space for delete, [...]
if (ch == 8 && lines.length() > 0)Although the comment (sort of) explains that 8 is the backspace character,
it would be better to use the more idiomatic
'\b' instead.If
'\b' is not obviously backspace to some readers,you could even define a
BACKSPACE constant for it.Code Snippets
public class LANChat extends Frame implements Runnable {for (i = 0; i < sockets.size(); i++) {
try {
Socket s = sockets.get(i);
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.print(String.valueOf(ke.getKeyChar()));
pw.flush();
} catch (IOException ex) {
// remove socket, continue to any next if exception occurs
// (socket closed)
ex.printStackTrace();
sockets.remove(i);
continue;
}
}List<Socket> toRemove = new LinkedList<>();
for (Socket s : sockets) {
try {
PrintWriter pw = new PrintWriter(s.getOutputStream());
pw.print(String.valueOf(ke.getKeyChar()));
pw.flush();
} catch (IOException ex) {
ex.printStackTrace();
toRemove.add(s);
}
}
sockets.removeAll(toRemove);// check for backspace and space for delete, [...]
if (ch == 8 && lines.length() > 0)Context
StackExchange Code Review Q#91675, answer score: 5
Revisions (0)
No revisions yet.