We made socket chatting program by Java. But we have a problem that happens when there are 2 or more chatting members. If there are members named A, B, and C(entered by alphabet order), when A (or B) left the room, the last member(C) left the room with A(or B) without error. If there is member D, the prior member leaves the room, the final member(this case member D) leaves the room with prior member without error. How can we solve it?
//Process that error happened//
A enters > B enters > C enters > D enters > A or B or C leaves(D leaves too why???) Only the last member leaves with prior member
Server
public class Server {
public static final int PORT = 7777;
private ServerSocket serverSocket;
private Socket socket;
private ServerGUI gui;
private String msg;
private int count = 0;
private int NMC=0;
private Map<String, DataOutputStream> clientsMap = new HashMap<String, DataOutputStream>();
public final void setGUI(ServerGUI gui) {
this.gui = gui;
}
public void setting() throws IOException {
Collections.synchronizedMap(clientsMap);
serverSocket = new ServerSocket(PORT);
String hostAddress = InetAddress.getLocalHost().getHostAddress();
while (true) {
System.out.println("waiting connection - " + hostAddress + ":" + PORT);
socket = serverSocket.accept();
System.out.println("connected from" + socket.getInetAddress());
Receiver receiver = new Receiver(socket);
receiver.start();
}
}
public void addClient(String name, DataOutputStream out) throws IOException {
clientsMap.put(name, out);
sendMessage(name + "entered\n");
gui.appendMsg(name + "entered\n");
count++;
gui.appendMsg("joining member : " + count + "\n");
}
public void sendMessage(String msg) {
Iterator<String> member = clientsMap.keySet().iterator();
String key = "";
while (member.hasNext()) {
key = member.next();
try {
clientsMap.get(key).writeUTF(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Receiver extends Thread {
private DataInputStream in;
private DataOutputStream out;
private String name;
public Receiver(Socket socket) throws IOException {
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
name = in.readUTF();
if (count != 0) {
NMC=NameMatch(name);
}
if (NMC == 1 || count == 0) {
addClient(name, out);
} else {
out.writeUTF("not available");
socket.close();
out.close();
in.close();
}
}
public void run() {
try {
while (in != null) {
msg = in.readUTF();
sendMessage(msg);
gui.appendMsg(msg);
}
} catch (IOException e) {
try {
if (NMC == 0) {
socket.close();
in.close();
out.close();
NMC++;
} else {
socket.close();
in.close();
out.close();
clientsMap.remove(name);
count--;
gui.appendMsg(name + "has left.\n");
gui.appendMsg("joining member : " + count + "\n");
sendMessage(name + "has left.\n");
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
public int NameMatch(String name) {
Iterator<String> member = clientsMap.keySet().iterator();
String key = "";
while (member.hasNext()) {
int i = 0;
if (i == count) {
break;
}else{
key = member.next();
if (key.equals(name)) {
return 0;
}
}
i++;
}
return 1;
}
public static void main(String[] args) throws IOException {
Server server = new Server();
server.setting();
}
}
''' Client
package chat.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import javax.swing.JOptionPane;
public class Client {
private Socket socket;
private DataInputStream in;
private DataOutputStream out;
private ClientGUI gui;
private String msg;
private String Name;
private int PORT = 0;
private String IP = "";
public final void setGUI(ClientGUI gui) {
this.gui = gui;
}
class connect extends Thread {
public connect() {
try {
socket = new Socket(IP, PORT);
System.out.println("Connected.");
out = new DataOutputStream(socket.getOutputStream());
in = new DataInputStream(socket.getInputStream());
out.writeUTF(Name);
if (in.readUTF().equals("not available")) {
out.close();
in.close();
socket.close();
JOptionPane.showMessageDialog(null, "not available");
System.exit(1);
}
} catch (IOException e) {
e.printStackTrace();
}
}
public void run() {
try {
while (in != null) {
msg = in.readUTF();
gui.appendMsg(msg);
}
} catch (IOException e) {
try {
out.close();
in.close();
socket.close();
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException {
Client client = new Client();
client.setConnect();
}
public void setConnect() {
connect c = new connect();
c.start();
}
public void sendMessage(String msg) {
try {
out.writeUTF(msg);
} catch (IOException e) {
e.printStackTrace();
}
}
public void setNickname(String Name) {
this.Name = Name;
}
public void Port(int port) {
this.PORT = port;
}
public void Ip(String ip) {
this.IP = ip;
}
}
In the Server.Receiver
class, you don't keep a reference to the client socket of the connection. This means that when the run
method closes the socket with:
socket.close();
it is actually using the socket
variable from the containing Server
class, which happens to be the connection of the latest user that has connected. This means that when any of A, B, or C leaves, D gets disconnected.
You can fix this by simply removing the call to socket.close()
from the Receiver.run
method. The socket is closed when you close the input or output stream associated with it.
Tips to avoid similar bugs in the future:
Avoid using instance variables whenever you can. If Server.socket
was a local variable, the error would have been clear from the start because there wouldn't have been a "socket" variable for you to use in the first place.
Socket socket = serverSocket.accept();
System.out.println("connected from" + socket.getInetAddress());
Receiver receiver = new Receiver(socket);
When you create nested classes, prefer make them static
at first:
static class Receiver extends Thread {
This prevents accidentally using the enclosing object instance. You can always remove the "static" modifier when you find that you need to use the enclosing object instance.