由于需要制作网络计算机网络课程设计,并且不想搞网络布线或者局域网路由器配置等等这种完全搞不懂的东西,最后决定使用socket基于java编写一个局域网聊天室:
关于socket以及网络编程的相关知识详见我另一篇文章:Java基于socket编程
程序基于C/S结构,即客户端服务器模式。
服务器:
默认ip为本机ip
需要双方确定一个端口号
可设置最大连接人数
可启动与关闭
界面显示在线用户人以及姓名(本机不在此显示)
客户端:
需要手动设置服务器ip地址(局域网)
手动设置端口号
输入姓名
可连接可断开
程序运行界面如下:
服务器:
客户端:
具体代码我会在最后上传。
软件有很多不足,其中比如:
没有与数据库有任何交集
优化:可将所有用户存放在数据库中,以及将聊天记录也放入数据库中
没有实现一对一聊天
优化:需重新定义一对一聊天的方法
还有许多不足的地方,日后有兴趣再回来慢慢研究
下面为该程序三个代码:
User.java
public class User{/*** 用户信息类* 用于记录用户个人信息:姓名以及IP*/private String name;private String ip;public User(String name, String ip) {this.name = name;this.ip = ip;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getIp() {return ip;}public void setIp(String ip) {this.ip = ip;}
}
Server_more.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.StringTokenizer;import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;public class Server_more {private JFrame frame;private JTextArea contentArea; //文本域private JTextField txt_message; //用于显示文本信息private JTextField txt_max; //设置最大连接人数private JTextField txt_port; //设置端口号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;private ArrayList<ClientThread> clients;private boolean isStart = false;// 主方法,程序执行入口public static void main(String[] args) {new Server_more();}// 执行消息发送public void send() {if (!isStart) {JOptionPane.showMessageDialog(frame, "服务器还未启动,不能发送消息!", "错误",JOptionPane.ERROR_MESSAGE);return;}if (clients.size() == 0) {JOptionPane.showMessageDialog(frame, "没有用户在线,不能发送消息!", "错误",JOptionPane.ERROR_MESSAGE);return;}String message = txt_message.getText().trim();if (message == null || message.equals("")) {JOptionPane.showMessageDialog(frame, "消息不能为空!", "错误",JOptionPane.ERROR_MESSAGE);return;}sendServerMessage(message);// 群发服务器消息contentArea.append("服务器:" + txt_message.getText() + "\r\n");txt_message.setText(null);}// 构造放法public Server_more() {frame = new JFrame("服务器");// 更改JFrame的图标://frame.setIconImage(Toolkit.getDefaultToolkit().createImage(Client.class.getResource("qq.png")));//frame.setIconImage(Toolkit.getDefaultToolkit().createImage(Server.class.getResource("qq.png")));contentArea = new JTextArea();contentArea.setEditable(false);contentArea.setForeground(Color.blue);txt_message = new JTextField();txt_max = new JTextField("30");txt_port = new JTextField("6666");btn_start = new JButton("启动");btn_stop = new JButton("停止");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_message, "Center");southPanel.add(btn_send, "East");leftPanel = new JScrollPane(userList);leftPanel.setBorder(new TitledBorder("在线用户"));rightPanel = new JScrollPane(contentArea);rightPanel.setBorder(new TitledBorder("消息显示区"));centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftPanel,rightPanel);centerSplit.setDividerLocation(100);northPanel = new JPanel();northPanel.setLayout(new GridLayout(1, 6));northPanel.add(new JLabel("人数上限"));northPanel.add(txt_max);northPanel.add(new JLabel("端口"));northPanel.add(txt_port);northPanel.add(btn_start);northPanel.add(btn_stop);northPanel.setBorder(new TitledBorder("配置信息"));frame.setLayout(new BorderLayout());frame.add(northPanel, "North");frame.add(centerSplit, "Center");frame.add(southPanel, "South");frame.setSize(600, 400);//frame.setSize(Toolkit.getDefaultToolkit().getScreenSize());//设置全屏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_message.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {send();}});// 单击发送按钮时事件btn_send.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent arg0) {send();}});// 单击启动服务器按钮时事件btn_start.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {if (isStart) {JOptionPane.showMessageDialog(frame, "服务器已处于启动状态,不要重复启动!","错误", JOptionPane.ERROR_MESSAGE);return;}int max;int port;try {try {max = Integer.parseInt(txt_max.getText());} catch (Exception e1) {throw new Exception("人数上限为正整数!");}if (max <= 0) {throw new Exception("人数上限为正整数!");}try {port = Integer.parseInt(txt_port.getText());} catch (Exception e1) {throw new Exception("端口号为正整数!");}if (port <= 0) {throw new Exception("端口号 为正整数!");}serverStart(max, port);contentArea.append("服务器已成功启动!人数上限:" + max + ",端口:" + port+ "\r\n");JOptionPane.showMessageDialog(frame, "服务器成功启动!");btn_start.setEnabled(false);txt_max.setEnabled(false);txt_port.setEnabled(false);btn_stop.setEnabled(true);} catch (Exception exc) {JOptionPane.showMessageDialog(frame, exc.getMessage(),"错误", JOptionPane.ERROR_MESSAGE);}}});// 单击停止服务器按钮时事件btn_stop.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {if (!isStart) {JOptionPane.showMessageDialog(frame, "服务器还未启动,无需停止!", "错误",JOptionPane.ERROR_MESSAGE);return;}try {closeServer();btn_start.setEnabled(true);txt_max.setEnabled(true);txt_port.setEnabled(true);btn_stop.setEnabled(false);contentArea.append("服务器成功停止!\r\n");JOptionPane.showMessageDialog(frame, "服务器成功停止!");} catch (Exception exc) {JOptionPane.showMessageDialog(frame, "停止服务器发生异常!", "错误",JOptionPane.ERROR_MESSAGE);}}});}// 启动服务器public void serverStart(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 (BindException e) {isStart = false;throw new BindException("端口号已被占用,请换一个!");} catch (Exception e1) {e1.printStackTrace();isStart = false;throw new BindException("启动服务器异常!");}}// 关闭服务器@SuppressWarnings("deprecation")public void closeServer() {try {if (serverThread != null)serverThread.stop();// 停止服务器线程for (int i = clients.size() - 1; i >= 0; i--) {// 给所有在线用户发送关闭命令clients.get(i).getWriter().println("CLOSE");clients.get(i).getWriter().flush();// 释放资源clients.get(i).stop();// 停止此条为客户端服务的线程clients.get(i).reader.close();clients.get(i).writer.close();clients.get(i).socket.close();clients.remove(i);}if (serverSocket != null) {serverSocket.close();// 关闭服务器端连接}listModel.removeAllElements();// 清空用户列表isStart = false;} catch (IOException e) {e.printStackTrace();isStart = true;}}// 群发服务器消息public void sendServerMessage(String message) {for (int i = clients.size() - 1; i >= 0; i--) {clients.get(i).getWriter().println("服务器:" + message + "(多人发送)");clients.get(i).getWriter().flush();}}// 服务器线程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@服务器:对不起," + user.getName()+ user.getIp() + ",服务器在线人数已达上限,请稍后尝试连接!");w.flush();// 释放资源r.close();w.close();socket.close();continue;}ClientThread client = new ClientThread(socket);client.start();// 开启对此客户端服务的线程clients.add(client);listModel.addElement(client.getUser().getName());// 更新在线列表contentArea.append(client.getUser().getName()+ client.getUser().getIp() + "上线!\r\n");} catch (IOException e) {e.printStackTrace();}}}}// 为一个客户端服务的线程class ClientThread extends Thread {private Socket socket;private BufferedReader reader;private PrintWriter writer;private User user;public BufferedReader getReader() {return reader;}public PrintWriter getWriter() {return writer;}public User getUser() {return user;}// 客户端线程的构造方法public ClientThread(Socket socket) {try {this.socket = socket;reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));writer = new PrintWriter(socket.getOutputStream());// 接收客户端的基本用户信息String inf = reader.readLine();StringTokenizer st = new StringTokenizer(inf, "@");user = new User(st.nextToken(), st.nextToken());// 反馈连接成功信息writer.println(user.getName() + user.getIp() + "与服务器连接成功!");writer.flush();// 反馈当前在线用户信息if (clients.size() > 0) {String temp = "";for (int i = clients.size() - 1; i >= 0; i--) {temp += (clients.get(i).getUser().getName() + "/" + clients.get(i).getUser().getIp())+ "@";}writer.println("USERLIST@" + clients.size() + "@" + temp);writer.flush();}// 向所有在线用户发送该用户上线命令for (int i = clients.size() - 1; i >= 0; i--) {clients.get(i).getWriter().println("ADD@" + user.getName() + user.getIp());clients.get(i).getWriter().flush();}} catch (IOException e) {e.printStackTrace();}}@SuppressWarnings("deprecation")public void run() {// 不断接收客户端的消息,进行处理。String message = null;while (true) {try {message = reader.readLine();// 接收客户端消息if (message.equals("CLOSE"))// 下线命令{contentArea.append(this.getUser().getName()+ this.getUser().getIp() + "下线!\r\n");// 断开连接释放资源reader.close();writer.close();socket.close();// 向所有在线用户发送该用户的下线命令for (int i = clients.size() - 1; i >= 0; i--) {clients.get(i).getWriter().println("DELETE@" + user.getName());clients.get(i).getWriter().flush();}listModel.removeElement(user.getName());// 更新在线列表// 删除此条客户端服务线程for (int i = clients.size() - 1; i >= 0; i--) {if (clients.get(i).getUser() == user) {ClientThread temp = clients.get(i);clients.remove(i);// 删除此用户的服务线程temp.stop();// 停止这条服务线程return;}}} else {dispatcherMessage(message);// 转发消息}} catch (IOException e) {e.printStackTrace();}}}// 转发消息public void dispatcherMessage(String message) {StringTokenizer stringTokenizer = new StringTokenizer(message, "@");String source = stringTokenizer.nextToken();String owner = stringTokenizer.nextToken();String content = stringTokenizer.nextToken();message = source + ":" + content;contentArea.append(message + "\r\n");if (owner.equals("ALL")) {// 群发for (int i = clients.size() - 1; i >= 0; i--) {clients.get(i).getWriter().println(message + "(多人发送)");clients.get(i).getWriter().flush();}}}}
}
Client_more.java
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.StringTokenizer;import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSplitPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;public class Client_more{private JFrame frame;private JList userList;private JTextArea textArea;private JTextField textField;private JTextField txt_port;private JTextField txt_hostIp;private JTextField txt_name;private JButton btn_start;private JButton btn_stop;private JButton btn_send;private JPanel northPanel;private JPanel southPanel;private JScrollPane rightScroll;private JScrollPane leftScroll;private JSplitPane centerSplit;private DefaultListModel listModel;private boolean isConnected = false;private Socket socket;private PrintWriter writer;private BufferedReader reader;private MessageThread messageThread;// 负责接收消息的线程private Map<String, User> onLineUsers = new HashMap<String, User>();// 所有在线用户// 主方法,程序入口public static void main(String[] args) {new Client_more();}// 执行发送public void send() {if (!isConnected) {JOptionPane.showMessageDialog(frame, "还没有连接服务器,无法发送消息!", "错误",JOptionPane.ERROR_MESSAGE);return;}String message = textField.getText().trim();if (message == null || message.equals("")) {JOptionPane.showMessageDialog(frame, "消息不能为空!", "错误",JOptionPane.ERROR_MESSAGE);return;}sendMessage(frame.getTitle() + "@" + "ALL" + "@" + message);textField.setText(null);}// 构造方法public Client_more() {textArea = new JTextArea();textArea.setEditable(false);textArea.setForeground(Color.blue);textField = new JTextField();txt_port = new JTextField("6666");txt_hostIp = new JTextField("127.0.0.1");txt_name = new JTextField("吴承潜");btn_start = new JButton("连接");btn_stop = new JButton("断开");btn_send = new JButton("发送");listModel = new DefaultListModel();userList = new JList(listModel);northPanel = new JPanel();northPanel.setLayout(new GridLayout(1, 7));northPanel.add(new JLabel("端口"));northPanel.add(txt_port);northPanel.add(new JLabel("服务器IP"));northPanel.add(txt_hostIp);northPanel.add(new JLabel("姓名"));northPanel.add(txt_name);northPanel.add(btn_start);northPanel.add(btn_stop);northPanel.setBorder(new TitledBorder("连接信息"));rightScroll = new JScrollPane(textArea);rightScroll.setBorder(new TitledBorder("消息显示区"));leftScroll = new JScrollPane(userList);leftScroll.setBorder(new TitledBorder("在线用户"));southPanel = new JPanel(new BorderLayout());southPanel.add(textField, "Center");southPanel.add(btn_send, "East");southPanel.setBorder(new TitledBorder("写消息"));centerSplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftScroll,rightScroll);centerSplit.setDividerLocation(100);frame = new JFrame("客户机");// 更改JFrame的图标:// frame.setIconImage(Toolkit.getDefaultToolkit().createImage(Client.class.getResource("qq.png")));frame.setLayout(new BorderLayout());frame.add(northPanel, "North");frame.add(centerSplit, "Center");frame.add(southPanel, "South");frame.setSize(600, 400);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);// 写消息的文本框中按回车键时事件textField.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent arg0) {send();}});// 单击发送按钮时事件btn_send.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {send();}});// 单击连接按钮时事件btn_start.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {int port;if (isConnected) {JOptionPane.showMessageDialog(frame, "已处于连接上状态,不要重复连接!","错误", JOptionPane.ERROR_MESSAGE);return;}try {try {port = Integer.parseInt(txt_port.getText().trim());} catch (NumberFormatException e2) {throw new Exception("端口号不符合要求!端口为整数!");}String hostIp = txt_hostIp.getText().trim();String name = txt_name.getText().trim();if (name.equals("") || hostIp.equals("")) {throw new Exception("姓名、服务器IP不能为空!");}boolean flag = connectServer(port, hostIp, name);if (flag == false) {throw new Exception("与服务器连接失败!");}frame.setTitle(name);JOptionPane.showMessageDialog(frame, "成功连接!");} catch (Exception exc) {JOptionPane.showMessageDialog(frame, exc.getMessage(),"错误", JOptionPane.ERROR_MESSAGE);}}});// 单击断开按钮时事件btn_stop.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {if (!isConnected) {JOptionPane.showMessageDialog(frame, "已处于断开状态,不要重复断开!","错误", JOptionPane.ERROR_MESSAGE);return;}try {boolean flag = closeConnection();// 断开连接if (flag == false) {throw new Exception("断开连接发生异常!");}JOptionPane.showMessageDialog(frame, "成功断开!");} catch (Exception exc) {JOptionPane.showMessageDialog(frame, exc.getMessage(),"错误", JOptionPane.ERROR_MESSAGE);}}});// 关闭窗口时事件frame.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {if (isConnected) {closeConnection();// 关闭连接}System.exit(0);// 退出程序}});}/*** 连接服务器* * @param port* @param hostIp* @param name*/public boolean connectServer(int port, String hostIp, String name) {// 连接服务器try {socket = new Socket(hostIp, port);// 根据端口号和服务器ip建立连接writer = new PrintWriter(socket.getOutputStream());reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 发送客户端用户基本信息(用户名和ip地址)sendMessage(name + "@" + socket.getLocalAddress().toString());// 开启接收消息的线程messageThread = new MessageThread(reader, textArea);messageThread.start();isConnected = true;// 已经连接上了return true;} catch (Exception e) {textArea.append("与端口号为:" + port + " IP地址为:" + hostIp+ " 的服务器连接失败!" + "\r\n");isConnected = false;// 未连接上return false;}}/*** 发送消息* * @param message*/public void sendMessage(String message) {writer.println(message);writer.flush();}/*** 客户端主动关闭连接*/@SuppressWarnings("deprecation")public synchronized boolean closeConnection() {try {sendMessage("CLOSE");// 发送断开连接命令给服务器messageThread.stop();// 停止接受消息线程// 释放资源if (reader != null) {reader.close();}if (writer != null) {writer.close();}if (socket != null) {socket.close();}isConnected = false;return true;} catch (IOException e1) {e1.printStackTrace();isConnected = true;return false;}}// 不断接收消息的线程class MessageThread extends Thread {private BufferedReader reader;private JTextArea textArea;// 接收消息线程的构造方法public MessageThread(BufferedReader reader, JTextArea textArea) {this.reader = reader;this.textArea = textArea;}// 被动的关闭连接public synchronized void closeCon() throws Exception {// 清空用户列表listModel.removeAllElements();// 被动的关闭连接释放资源if (reader != null) {reader.close();}if (writer != null) {writer.close();}if (socket != null) {socket.close();}isConnected = false;// 修改状态为断开}public void run() {String message = "";while (true) {try {message = reader.readLine();StringTokenizer stringTokenizer = new StringTokenizer(message, "/@");String command = stringTokenizer.nextToken();// 命令if (command.equals("CLOSE"))// 服务器已关闭命令{textArea.append("服务器已关闭!\r\n");closeCon();// 被动的关闭连接return;// 结束线程} else if (command.equals("ADD")) {// 有用户上线更新在线列表String username = "";String userIp = "";if ((username = stringTokenizer.nextToken()) != null&& (userIp = stringTokenizer.nextToken()) != null) {User user = new User(username, userIp);onLineUsers.put(username, user);listModel.addElement(username);}} else if (command.equals("DELETE")) {// 有用户下线更新在线列表String username = stringTokenizer.nextToken();User user = (User) onLineUsers.get(username);onLineUsers.remove(user);listModel.removeElement(username);} else if (command.equals("USERLIST")) {// 加载在线用户列表int size = Integer.parseInt(stringTokenizer.nextToken());String username = null;String userIp = null;for (int i = 0; i < size; i++) {username = stringTokenizer.nextToken();userIp = stringTokenizer.nextToken();User user = new User(username, userIp);onLineUsers.put(username, user);listModel.addElement(username);}} else if (command.equals("MAX")) {// 人数已达上限textArea.append(stringTokenizer.nextToken()+ stringTokenizer.nextToken() + "\r\n");closeCon();// 被动的关闭连接JOptionPane.showMessageDialog(frame, "服务器缓冲区已满!", "错误",JOptionPane.ERROR_MESSAGE);return;// 结束线程} else {// 普通消息textArea.append(message + "\r\n");}} catch (IOException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}}}
}