Змінив спілкування в чаті Stringи на клас Message. Тако ж була проблема при зупинці сервера, не завжди закривалося з'єднання з клієнтами (проблема при ітерації списка клієнтів), замінив ArrayList на CopyOnWriteArrayList. Ще добавив шрифт для клієнта:
MESSAGE.JAVA
package main;
import java.io.Serializable;
public class Message implements Serializable {
private static final long serialVersionUID = 6341796648975819075L;
public static final int TEXT = 0;
public static final int EXIT = 1;
private int type;
private String text;
public Message(String text) {
this(Message.TEXT, text);
}
public Message(int type, String text) {
this.type = type;
this.text = text;
}
public int getType() {
return type;
}
public String getText() {
return text;
}
}
SERVER.JAVA
package main;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArrayList;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"HH:mm:ss");
private final int port;
private int uniqueId = 0;
private CopyOnWriteArrayList<ClientHandler> clientsList = new CopyOnWriteArrayList<ClientHandler>();
private boolean keepGoing = true;
private GUI frame;
public Server(int port, GUI frame) {
this.port = port;
this.frame = frame;
}
public void start() {
try (ServerSocket serverSocket = new ServerSocket(port);) {
serverDisplayMsg("***SERVER STARTING!***");
while (keepGoing) {
serverDisplayMsg("Server waiting for client on port " + port
+ "...");
Socket clientSocket = serverSocket.accept();
if (keepGoing) {
ClientHandler clientHandler = new ClientHandler(
clientSocket);
Thread thread = new Thread(clientHandler);
clientsList.add(clientHandler);
frame.getListModel().addElement(clientHandler.name);
thread.start();
serverDisplayMsg(clientHandler.name + " just connected.");
serverDisplayMsg("On the server, " + clientsList.size()
+ " client(s).");
}
}
serverDisplayMsg("***SERVER IS STOPPED!***");
for (ClientHandler client : clientsList) {
client.close();
}
} catch (IOException e) {
serverDisplayErr(e.getMessage());
}
}
private void serverDisplayMsg(String msg) {
frame.appendMessage(frame.getTxtMsg(), DATE_FORMAT.format(new Date())
+ " " + msg);
}
private void serverDisplayErr(String msg) {
frame.appendMessage(frame.getTxtErr(), DATE_FORMAT.format(new Date())
+ " " + msg);
}
private synchronized void tellEveryone(String msg) {
for (int i = clientsList.size(); --i >= 0;) {
try {
clientsList.get(i).writer.writeObject(new Message(DATE_FORMAT
.format(new Date()) + " " + msg));
} catch (IOException e) {
serverDisplayErr(e.getMessage());
}
}
}
private synchronized void removeClient(int id) {
for (int i = 0; i < clientsList.size(); i++) {
ClientHandler client = clientsList.get(i);
if (client.id == id) {
clientsList.remove(i);
frame.getListModel().remove(i);
return;
}
}
}
public void stopServer() {
keepGoing = false;
try {
new Socket("localhost", port);
} catch (IOException e) {
serverDisplayErr(e.getMessage());
}
}
public class ClientHandler implements Runnable {
private int id;
private String name;
private Socket socket;
private ObjectOutputStream writer;
private ObjectInputStream read;
public ClientHandler(Socket socket) {
id = ++uniqueId;
this.socket = socket;
try {
writer = new ObjectOutputStream(socket.getOutputStream());
read = new ObjectInputStream(socket.getInputStream());
name = ((Message) read.readObject()).getText();
writer.writeObject(new Message("Hello " + name + "!"));
} catch (ClassNotFoundException | IOException e) {
serverDisplayErr(name + " " + e.getMessage());
}
}
@Override
public void run() {
tellEveryone(name + " just connected.");
boolean keepGoing = true;
while (keepGoing) {
Message msg;
try {
msg = (Message) read.readObject();
} catch (ClassNotFoundException | IOException e) {
if (Server.this.keepGoing) {
serverDisplayErr(name + " " + e.getMessage());
}
break;
}
switch (msg.getType()) {
case Message.TEXT:
tellEveryone("<" + name + "> " + msg.getText());
break;
case Message.EXIT:
keepGoing = false;
serverDisplayMsg(name + " disconnected.");
tellEveryone(name + " disconnected.");
try {
writer.writeObject(new Message("Bye-bye " + name + "!"));
} catch (IOException e) {
serverDisplayErr(name + " " + e.getMessage());
}
break;
}
}
removeClient(id);
close();
if (Server.this.keepGoing) {
serverDisplayMsg("On the server, " + clientsList.size()
+ " client(s).");
serverDisplayMsg("Server waiting for client on port " + port
+ "...");
}
}
private void close() {
try {
writer.close();
} catch (IOException e1) {
serverDisplayErr(name + " " + e1.getMessage());
}
try {
read.close();
} catch (IOException e2) {
serverDisplayErr(name + " " + e2.getMessage());
}
try {
socket.close();
} catch (IOException e3) {
serverDisplayErr(name + " " + e3.getMessage());
}
}
}
}
GUI
package main;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.BorderLayout;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.WindowEvent;
import java.awt.event.WindowAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JList;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.BadLocationException;
public class GUI extends JFrame {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"HH:mm:ss");
private static final String PATTERN_TIME = "[0-2][0-9]:[0-5][0-9]:[0-5][0-9]";
private SimpleAttributeSet styleTime = new SimpleAttributeSet();
private SimpleAttributeSet styleMsg = new SimpleAttributeSet();
private JTextField txtPort = new JTextField(" 5000 ", 4);
private final int width = txtPort.getPreferredSize().width;
private final int height = txtPort.getPreferredSize().height;
private JTextPane txtMsg = new JTextPane();
private JTextPane txtErr = new JTextPane();
private DefaultListModel<String> listModel = new DefaultListModel<String>();
private Server server;
public GUI() {
setTitle("Chat Server");
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent arg0) {
if (server != null) {
server.stopServer();
}
System.exit(0);
}
});
createStylesTextPane();
createNorthPanel();
createCenterPanel();
createEastPanel();
createSouthPanel();
pack();
setLocationRelativeTo(null);
setVisible(true);
}
private void createStylesTextPane() {
StyleConstants.setItalic(styleTime, true);
StyleConstants.setFontSize(styleTime,
StyleConstants.getFontSize(styleTime) - 1);
StyleConstants.setBold(styleMsg, true);
StyleConstants.setBackground(styleMsg, Color.GRAY.darker());
}
public void appendMessage(JTextPane pane, String msg) {
try {
Document doc = pane.getDocument();
String time = null;
if (msg.length() >= 8
&& (time = msg.substring(0, 8)).matches(PATTERN_TIME)) {
doc.insertString(doc.getLength(), time, styleTime);
if (msg.indexOf(" ***") == 8 && msg.indexOf("***", 10) >= 10) {
doc.insertString(doc.getLength(), " ", null);
String txt = msg.substring(9);
doc.insertString(doc.getLength(), txt, styleMsg);
} else {
String text = msg.substring(8);
doc.insertString(doc.getLength(), text, null);
}
} else {
doc.insertString(doc.getLength(), msg, null);
}
doc.insertString(doc.getLength(), "\n", null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
private void createNorthPanel() {
JPanel panel = new JPanel();
((FlowLayout) panel.getLayout()).setHgap(10);
panel.add(new JLabel("IP: "));
JTextField txtIP = new JTextField(9);
txtIP.setEditable(false);
String ip = null;
try {
ip = " " + InetAddress.getLocalHost().getHostAddress() + " ";
} catch (UnknownHostException e) {
appendMessage(txtErr, DATE_FORMAT.format(new Date()) + " "
+ "Unknown host exception.");
}
txtIP.setText(ip);
panel.add(txtIP);
panel.add(new JLabel("PORT: "));
panel.add(txtPort);
add(panel, BorderLayout.PAGE_START);
}
private void createCenterPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout(2, 1));
panel.add(createFormatPane(txtMsg, Color.GREEN, "Server messages:"));
panel.add(createFormatPane(txtErr, Color.RED, "Server errors:"));
add(panel, BorderLayout.CENTER);
}
private JScrollPane createFormatPane(JTextPane txtPane, Color colTxt,
String title) {
txtPane.setEditable(false);
txtPane.setBackground(Color.BLACK);
txtPane.setForeground(colTxt);
JScrollPane pane = new JScrollPane(txtPane);
pane.setBorder(BorderFactory.createTitledBorder(title));
pane.setPreferredSize(new Dimension(width * 10, height * 10));
return pane;
}
private void createEastPanel() {
JPanel panel = new JPanel();
panel.setLayout(new GridLayout());
JScrollPane pane = new JScrollPane(new JList<String>(listModel));
pane.setBorder(BorderFactory.createTitledBorder("Client list:"));
panel.add(pane);
panel.setPreferredSize(new Dimension(width * 4, 0));
add(panel, BorderLayout.LINE_END);
}
private void createSouthPanel() {
JPanel panel = new JPanel();
panel.add(new JButton(new ButtonAction()));
add(panel, BorderLayout.PAGE_END);
}
private int getPort() {
return Integer.parseInt(txtPort.getText().trim());
}
public JTextPane getTxtMsg() {
return txtMsg;
}
public JTextPane getTxtErr() {
return txtErr;
}
public DefaultListModel<String> getListModel() {
return listModel;
}
public static void main(String arr[]) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new GUI();
}
});
}
public class ButtonAction extends AbstractAction {
public ButtonAction() {
putValue(AbstractAction.NAME, "Start");
}
@Override
public void actionPerformed(ActionEvent e) {
switch (getValue(AbstractAction.NAME).toString()) {
case "Start":
try {
txtMsg.setText("");
txtErr.setText("");
server = new Server(getPort(), GUI.this);
Thread thread = new Thread(new ServerRunning());
thread.start();
txtPort.setEditable(false);
putValue(AbstractAction.NAME, "Stop");
break;
} catch (NumberFormatException err) {
appendMessage(txtErr, DATE_FORMAT.format(new Date()) + " "
+ "Invalid port number.");
return;
}
case "Stop":
server.stopServer();
server = null;
txtPort.setEditable(true);
putValue(AbstractAction.NAME, "Start");
break;
}
}
}
public class ServerRunning implements Runnable {
@Override
public void run() {
server.start();
}
}
}
CLIENT.JAVA
package main;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.IOException;
import java.net.Socket;
public class Client {
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"HH:mm:ss");
private Socket socket;
private ObjectOutputStream writer;
private ObjectInputStream read;
private GUI frame;
public Client(String serverIP, int port, String name, GUI frame)
throws IOException {
this.frame = frame;
try {
socket = new Socket(serverIP, port);
} catch (IOException e) {
throw new IOException("Error connectiong to server: "
+ e.getMessage());
}
try {
writer = new ObjectOutputStream(socket.getOutputStream());
read = new ObjectInputStream(socket.getInputStream());
} catch (IOException e) {
throw new IOException("Exception creating Input/Output streams: "
+ e.getMessage());
}
writer.writeObject(new Message(name));
new Thread(new ServerListener()).start();
}
private void clientDisplayMsg(String msg) {
frame.appendMessage(msg, frame.getTxtChat());
}
private void clientDisplayErr(String msg) {
frame.appendMessage(DATE_FORMAT.format(new Date()) + " " + msg,
frame.getTxtErr());
}
public void sendMessage(Message msg) {
try {
writer.writeObject(msg);
} catch (IOException e) {
clientDisplayErr(e.getMessage());
}
}
private void disconnect() {
try {
writer.close();
} catch (IOException e1) {
clientDisplayErr(e1.getMessage());
}
try {
read.close();
} catch (IOException e2) {
clientDisplayErr(e2.getMessage());
}
try {
socket.close();
} catch (IOException e3) {
clientDisplayErr(e3.getMessage());
} finally {
frame.disconnect();
}
}
public class ServerListener implements Runnable {
@Override
public void run() {
while (true) {
Message msg;
try {
msg = (Message) read.readObject();
switch (msg.getType()) {
case Message.TEXT:
clientDisplayMsg(msg.getText());
break;
}
} catch (Exception e1) {
try {
if (e1 instanceof IOException
&& !frame.getLastMessage().equals(
"Bye-bye " + frame.getLogin() + "!")) {
clientDisplayMsg("Server has close the connection!");
}
disconnect();
break;
} catch (Exception e2) {
clientDisplayErr(e2.getMessage());
disconnect();
break;
}
}
}
}
}
}
GUI.JAVA
package main;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.io.IOException;
import java.net.URL;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.GraphicsEnvironment;
import java.awt.FlowLayout;
import java.awt.BorderLayout;
import java.awt.event.WindowEvent;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.awt.event.WindowAdapter;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextPane;
import javax.swing.JTextArea;
import javax.swing.JButton;
import javax.swing.JTextField;
import javax.swing.JLabel;
import javax.swing.AbstractAction;
import javax.swing.ImageIcon;
import javax.swing.BorderFactory;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.StyleConstants;
import javax.swing.text.BadLocationException;
public class GUI extends JFrame {
private static final String PATTERN_TIME = "[0-2][0-9]:[0-5][0-9]:[0-5][0-9]";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat(
"HH:mm:ss");
private static final SimpleAttributeSet STYLE_TIME = new SimpleAttributeSet();
private static final SimpleAttributeSet STYLE_LOG = new SimpleAttributeSet();
private static final SimpleAttributeSet STYLE_LOG_MSG = new SimpleAttributeSet();
private static final SimpleAttributeSet STYLE_MSG = new SimpleAttributeSet();
private static final ImageIcon ICON_TALK = new ImageIcon(
GUI.class.getResource("files/talk.png"));
private static final URL URL_FONT = GUI.class
.getResource("files/v_CCcomicrazy.ttf");
private static final KeyStroke KEY_SHIFT_ENTER = KeyStroke.getKeyStroke(
KeyEvent.VK_ENTER, KeyEvent.SHIFT_DOWN_MASK, true);
private static final KeyStroke KEY_ENTER = KeyStroke.getKeyStroke("ENTER");
private JTextField txtIP = new JTextField(" 192.168.1.94 ", 9);
private JTextField txtPort = new JTextField(" 5000 ", 4);
private JTextField txtLog = new JTextField(7);
private final int width = txtIP.getPreferredSize().width;
private final int height = txtIP.getPreferredSize().height;
private JTextPane txtChat = new JTextPane();
private JTextPane txtErr = new JTextPane();
private JTextArea txtMsg = new JTextArea();
private Client client;
private ConnectAction conAct = new ConnectAction();
public GUI() {
createStylesTextPane();
createNorthPanel();
createCenterPanel();
createEastPanel();
createSouthPanel();
setListeners();
setTitle("Chat");
setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
pack();
setLocationRelativeTo(null);
txtLog.requestFocusInWindow();
setVisible(true);
}
private void createStylesTextPane() {
StyleConstants.setItalic(STYLE_TIME, true);
StyleConstants.setFontSize(STYLE_TIME,
StyleConstants.getFontSize(STYLE_TIME) - 1);
StyleConstants.setForeground(STYLE_LOG, Color.YELLOW);
try {
Font font = Font.createFont(Font.TRUETYPE_FONT,
URL_FONT.openStream());
GraphicsEnvironment ge = GraphicsEnvironment
.getLocalGraphicsEnvironment();
ge.registerFont(font);
} catch (FontFormatException | IOException e) {
e.printStackTrace();
}
StyleConstants.setFontFamily(STYLE_LOG_MSG, "v_CCcomicrazy");
StyleConstants.setBold(STYLE_LOG_MSG, true);
StyleConstants.setForeground(STYLE_LOG_MSG, Color.MAGENTA);
StyleConstants.setFontSize(STYLE_MSG,
StyleConstants.getFontSize(STYLE_MSG) + 1);
StyleConstants.setBold(STYLE_MSG, true);
StyleConstants.setUnderline(STYLE_MSG, true);
}
private void createNorthPanel() {
JPanel panel = new JPanel();
((FlowLayout) panel.getLayout()).setHgap(10);
panel.add(new JLabel("Server ip: "));
panel.add(txtIP);
panel.add(new JLabel("Server port: "));
panel.add(txtPort);
panel.add(new JLabel("Login: "));
panel.add(txtLog);
panel.add(new JButton(conAct));
add(panel, BorderLayout.PAGE_START);
}
private void createCenterPanel() {
add(createFormatPane(txtChat, Color.GREEN, "Chat:"));
}
private JScrollPane createFormatPane(JTextPane txtPane, Color colTxt,
String title) {
txtPane.setEditable(false);
txtPane.setBackground(Color.BLACK);
txtPane.setForeground(colTxt);
JScrollPane pane = new JScrollPane(txtPane);
pane.setBorder(BorderFactory.createTitledBorder(title));
pane.setPreferredSize(new Dimension(width * 5, height * 5));
return pane;
}
private void createEastPanel() {
JPanel panel = new JPanel(new BorderLayout());
txtMsg.setEnabled(false);
txtMsg.setLineWrap(true);
txtMsg.setWrapStyleWord(true);
JScrollPane pane = new JScrollPane(txtMsg);
pane.setPreferredSize(new Dimension(width * 2, height * 10));
panel.add(pane);
panel.setBorder(BorderFactory.createTitledBorder("Your message:"));
add(panel, BorderLayout.LINE_END);
}
private void createSouthPanel() {
add(createFormatPane(txtErr, Color.RED, "Errors:"),
BorderLayout.PAGE_END);
}
private void setListeners() {
addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent we) {
if (client != null) {
client.sendMessage(new Message(Message.EXIT, ""));
}
System.exit(0);
}
});
txtMsg.getInputMap().put(KEY_SHIFT_ENTER, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ae) {
txtMsg.append("\n");
}
});
txtMsg.getInputMap().put(KEY_ENTER, new AbstractAction() {
@Override
public void actionPerformed(ActionEvent ae) {
String txt = txtMsg.getText().trim();
if (txt.length() > 0) {
client.sendMessage(new Message(txt));
txtMsg.setText("");
txtMsg.requestFocusInWindow();
}
}
});
}
public void appendMessage(String string, JTextPane pane) {
try {
Document doc = pane.getDocument();
String time = null;
if (string.length() >= 8
&& (time = string.substring(0, 8)).matches(PATTERN_TIME)) {
doc.insertString(doc.getLength(), time, STYLE_TIME);
int index;
if (string.indexOf(" <") == 8
&& (index = string.indexOf("> ") + 1) > 11) {
String log = string.substring(8, index);
doc.insertString(doc.getLength(), log, STYLE_LOG);
pane.setCaretPosition(doc.getLength());
pane.insertIcon(ICON_TALK);
String text = string.substring(index);
doc.insertString(doc.getLength(), text, STYLE_LOG_MSG);
} else {
String text = string.substring(8);
doc.insertString(doc.getLength(), text, null);
}
} else {
doc.insertString(doc.getLength(), string, STYLE_MSG);
}
doc.insertString(doc.getLength(), "\n", null);
} catch (BadLocationException e) {
e.printStackTrace();
}
}
private String getIP() throws Exception {
String txt = txtIP.getText().trim();
if (txt.length() == 0) {
throw new Exception("Please enter IP address server.");
} else {
return txt;
}
}
private int getPort() throws Exception {
try {
int x = Integer.parseInt(txtPort.getText().trim());
return x;
} catch (NumberFormatException err) {
throw new NumberFormatException("Invalid port number.");
}
}
public String getLogin() throws Exception {
String txt = txtLog.getText().trim();
if (txt.length() == 0) {
throw new Exception("Please enter your login.");
} else {
return txt;
}
}
public JTextPane getTxtChat() {
return txtChat;
}
public JTextPane getTxtErr() {
return txtErr;
}
public String getLastMessage() {
Document doc = txtChat.getDocument();
String msg = null;
try {
String text = doc.getText(0, txtChat.getDocument().getLength() - 1);
int x = text.lastIndexOf('\n');
msg = doc.getText(x, doc.getLength() - x).replaceAll("\n", "");
} catch (BadLocationException e) {
appendMessage(
DATE_FORMAT.format(new Date()) + " " + e.getMessage(),
txtErr);
}
return msg;
}
public void disconnect() {
conAct.putValue(AbstractAction.NAME, "Connect");
txtIP.setEnabled(true);
txtPort.setEnabled(true);
txtLog.setEnabled(true);
txtMsg.setEnabled(false);
}
public static void main(String arr[]) {
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
new GUI();
}
});
}
public class ConnectAction extends AbstractAction {
public ConnectAction() {
putValue(AbstractAction.NAME, "Connect");
}
@Override
public void actionPerformed(ActionEvent e) {
switch (getValue(AbstractAction.NAME).toString()) {
case "Connect":
try {
txtChat.setText("");
txtErr.setText("");
client = new Client(getIP(), getPort(), getLogin(),
GUI.this);
txtIP.setEnabled(false);
txtPort.setEnabled(false);
txtLog.setEnabled(false);
txtMsg.setEnabled(true);
txtMsg.requestFocusInWindow();
putValue(AbstractAction.NAME, "Disconnect");
} catch (Exception err) {
appendMessage(
DATE_FORMAT.format(new Date()) + " "
+ err.getMessage(), txtErr);
txtLog.requestFocusInWindow();
return;
}
break;
case "Disconnect":
client.sendMessage(new Message(Message.EXIT, ""));
client = null;
txtMsg.setText(null);
txtLog.requestFocusInWindow();
break;
}
}
}
}
Post's attachments2.png 23.16 kb, 282 downloads since 2015-07-01