本程序在程序/joffy/article/details/18079331的基础是上添加了私聊,踢出用户两个功能。
由客户端和服务器端构成程序,程序借助Json包处理数据,项目需要导入包。
程序具体流程看注释。
用户 Bean
package com.server;import .Socket;/*** 用户 JavaBean*/public class User {private String UserName = "";private Socket socket;public String getUserName() {return UserName;}public void setUserName(String userName) {UserName = userName;}public Socket getSocket() {return socket;}public void setSocket(Socket socket) {this.socket = socket;}}
服务器类
package com.server;import java.io.*;import .*;import java.util.*;import java.awt.*;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.*;import net.sf.json.JSONObject;/*** 服务器类,继承JFrame,实现窗口化界面*/public class Server extends JFrame {// 在线用户列表ArrayList<User> clientList = new ArrayList<User>();// 在线用户名列表ArrayList<String> usernamelist = new ArrayList<String>();// 创建一个信息显示框private JTextArea jta = new JTextArea();// 用于要踢除用户名的输入框private JTextField jtf=new JTextField();// 踢除用户名private String usernameOut=null;// 声明一个用户对象,该类里面有两个变量 socket,username;private User user = null;// 声明一个输出流DataOutputStream output = null;// 声明一个输入流DataInputStream input = null;public static void main(String[] args) {new Server();}/*** 服务器构造方法, 绘画图形界面 , 监听socket连接*/public Server() {// 设置信息显示框版面setLayout(new BorderLayout());add(new JScrollPane(jta), BorderLayout.CENTER);jta.setEditable(false);jta.setFont(new Font("", 0, 18));//设置要踢除用户的输入框jtf.setFont(new Font("", 0, 18));final JPanel p = new JPanel();p.setLayout(new BorderLayout());JLabel jLabel = new JLabel("输入要踢除用户的名称,按回车发送");jLabel.setFont(new Font("", 0, 20));p.add(jLabel, BorderLayout.WEST);p.add(jtf, BorderLayout.CENTER);jtf.setHorizontalAlignment(JTextField.LEFT);add(p, BorderLayout.SOUTH);jtf.addActionListener(new ButtonListener());setTitle("服务器 ");setSize(800, 400);setLocation(100, 500);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setVisible(true); //try {// 创建一个服务器socket,绑定端口8000ServerSocket serverSocket = new ServerSocket(8080);// 打印启动时间jta.append("服务器启动时间 " + new Date() + "\n\n");// 无限循环监听是否有新的客户端连接while (true) {// 监听一个新的连接Socket socket = serverSocket.accept();if (socket != null) {// 获取上线用户的信息input = new DataInputStream(socket.getInputStream());String json = input.readUTF();JSONObject data = JSONObject.fromObject(json.toString());jta.append("用户 " + data.getString("username") + " 在" + new Date() + "登陆系统");// 显示用户登录ip地址InetAddress inetAddress = socket.getInetAddress();jta.append(",IP地址是:" + inetAddress.getHostAddress() + "\n\n");// 新建一个用户对象,设置socket,用户名user = new User();user.setSocket(socket);user.setUserName(data.getString("username"));// 加入在线用户组列表clientList.add(user);// 加入用户名列表(用户显示在客户端的用户列表)usernamelist.add(data.getString("username"));}// 用户上线提示,打包成json格式数据JSONObject online = new JSONObject();online.put("userlist", usernamelist);online.put("msg", user.getUserName() + "上线了");// 提示所有用户有新的用户上线for (int i = 0; i < clientList.size(); i++) {try {User user = clientList.get(i);// 获取每一个用户的socket,得到输出流,output = new DataOutputStream(user.getSocket().getOutputStream());// 向每个用户端发送数据output.writeUTF(online.toString());} catch (IOException ex) {System.err.println(ex);}}// 该socket作为参数,为当前连接用户创建一个线程,用于监听该socket的数据HandleAClient task = new HandleAClient(socket);new Thread(task).start();}} catch (IOException ex) {System.err.println(ex);}}/*** 自定义用户线程类 ,判断是否私聊, 提示用户下线*/class HandleAClient implements Runnable {// 已连接的cocketprivate Socket socket;public HandleAClient(Socket socket) {this.socket = socket;}public void run() {try {// 获取本线程监听的socket客户端的输入流DataInputStream inputFromClient = new DataInputStream(socket.getInputStream());// 循环监听while (true) {// 获取客户端的数据String json = inputFromClient.readUTF();JSONObject data = JSONObject.fromObject(json.toString());// 私聊标记变量boolean isPrivate = false;// 判断是否私聊for (int i = 0; i < clientList.size(); i++) {// 私聊,将获取的数据转发给指定用户// 通过用户名比较,存在该用户名if (clientList.get(i).getUserName().equals(data.getString("isPrivChat"))) {// 处理聊天内容,这是私聊内容String msg = "来自 " + data.getString("username") + " 私发给你的消息,其它用户看不到,"+ data.getString("time") + ":\n" + data.getString("msg");// 将消息打包成json格式数据发给指定客户端packMsg(data, i, msg);i++;// 标记私聊,结束此次消息发送过程isPrivate = true;break;}}// 群聊,将获取的数据转发给每一个用户if (isPrivate == false) {for (int i = 0; i < clientList.size();) {// 将聊天的信息和用户列表打包成json格式数据发给每个客户端String msg = data.getString("username") + " " + data.getString("time") + ":\n"+ data.getString("msg");packMsg(data, i, msg);i++;}}}} catch (IOException e) {System.err.println(e);}}// 将聊天的信息和用户列表打包成json格式数据发给一个客户端public void packMsg(JSONObject data, int i, String msg) {// 打包数据JSONObject chat = new JSONObject();chat.put("userlist", usernamelist);chat.put("msg", msg);// 获取一个用户User user = clientList.get(i);// 发送消息try {output = new DataOutputStream(user.getSocket().getOutputStream());output.writeUTF(chat.toString());} catch (IOException e) {// 出现异常,说明该用户已下线offLine(i);}}// 提示用户下线public void offLine(int i) {User outuser = clientList.get(i);// 从列表中移除clientList.remove(i);usernamelist.remove(outuser.getUserName());// 打包下线的发送消息JSONObject out = new JSONObject();out.put("userlist", usernamelist);out.put("msg", outuser.getUserName() + "下线了\n");// 提示每个用户有用户下线了for (int j = 0; j < clientList.size(); j++) {try {User user = clientList.get(j);output = new DataOutputStream(user.getSocket().getOutputStream());output.writeUTF(out.toString());} catch (IOException ex1) {}}}}/*** 监听输入框,踢除指定用户*/private class ButtonListener implements ActionListener {public void actionPerformed(ActionEvent e) {try {// 获取用户名usernameOut = jtf.getText().trim();boolean isUsernameOut=false;if(usernameOut!=null){for (int i = 0; i < clientList.size(); i++){if(clientList.get(i).getUserName().equals(usernameOut)){User outuser = clientList.get(i);// 打包被踢出的发送消息JSONObject out = new JSONObject();out.put("userlist", usernamelist);out.put("msg", outuser.getUserName() + "被踢出\n");// 提示每个用户有用户被踢出了for (int j = 0; j < clientList.size(); j++) {try {User user = clientList.get(j);output = new DataOutputStream(user.getSocket().getOutputStream());output.writeUTF(out.toString());} catch (IOException ex1) {}}// 从列表中移除clientList.remove(i);usernamelist.remove(outuser.getUserName());isUsernameOut=true;break;}}if(isUsernameOut==false){jta.append("不存在用户"+usernameOut+"\n");}}jtf.setText("");} catch (Exception ex) {System.err.println(ex);}}}}
客户端类
package com.client;import java.io.*;import .*;import java.text.SimpleDateFormat;import java.util.ArrayList;import java.util.Date;import java.awt.*;import java.awt.event.*;import javax.swing.*;import javax.swing.border.TitledBorder;import net.sf.json.JSONArray;import net.sf.json.JSONObject;/*** 客户端类,继承JFrame,实现窗口化界面*/public class Client extends JFrame {// 接受消息框private JTextField jtf = new JTextField();// 显示信息框private JTextArea jta = new JTextArea();// 显示用户列表private JTextArea userlist = new JTextArea(10, 10);// IOprivate DataOutputStream toServer;private DataInputStream fromServer;// 客户端用户名private String username = null;// 用户列表private ArrayList<String> list = new ArrayList<>();private boolean isStop = false;// 客户端构造函数,绘画界面public Client(final String username) {this.username = username;jtf.setFont(new Font("", 0, 18));jta.setFont(new Font("", 0, 18));userlist.setFont(new Font("", 0, 18));// 设置输入框final JPanel p = new JPanel();p.setLayout(new BorderLayout());JLabel jLabel = new JLabel("若私聊,在内容后添加(-用户名),否则按回车发送");jLabel.setFont(new Font("", 0, 20));p.add(jLabel, BorderLayout.WEST);p.add(jtf, BorderLayout.CENTER);jtf.setHorizontalAlignment(JTextField.LEFT);add(p, BorderLayout.SOUTH);// 在版面的中间增加一个聊天信息显示框add(new JScrollPane(jta), BorderLayout.CENTER);jta.setEditable(false);// 设置用户列表final JPanel p2 = new JPanel();p2.setLayout(new BorderLayout());p2.setBorder(new TitledBorder("在线用户"));p2.add(new JScrollPane(userlist), BorderLayout.CENTER);userlist.setEditable(false);add(p2, BorderLayout.EAST);setTitle("用户 " + username);setSize(800, 400);setLocation(100, 100);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 监听输入框事件jtf.addActionListener(new ButtonListener());setVisible(true);// 将消息打包成json数据格式JSONObject data = new JSONObject();data.put("username", username);data.put("msg", null);try {// 创建一个socket连接服务器Socket socket = new Socket("127.0.0.1", 8080);// 创建一个输入流用于获取服务器的数据fromServer = new DataInputStream(socket.getInputStream());// 创建一个输出流用于向服务器发送数据toServer = new DataOutputStream(socket.getOutputStream());// 向服务器发送数据(用户名)toServer.writeUTF(data.toString());// 开启一个线程,用于读取服务器发送过来的数据ReadThread readThread = new ReadThread();readThread.start();} catch (IOException ex) {// 出现异常,连接服务器失败jta.append("服务器无响应");}}// 监听输入框消息事件类private class ButtonListener implements ActionListener {public void actionPerformed(ActionEvent e) {try {if (isStop) {jta.append("你已被踢出,不能发送消息\n");} else {// 设置日期格式SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 设置日期格式String time = df.format(new Date()).toString();// 获取输入框信息String msg = jtf.getText().trim();if (msg.equals("")) {} else {// 处理消息,群聊或者私聊// 将消息拆分为聊天内容和用户名String[] msg1 = msg.split("-");// 打包数据成json格式JSONObject data1 = new JSONObject();data1.put("username", username);data1.put("msg", msg1[0]);data1.put("time", time);try {// 有用户名,将isPrivChat设置成用户名data1.put("isPrivChat", msg1[1]);} catch (ArrayIndexOutOfBoundsException e1) {// 无用户名,将isPrivChat设置空值data1.put("isPrivChat", "");}// 向服务器发送数据toServer.writeUTF(data1.toString());}}jtf.setText("");} catch (Exception ex) {System.err.println(ex);}}}// 读取服务器发来消息的线程类public class ReadThread extends Thread {public void run() {String json = null;try {// 无线循环监听服务器发来的数据while (true) {// 读取服务器的数据json = fromServer.readUTF();// 转化成json格式JSONObject data = JSONObject.fromObject(json.toString());if (json != null) {String mString = data.getString("msg");// 是否被踢出群聊if (mString.contains("被踢出") && mString.contains(username)) {isStop = true;jta.append(username + ",你已经被踢出群聊\n");} else {// 打印聊天信息或者系统提示信息jta.append(mString + "\n\n");// 强制使光标移动最底部jta.selectAll();// 刷新用户列表list.clear();JSONArray jsonArray = data.getJSONArray("userlist");// 获取用户列表for (int i = 0; i < jsonArray.size(); i++) {list.add(jsonArray.get(i).toString());}// 打印用户列表userlist.setText("人数有 " + jsonArray.size() + " 人\n");for (int i = 0; i < list.size(); i++) {userlist.append(list.get(i) + "\n");}}}}} catch (Exception e) {e.printStackTrace();}}}}
注册界面
package com.client;import java.awt.BorderLayout;import java.awt.FlowLayout;import java.awt.Font;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import javax.swing.JButton;import javax.swing.JFrame;import javax.swing.JLabel;import javax.swing.JPanel;import javax.swing.JTextField;/*** 注册用户类,JFrame实现窗口化界面*/public class Regist extends JFrame {public static void main(String[] args) {new Regist();}public Regist() {// 绘画界面final JTextField jtfname = new JTextField(13);jtfname.setFont(new Font("", 0, 15));final JPanel p1 = new JPanel();p1.setLayout(new BorderLayout());JLabel jLabel = new JLabel("输入用户名(回车登录)");jLabel.setFont(new Font("", 0, 20));p1.add(jLabel, BorderLayout.WEST);p1.add(jtfname, BorderLayout.CENTER);jtfname.setHorizontalAlignment(JTextField.LEFT);p1.setLayout(new FlowLayout(FlowLayout.LEFT, 10, 20));p1.setSize(300, 100);setLayout(new BorderLayout());add(p1, BorderLayout.CENTER);JLabel jLabel2 = new JLabel("欢迎来到多人聊天室 ");jLabel2.setFont(new Font("", 0, 18));add(jLabel2, BorderLayout.NORTH);setTitle("多人聊天室 ");setSize(500, 300);setLocation(100, 100);setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);setVisible(true);// 回车登陆jtfname.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent arg0) {// 获取用户名String username = jtfname.getText().trim();if (username.equals("")) {} else {// 关闭设置页面,启动聊天框页面setVisible(false);new Client(username);}}});}}
项目结构与需要导入的Json包