demo.jpg
當要寫成網頁版本的時候改成 JApplet, 但是在瀏覽器關閉的時候, 並不能向 Server 發出關閉的訊息。
因此設置一個類似 Heart 的部分在 Client 端。
還沒解決的部分
1. 重複的 ID 進入聊天室。
2. 由於設置 Heart, 有個環節未能解決, 導致在某個時段登入會直接踢出。
MServer.java // Server 端
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.io.BufferedInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.AdjustmentListener;
import java.awt.event.AdjustmentEvent;
import java.lang.Thread;
public class MServer {
private JFrame frame;
private JTextArea contentArea;
private JTextField txt_msg;
private JTextField txt_port;
private JTextField txt_max;
private JButton btn_start;
private JButton btn_stop;
private JButton btn_send;
private JPanel northPanel;
private JPanel southPanel;
private JScrollPane rightPanel;
private JScrollPane leftPanel;
private JSplitPane centerSplit; // 分頁窗格
private JList userList;
private DefaultListModel listModel;
private ServerSocket serverSocket;
private ServerThread serverThread; // 自行定義的 class
private ArrayList<ClientThread> clients;
private final int ServerPort = 8765;
private boolean isStart = false;
public MServer() {
frame = new JFrame("MServer - Beta");
contentArea = new JTextArea();
contentArea.setEditable(false);
contentArea.setBackground(Color.black);
contentArea.setForeground(Color.green);
txt_msg = new JTextField();
txt_max = new JTextField("7");
txt_port = new JTextField("2047");
btn_start = new JButton("On Server");
btn_stop = new JButton("Off Server");
btn_send = new JButton("↵ ");
btn_stop.setEnabled(false);
listModel = new DefaultListModel();
userList = new JList(listModel);
southPanel = new JPanel(new BorderLayout());
southPanel.setBorder(new TitledBorder(""));
southPanel.add(txt_msg, BorderLayout.CENTER);
southPanel.add(btn_send, BorderLayout.EAST);
leftPanel = new JScrollPane(userList);
leftPanel.setBorder(new TitledBorder("Who's here"));
rightPanel = new JScrollPane(contentArea);
rightPanel.setBorder(new TitledBorder("Chat Area"));
rightPanel.getVerticalScrollBar().addAdjustmentListener(
new AdjustmentListener() {
int cnt = 0;
public void adjustmentValueChanged(AdjustmentEvent e) {
if(cnt == 3) {
JScrollBar sb = rightPanel.getVerticalScrollBar();
sb.setValue(sb.getMaximum());
cnt = 0;
}
cnt++;
}
}); // 自動置底
centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel,
rightPanel);
centerSplit.setDividerLocation(100);
northPanel = new JPanel();
northPanel.setLayout(new GridLayout(1, 6));
northPanel.add(new JLabel("User limit"));
northPanel.add(txt_max);
northPanel.add(new JLabel("Port"));
northPanel.add(txt_port);
northPanel.add(btn_start);
northPanel.add(btn_stop);
northPanel.setBorder(new TitledBorder("Setting"));
frame.setLayout(new BorderLayout());
frame.add(northPanel, BorderLayout.NORTH);
frame.add(southPanel, BorderLayout.SOUTH);
frame.add(centerSplit, BorderLayout.CENTER);
frame.setSize(800, 600);
int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;
int screen_height = Toolkit.getDefaultToolkit().getScreenSize().height;
frame.setLocation((screen_width - frame.getWidth()) / 2,
(screen_height - frame.getHeight()) / 2);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (isStart)
closeServer();
System.exit(0);
}
});
txt_msg.addActionListener(new ActionListener() { // enter touch
public void actionPerformed(ActionEvent e) {
send();
}
});
btn_send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
send();
}
});
btn_start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (isStart) {
JOptionPane.showMessageDialog(frame, "Server has opened",
"Error120", JOptionPane.ERROR_MESSAGE);
return;
}
int max, port;
try {
max = Integer.parseInt(txt_max.getText());
port = Integer.parseInt(txt_port.getText());
if (max < 0)
throw new Exception("max <= 0");
if (port <= 0)
throw new Exception("port <= 0");
openServer(max, port);
if (isStart) {
contentArea.append("System> Server open ! \r\n");
btn_start.setEnabled(false);
txt_max.setEditable(false);
txt_port.setEditable(false);
btn_stop.setEnabled(true);
}
} catch (Exception e3) {
JOptionPane.showMessageDialog(frame, "", "Error136",
JOptionPane.ERROR_MESSAGE);
}
}
});
btn_stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!isStart) {
JOptionPane.showMessageDialog(frame, "Server has closed",
"Error147", JOptionPane.ERROR_MESSAGE);
return;
}
try {
closeServer();
if (!isStart) {
btn_start.setEnabled(true);
txt_max.setEditable(true);
txt_port.setEditable(true);
btn_stop.setEnabled(false);
contentArea.append("System> Server close ! \r\n");
}
} catch (Exception e3) {
JOptionPane.showMessageDialog(frame, "", "Error158",
JOptionPane.ERROR_MESSAGE);
}
}
});
}
public void send() {
if (!isStart) {
JOptionPane.showMessageDialog(frame, "Server closed", "Error120",
JOptionPane.ERROR_MESSAGE);
return;
}
if (clients.size() == 0) {
JOptionPane.showMessageDialog(frame, "No user", "Error124",
JOptionPane.ERROR_MESSAGE);
return;
}
String msg = txt_msg.getText();
if (msg == null || msg.equals("")) {
JOptionPane.showMessageDialog(frame, "msg null", "Error128",
JOptionPane.ERROR_MESSAGE);
return;
}
msg = "System> " + msg + "\r\n";
sendServerMessage("SYSTEM@" + msg);
contentArea.append(msg);
txt_msg.setText(null);
}
public void sendServerMessage(String msg) {
for (int i = clients.size() - 1; i >= 0; i--) {
clients.get(i).getWriter().println(msg);
clients.get(i).getWriter().flush();
}
}
public void openServer(int max, int port) throws java.net.BindException {
try {
clients = new ArrayList<ClientThread>();
serverSocket = new ServerSocket(port);
serverThread = new ServerThread(serverSocket, max);
serverThread.start();
isStart = true;
} catch (java.net.BindException e) {
isStart = false;
throw new java.net.BindException(
"port used, please use another port");
} catch (Exception e2) {
e2.printStackTrace();
isStart = false;
throw new java.net.BindException("unknown exception");
}
}
@SuppressWarnings("deprecation")
public void closeServer() {
try {
if (serverThread != null)
serverThread.stop();
for (int i = 0; i < clients.size(); i++) {
ClientThread tmp = clients.get(i);
tmp.socket.setSoTimeout(3000);
tmp.getWriter().println("CLOSE");
tmp.getWriter().flush();
tmp.stop();
tmp.bin.close();
tmp.wout.close();
tmp.socket.close();
clients.remove(i);
i--;
}
if (serverSocket != null)
serverSocket.close();
listModel.removeAllElements();
isStart = false;
} catch (Exception e) {
e.printStackTrace();
isStart = true;
}
}
class ServerThread extends Thread {
private ServerSocket serverSocket;
private int max;
public ServerThread(ServerSocket serverSocket, int max) {
this.serverSocket = serverSocket;
this.max = max;
}
public void run() {
while (true) {
try {
Socket socket = serverSocket.accept();
if (clients.size() == max) {
BufferedReader r = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter w = new PrintWriter(
socket.getOutputStream());
String inf = r.readLine();
StringTokenizer st = new StringTokenizer(inf, "@");
User user = new User(st.nextToken(), st.nextToken());
w.println("MAX@Sorry, " + user.getName()
+ "! Server Busy. Waiting... \r\n");
w.flush();
r.close();
socket.close();
continue;
}
ClientThread client = new ClientThread(socket);
client.start();
clients.add(client);
listModel.addElement(client.getUser().getName());
String msg;
msg = "System> " + client.getUser().getName()
+ " has entered the room.\r\n";
contentArea.append(msg);
sendServerMessage("ADD@" + client.getUser().getName());
sendServerMessage("SYSTEM@" + msg);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class ClientThread extends Thread {
private Socket socket;
private BufferedReader bin;
private PrintWriter wout;
private User user;
private boolean closeflag = false;
Timer timer1;
Timer timer2;
public ClientThread(Socket socket) {
try {
this.socket = socket;
this.socket.setSoTimeout(3000);
bin = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
wout = new PrintWriter(socket.getOutputStream());
String inf = bin.readLine();
StringTokenizer st = new StringTokenizer(inf, "@");
user = new User(st.nextToken(), st.nextToken());
for (int i = clients.size() - 1; i >= 0; i--)
wout.println("ADD@" + clients.get(i).getUser().getName());
} catch (Exception e) {
e.printStackTrace();
}
timer1 = new Timer();
timer1.schedule(this.new Heart1(), 100, 1000);
timer2 = new Timer();
timer2.schedule(this.new Heart2(), 100, 10000);
}
boolean alive = true, runflag = true;
class Heart1 extends TimerTask {
public void run() {
if(runflag == true) {
alive = false;
runflag = false;
}
}
}
class Heart2 extends TimerTask {
public void run() {
if(alive == false) {
try {
closeConnection();
} catch(Exception e) {
}
}
runflag = true;
}
}
public void closeConnection() throws IOException {
String msg;
msg = "System> " + user.getName() + " has left the room.\r\n";
contentArea.append(msg);
sendServerMessage("SYSTEM@" + msg);
msg = "DELETE@" + user.getName();
sendServerMessage(msg);
bin.close();
wout.close();
socket.close();
listModel.removeElement(user.getName());
for (int i = 0; i < clients.size(); i++) {
if (clients.get(i).getUser().getName().equals(user.getName())) {
ClientThread tmp = clients.get(i);
clients.remove(i);
break;
}
}
System.out.println(clients.size());
closeflag = true;
timer1.cancel();
timer2.cancel();
}
public void run() {
String msg = null;
while (!closeflag) {
try {
msg = bin.readLine();
if (msg.equals("CLOSE")) {
closeConnection();
break;
} else if(msg.equals("A@")) {
alive = true;
} else {
dispatcherMessage(msg);
}
} catch (SocketException se) {
try {
closeConnection();
} catch (Exception ee) {
}
break;
} catch (Exception e) {
}
}
}
public void dispatcherMessage(String msg) {
StringTokenizer st = new StringTokenizer(msg, "@");
String source = st.nextToken();
String ch = st.nextToken();
String content = st.nextToken();
while (st.hasMoreTokens())
content += st.nextToken();
msg = source + "> " + content + "\r\n";
if (ch.equals("ALL")) {
contentArea.append(msg);
sendServerMessage("MSG@" + msg);
}
}
public BufferedReader getReader() {
return bin;
}
public PrintWriter getWriter() {
return wout;
}
public User getUser() {
return user;
}
}
public static void main(String[] args) {
new MServer();
}
}
MClient.java //顧客端
import java.net.Socket;
import java.io.BufferedInputStream;
import java.io.InputStreamReader;
import java.io.BufferedReader;
import java.io.PrintWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.StringTokenizer;
import java.util.Map;
import java.util.HashMap;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
import java.awt.event.AdjustmentEvent;
import java.awt.event.AdjustmentListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.lang.Thread;
public class MClient {
private JFrame frame;
private JTextArea contentArea;
private JTextField txt_msg;
private JTextField txt_port;
private JTextField txt_host;
private JTextField txt_name;
private JButton btn_start;
private JButton btn_stop;
private JButton btn_send;
private JPanel northPanel;
private JPanel southPanel;
private JScrollPane rightPanel;
private JScrollPane leftPanel;
private JSplitPane centerSplit; // 分頁窗格
private JList userList;
private DefaultListModel listModel;
private Socket socket;
private PrintWriter writer;
private BufferedReader reader;
private boolean isConnected = false;
private MessageThread messageThread;
private Map<String, User> onlineUsers = new HashMap<String, User>();
public MClient() {
frame = new JFrame("MClient - Beta");
contentArea = new JTextArea();
contentArea.setEditable(false);
contentArea.setBackground(Color.black);
contentArea.setForeground(Color.green);
txt_msg = new JTextField();
txt_port = new JTextField("2047");
txt_host = new JTextField("127.0.0.1");
txt_name = new JTextField("guest");
btn_start = new JButton("LogIn");
btn_stop = new JButton("LogOut");
btn_send = new JButton("↵ ");
btn_stop.setEnabled(false);
listModel = new DefaultListModel();
userList = new JList(listModel);
southPanel = new JPanel(new BorderLayout());
southPanel.setBorder(new TitledBorder(""));
southPanel.add(txt_msg, BorderLayout.CENTER);
southPanel.add(btn_send, BorderLayout.EAST);
leftPanel = new JScrollPane(userList);
leftPanel.setBorder(new TitledBorder("Who's here"));
rightPanel = new JScrollPane(contentArea);
rightPanel.setBorder(new TitledBorder("Chat Area"));
rightPanel.getVerticalScrollBar().addAdjustmentListener(
new AdjustmentListener() {
int cnt = 0;
public void adjustmentValueChanged(AdjustmentEvent e) {
if(cnt == 3) {
JScrollBar sb = rightPanel.getVerticalScrollBar();
sb.setValue(sb.getMaximum());
cnt = 0;
}
cnt++;
}
}); // 自動置底
centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel,
rightPanel);
centerSplit.setDividerLocation(100);
northPanel = new JPanel();
northPanel.setLayout(new GridLayout(1, 7));
northPanel.add(new JLabel("Port"));
northPanel.add(txt_port);
northPanel.add(new JLabel("Host IP"));
northPanel.add(txt_host);
northPanel.add(new JLabel("Username"));
northPanel.add(txt_name);
northPanel.add(btn_start);
northPanel.add(btn_stop);
northPanel.setBorder(new TitledBorder("Setting"));
frame.setLayout(new BorderLayout());
frame.add(northPanel, BorderLayout.NORTH);
frame.add(southPanel, BorderLayout.SOUTH);
frame.add(centerSplit, BorderLayout.CENTER);
frame.setSize(800, 600);
int screen_width = Toolkit.getDefaultToolkit().getScreenSize().width;
int screen_height = Toolkit.getDefaultToolkit().getScreenSize().height;
frame.setLocation((screen_width - frame.getWidth()) / 2,
(screen_height - frame.getHeight()) / 2);
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
if (isConnected)
closeConnection();
System.exit(0);
}
});
txt_msg.addActionListener(new ActionListener() { // enter touch
public void actionPerformed(ActionEvent e) {
send();
}
});
btn_send.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
send();
}
});
btn_start.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/*if (isConnected) {
��������������������JOptionPane.showMessageDialog(frame, "has opened",
����������������������������"Error120", JOptionPane.ERROR_MESSAGE);
��������������������return;
����������������}*/
int port;
try {
port = Integer.parseInt(txt_port.getText());
if (port <= 0)
throw new Exception("port <= 0");
if (txt_name.getText().equals(""))
throw new Exception("name too short");
openConnection(port, txt_host.getText(), txt_name.getText());
if (isConnected) {
contentArea.append("System> open connection! \r\n");
btn_start.setEnabled(false);
txt_port.setEditable(false);
txt_name.setEditable(false);
txt_host.setEditable(false);
btn_stop.setEnabled(true);
} else {
}
} catch (Exception e3) {
JOptionPane.showMessageDialog(frame, "", "Error136",
JOptionPane.ERROR_MESSAGE);
}
}
});
btn_stop.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
/*if (!isConnected) {
��������������������JOptionPane.showMessageDialog(frame, "has closed",
����������������������������"Error147", JOptionPane.ERROR_MESSAGE);
��������������������return;
����������������}*/
try {
closeConnection();
btn_start.setEnabled(true);
txt_port.setEditable(true);
txt_name.setEditable(true);
txt_host.setEditable(true);
btn_stop.setEnabled(false);
contentArea.append("System> close connection! \r\n");
} catch (Exception e3) {
JOptionPane.showMessageDialog(frame, "", "Error158",
JOptionPane.ERROR_MESSAGE);
}
}
});
Timer timer = new Timer();
timer.schedule(this.new Heart(), 100, 3000);
}
public void send() {
if (!isConnected) {
JOptionPane.showMessageDialog(frame, "hasn't connect.", "Error",
JOptionPane.ERROR_MESSAGE);
return;
}
String msg = txt_msg.getText();
txt_msg.setText("");
if (msg == null || msg.equals("")) {
JOptionPane.showMessageDialog(frame, "msg null", "Error128",
JOptionPane.ERROR_MESSAGE);
return;
}
sendMessage(txt_name.getText() + "@ALL@" + msg);
}
public void sendMessage(String msg) {
if(writer != null) {
writer.println(msg);
writer.flush();
}
}
public void openConnection(int port, String hostip, String name) {
try {
socket = new Socket(hostip, port);
writer = new PrintWriter(socket.getOutputStream());
reader = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
sendMessage(name + "@" + socket.getLocalAddress().toString());
isConnected = true;
messageThread = new MessageThread(reader, contentArea);
messageThread.start();
} catch (Exception e) {
contentArea.append("Error> Connection fails.\r\n");
isConnected = false;
}
}
public void closeConnection() {
try {
sendMessage("CLOSE");
if (reader != null)
reader.close();
if (writer != null)
writer.close();
if (socket != null)
socket.close();
isConnected = false;
messageThread.stop();
listModel.removeAllElements();
} catch (Exception e) {
e.printStackTrace();
isConnected = true;
}
}
class MessageThread extends Thread {
private BufferedReader reader;
private JTextArea contentArea;
public MessageThread(BufferedReader reader, JTextArea contentArea) {
this.reader = reader;
this.contentArea = contentArea;
}
public void run() {
String msg = "";
while(true) {
try {
msg = reader.readLine();
StringTokenizer st = new StringTokenizer(msg, "@");
if(msg == null || msg.equals(""))
continue;
String cmd = st.nextToken();
if(cmd.equals("CLOSE")) {
contentArea.append("System> Server close!\r\n");
closeConnection();
} else if(cmd.equals("ADD")) { // userlist
String name = st.nextToken();
listModel.addElement(name);
} else if(cmd.equals("DELETE")) { // userlist
String name = st.nextToken();
onlineUsers.remove(name);
listModel.removeElement(name);
} else if(cmd.equals("MSG")) { // other perople
contentArea.append(st.nextToken() + "\r\n");
} else if(cmd.equals("SYSTEM")) { // system
contentArea.append(st.nextToken() + "\r\n");
}
} catch(Exception e) {
e.printStackTrace();
}
}
}
}
class Heart extends TimerTask {
public void run() {
sendMessage("A@");
System.out.println("A@");
}
}
public static void main(String[] args) {
new MClient();
}
}
額外的 User.java
public class User {
private String name;
private String ip;
public User(String name, String ip) {
this.name = name;
this.ip = ip;
}
public String getName() {
return name;
}
public String getIp() {
return ip;
}
public void setName(String name) {
this.name = name;
}
public void setIp(String ip) {
this.ip = ip;
}
}
文章定位: