Java GUI编程——在线聊天室

引言

综合应用Java的GUI编程和网络编程,实现一个能够支持多组用户同时使用的聊天室软件。该聊天室具有比较友好的GUI界面,并使用C/S模式,支持多个用户同时使用,用户可以自己选择加入或者创建房间,和房间内的其他用户互发信息(文字和图片)

主要功能

客户端的功能主要包括如下的功能:
  • 选择连上服务端
  • 显示当前房间列表(包括房间号和房间名称)
  • 选择房间进入
  • 多个用户在线群聊
  • 可以发送表情(用本地的,实际上发送只发送表情的代码)
  • 退出房间
  • 选择创建房间
  • 房间里没人(房主退出),导致房间解散
  • 显示系统提示消息
  • 显示用户消息
  • 构造标准的消息结构发送
  • 维护GUI所需的数据模型
服务端的功能主要包括:
  • 维护用户信息和房间信息
  • 处理用户发送来的消息选择转发或者回复处理结果
  • 构造标准的消息结构发送

架构

整个程序采用C/S设计架构,分为一个服务端和多个客户端。服务端开放一个端口给所有开客户端,客户端连接该端口并收发信息,服务端在内部维护客户端的组,并对每一个客户端都用一个子线程来收发信息

基本类的设计

 User类
package User;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

/**
 * 
 * @author lannooo
 *
 */
public class User {
    private String name;
    private long id;
    private long roomId;
    private Socket socket;
    private BufferedReader br;
    private PrintWriter pw;

    /**
     * 
     * @param name: 设置user的姓名
     * @param id:设置user的id
     * @param socket:保存用户连接的socket
     * @throws IOException
     */
    public User(String name, long id, final Socket socket) throws IOException {
        this.name=name;
        this.id=id;
        this.socket=socket;
        this.br=new BufferedReader(new InputStreamReader(
                socket.getInputStream()));
        this.pw=new PrintWriter(socket.getOutputStream());

    }

    /**
     * 获得该用户的id
     * @return id
     */
    public long getId() {
        return id;
    }

    /**
     * 设置该用户的id
     * @param id 新的id
     */
    public void setId(long id) {
        this.id = id;
    }

    /**
     * 获得用户当前所在的房间号
     * @return roomId
     */
    public long getRoomId() {
        return roomId;
    }

    /**
     * 设置当前用户的所在的房间号
     * @param roomId
     */
    public void setRoomId(long roomId) {
        this.roomId = roomId;
    }

    /**
     * 设置当前用户在聊天室中的昵称
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 返回当前用户在房间中的昵称
     * @return
     */
    public String getName() {
        return name;
    }

    /**
     * 返回当前用户连接的socket实例
     * @return
     */
    public Socket getSocket() {
        return socket;
    }

    /**
     * 设置当前用户连接的socket
     * @param socket
     */
    public void setSocket(Socket socket) {
        this.socket = socket;
    }

    /**
     * 获得该用户的消息读取辅助类BufferedReader实例
     * @return
     */
    public BufferedReader getBr() {
        return br;
    }

    /**
     * 设置 用户的消息读取辅助类
     * @param br
     */
    public void setBr(BufferedReader br) {
        this.br = br;
    }

    /**
     * 获得消息写入类实例
     * @return
     */
    public PrintWriter getPw() {
        return pw;
    }

    /**
     * 设置消息写入类实例
     * @param pw
     */
    public void setPw(PrintWriter pw) {
        this.pw = pw;
    }

    /**
     * 重写了用户类打印的函数
     */
    @Override
    public String toString() {
        return "#User"+id+"#"+name+"[#Room"+roomId+"#]<socket:"+socket+">";
    }
}
Room类
package Room;

import java.util.ArrayList;
import java.util.List;

import User.User;

/**
 * 
 * @author lannooo
 *
 */
public class Room {
    private String name;
    private long roomId;
    private ArrayList<User> list;
    private int totalUsers;

    /**
     * 获得房间的名字
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置房间的新名字
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获得房间的id号
     * @return
     */
    public long getRoomId() {
        return roomId;
    }

    /**
     * 设置房间的id
     * @param roomId
     */
    public void setRoomId(long roomId) {
        this.roomId = roomId;
    }

    /**
     * 向房间中加入一个新用户
     * @param user
     */
    public void addUser(User user) {
        if(!list.contains(user)){
            list.add(user);
            totalUsers++;
        }else{
            System.out.println("User is already in Room<"+name+">:"+user);
        }
    }

    /**
     * 从房间中删除一个用户
     * @param user
     * @return 目前该房间中的总用户数目
     */
    public int delUser(User user){
        if(list.contains(user)){
            list.remove(user);
            return --totalUsers;
        }else{
            System.out.println("User is not in Room<"+name+">:"+user);
            return totalUsers;
        }
    }

    /**
     * 获得当前房间的用户列表
     * @return
     */
    public ArrayList<User> getUsers(){
        return list;
    }

    /**
     * 获得当前房间的用户昵称的列表
     * @return
     */
    public String[] getUserNames(){
        String[] userList = new String[list.size()];
        int i=0;
        for(User each: list){
            userList[i++]=each.getName();
        }
        return userList;
    }

    /**
     * 使用房间的名称和id来new一个房间
     * @param name
     * @param roomId
     */
    public Room(String name, long roomId) {
        this.name=name;
        this.roomId=roomId;
        this.totalUsers=0;
        list = new ArrayList<>();
    }
}
RoomList类
package Room;

import java.awt.image.DirectColorModel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import User.User;

/**
 * 
 * @author lannooo
 *
 */
public class RoomList {
    private HashMap<Long, Room> map;
    private long unusedRoomId;
    public static long MAX_ROOMS = 9999;
    private int totalRooms;

    /**
     * 未使用的roomid从1算起,起始的房间总数为0
     */
    public RoomList(){
        map = new HashMap<>();
        unusedRoomId = 1;
        totalRooms = 0;
    }

    /**
     * 创建一个新的房间,使用未使用的房间号进行创建,如果没有可以使用的则就创建失败
     * @param name: 房间的名字
     * @return 创建的房间的id
     */
    public long createRoom(String name){
        if(totalRooms<MAX_ROOMS){
            if(name.length()==0){
                name = ""+unusedRoomId;
            }
            Room room = new Room(name, unusedRoomId);
            map.put(unusedRoomId, room);
            totalRooms++;
            return unusedRoomId++;
        }else{
            return -1;
        }
    }
    /**
     * 用户加入一个房间
     * @param user
     * @param roomID
     * @return
     */
    public boolean join(User user, long roomID){
        if(map.containsKey(roomID)){
            map.get(roomID).addUser(user);
            return true;
        }else{
            return false;
        }
    }

    /**
     * 用户退出他的房间
     * @param user
     * @param roomID
     * @return
     */
    public int esc(User user, long roomID){
        if(map.containsKey(roomID)){
            int number = map.get(roomID).delUser(user);
            /*如果这个房间剩下的人数为0,那么删除该房间*/
            if(number==0){
                map.remove(roomID);
                totalRooms--;
                return 0;
            }
            return 1;
        }else{
            return -1;
        }
    }
    /**
     * 列出所有房间的列表,返回一个二维数组,strings[i][0]放房间的id,string[i][1]放房间的name
     * @return
     */
    public String[][] listRooms(){
        String[][] strings = new String[totalRooms][2];
        int i=0;
        /*将map转化为set并使用迭代器来遍历*/
        Set<Entry<Long, Room>> set = map.entrySet();
        Iterator<Entry<Long, Room>> iterator = set.iterator();
        while(iterator.hasNext()){
            Map.Entry<Long, Room> entry = iterator.next();
            long key = entry.getKey();
            Room value = entry.getValue();
            strings[i][0]=""+key;
            strings[i][1]=value.getName();
        }
        return strings;
    }

    /**
     * 通过roomID来获得房间
     * @param roomID
     * @return
     */
    public Room getRoom(long roomID){
        if(map.containsKey(roomID)){
            return map.get(roomID);
        }
        else 
            return null;
    }
}

服务端

Server
package Server;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import org.json.*;

import Room.Room;
import Room.RoomList;
import User.User;
/**
 * 
 * @author lannooo
 *
 */
public class Server {
    private ArrayList<User> allUsers;
    private RoomList rooms;
    private int port;
    private ServerSocket ss;
    private long unusedUserID;
    public final long MAX_USERS = 999999;

    /**
     * 通过port号来构造服务器端对象
     * 维护一个总的用户列表和一个房间列表
     * @param port
     * @throws Exception
     */
    public Server(int port) throws Exception {
        allUsers = new ArrayList<>();
        rooms = new RoomList();
        this.port=port;
        unusedUserID=1;
        ss = new ServerSocket(port);
        System.out.println("Server is builded!");
    }

    /**
     * 获得下一个可用的用户id
     * @return
     */
    private long getNextUserID(){
        if(unusedUserID < MAX_USERS)
            return unusedUserID++;
        else
            return -1;
    }

    /**
     * 开始监听,当接受到新的用户连接,就创建一个新的用户,并添加到用户列表中
     * 然后创建一个新的服务线程用于收发该用户的消息
     * @throws Exception
     */
    public void startListen() throws Exception{
        while(true){
            Socket socket = ss.accept();
            long id = getNextUserID();
            if(id != -1){
                User user = new User("User"+id, id, socket);
                System.out.println(user.getName() + " is login...");
                allUsers.add(user);
                ServerThread thread = new ServerThread(user, allUsers, rooms);
                thread.start();
            }else{
                System.out.println("Server is full!");
                socket.close();
            }
        }
    }

    /**
     * 测试用main方法,设置侦听端口为9999,并开始监听
     * @param args
     */
    public static void main(String[] args) {
        try {
            Server server = new Server(9999);
            server.startListen();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
ServerThread
package Server;

import java.io.IOException;
import java.io.PrintWriter;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import Room.Room;
import Room.RoomList;
import User.User;

/**
 * 
 * @author lannooo
 *
 */
public class ServerThread extends Thread {
    private User user;
    private ArrayList<User> userList;/*保存用户列表*/
    private RoomList map;            /*保存房间列表*/
    private long roomId;
    private PrintWriter pw;
    /**
     * 通过用户的对象实例、全局的用户列表、房间列表进行构造
     * @param user
     * @param userList
     * @param map
     */
    public ServerThread(User user, 
            ArrayList<User> userList, RoomList map){
        this.user=user;
        this.userList=userList;
        this.map=map;
        pw=null;
        roomId = -1;
    }

    /**
     * 线程运行部分,持续读取用户socket发送来的数据,并解析
     */
    public void run(){
        try{
            while (true) {
                String msg=user.getBr().readLine();
                System.out.println(msg);    /*解析用户的数据格式*/
                parseMsg(msg);
            }
        }catch (SocketException se) {   /*处理用户断开的异常*/
            System.out.println("user "+user.getName()+" logout.");

        }catch (Exception e) {  /*处理其他异常*/
            e.printStackTrace();
        }finally {
            try {
                /*
                 * 用户断开或者退出,需要把该用户移除
                 * 并关闭socket
                 */
                remove(user);
                user.getBr().close();
                user.getSocket().close();
            } catch (IOException ioe) {
                ioe.printStackTrace();
            }
        }
    }

    /**
     * 用正则表达式匹配数据的格式,根据不同的指令类型,来调用相应的方法处理
     * @param msg
     */
    private void parseMsg(String msg){
        String code = null;
        String message=null;
        if(msg.length()>0){
            /*匹配指令类型部分的字符串*/
            Pattern pattern = Pattern.compile("<code>(.*)</code>");
            Matcher matcher = pattern.matcher(msg);
            if(matcher.find()){
                code = matcher.group(1);
            }
            /*匹配消息部分的字符串*/
            pattern = Pattern.compile("<msg>(.*)</msg>");
            matcher = pattern.matcher(msg);
            if(matcher.find()){
                message = matcher.group(1);
            }

            switch (code) {
            case "join":
                // add to the room
                // code = 1, 直接显示在textArea中
                // code = 11, 在list中加入
                // code = 21, 把当前房间里的所有用户返回给client
                if(roomId == -1){
                    roomId = Long.parseLong(message);
                    map.join(user, roomId);
                    sendRoomMsgExceptSelf(buildCodeWithMsg("<name>"+user.getName()+"</name><id>"+user.getId()+"</id>", 11));
                    // 这个消息需要加入房间里已有用户的列表
                    returnMsg(buildCodeWithMsg("你加入了房间:" + map.getRoom(roomId).getName(), 1));
                    returnMsg(buildCodeWithMsg(getMembersInRoom(), 21));
                }else{
                    map.esc(user, roomId);
                    sendRoomMsg(buildCodeWithMsg(""+user.getId(), 12));
                    long oldRoomId = roomId;
                    roomId = Long.parseLong(message);
                    map.join(user, roomId);
                    sendRoomMsgExceptSelf(buildCodeWithMsg("<name>"+user.getName()+"</name><id>"+user.getId()+"</id>", 11));
                    returnMsg(buildCodeWithMsg("你退出房间:" + map.getRoom(oldRoomId).getName() + ",并加入了房间:" + roomId,1));
                    returnMsg(buildCodeWithMsg(getMembersInRoom(), 21));
                }
                break;
            case "esc":
                // delete from room list
                // code = 2, 弹窗提示
                // code = 12, 对所有该房间的其他用户发送该用户退出房间的信息,从list中删除
                if(roomId!=-1){
                    int flag=map.esc(user, roomId);
                    sendRoomMsgExceptSelf(buildCodeWithMsg(""+user.getId(), 12));
                    long oldRoomId=roomId;
                    roomId = -1;
                    returnMsg(buildCodeWithMsg("你已经成功退出房间,不会收到消息", 2));
                    if(flag==0){
                        sendMsg(buildCodeWithMsg(""+oldRoomId, 13));
                    }
                }else{
                    returnMsg(buildCodeWithMsg("你尚未加入任何房间", 2));
                }
                break;
            case "list":
                // list all the rooms
                // code = 3, 在客户端解析rooms,并填充roomlist
                returnMsg(buildCodeWithMsg(getRoomsList(), 3));
                break;
            case "message":
                // send message
                // code = 4, 自己收到的话,打印的是‘你说:....’否则打印user id对应的name
                sendRoomMsg(buildCodeWithMsg("<from>"+user.getId()+"</from><smsg>"+message+"</smsg>", 4));
                break;
            case "create":
                // create a room 
                // code=5,提示用户进入了房间
                // code=15,需要在其他所有用户的room列表中更新
                roomId = map.createRoom(message);
                map.join(user, roomId);
                sendMsg(buildCodeWithMsg("<rid>"+roomId+"</rid><rname>"+message+"</rname>", 15));
                returnMsg(buildCodeWithMsg("你进入了创建的房间:"+map.getRoom(roomId).getName(), 5));
                returnMsg(buildCodeWithMsg(getMembersInRoom(), 21));
                break;
            case "setname":
                // set name for user
                // code=16,告诉房间里的其他人,你改了昵称
                user.setName(message);
                sendRoomMsg(buildCodeWithMsg("<id>"+user.getId()+"</id><name>"+message+"</name>", 16));
                break;
            default:
                // returnMsg("something unknown");
                System.out.println("not valid message from user"+user.getId());
                break;
            }
        }   
    }

    /**
     * 获得该用户房间中的所有用户列表,并构造成一定格式的消息返回
     * @return
     */
    private String getMembersInRoom(){
        /*先从room列表获得该用户的room*/
        Room room = map.getRoom(roomId);
        StringBuffer stringBuffer = new StringBuffer();
        if(room != null){
            /*获得房间中所有的用户的列表,然后构造成一定的格式发送回去*/
            ArrayList<User> users = room.getUsers();
            for(User each: users){
                stringBuffer.append("<member><name>"+each.getName()+
                             "</name><id>"+each.getId()+"</id></member>");
            }
        }
        return stringBuffer.toString();
    }

    /**
     * 获得所有房间的列表,并构造成一定的格式
     * @return
     */
    private String getRoomsList(){
        String[][] strings = map.listRooms();
        StringBuffer sb = new StringBuffer();
        for(int i=0; i<strings.length; i++){
            sb.append("<room><rname>"+strings[i][1]+
                        "</rname><rid>"+strings[i][0]+"</rid></room>");
        }
        return sb.toString();
    }

    /**
     * 构造成一个统一的消息格式
     * @param msg
     * @param code
     * @return
     */
    private String buildCodeWithMsg(String msg, int code){
        return "<code>"+code+"</code><msg>"+msg+"</msg>\n";
    }

    /**
     * 这个是群发消息:全体用户,code>10
     * @param msg
     */
    private void sendMsg(String msg) {
//      System.out.println("In sendMsg()");
        /*取出用户列表中的每一个用户来发送消息*/
        for(User each:userList){
            try {
                pw=each.getPw();
                pw.println(msg);
                pw.flush();
                System.out.println(msg);
            } catch (Exception e) {
                System.out.println("exception in sendMsg()");
            }
        }
    }

    /**
     * 只对同一房间的用户发:code>10
     * @param msg
     */
    private void sendRoomMsg(String msg){
        /*先获得该用户的房间号,然后往该房间发送消息*/
        Room room = map.getRoom(roomId);
        if(room != null){
            ArrayList<User> users = room.getUsers();
            for(User each: users){
                pw = each.getPw();
                pw.println(msg);
                pw.flush();
            }
        }
    }
    /**
     * 向房间中除了该用户自己,发送消息
     * @param msg
     */
    private void sendRoomMsgExceptSelf(String msg){
        Room room = map.getRoom(roomId);
        if(room != null){
            ArrayList<User> users = room.getUsers();
            for(User each: users){
                if(each.getId()!=user.getId()){
                    pw = each.getPw();
                    pw.println(msg);
                    pw.flush();
                }
            }
        }
    }

    /**
     * 对于client的来信,返回一个结果,code<10
     * @param msg
     */
    private void returnMsg(String msg){
        try{
            pw = user.getPw();
            pw.println(msg);
            pw.flush();
        }catch (Exception e) {
            System.out.println("exception in returnMsg()");
        }
    }

    /**
     * 移除该用户,并向房间中其他用户发送该用户已经退出的消息
     * 如果房间中没人了,那么就更新房间列表给所有用户
     * @param user
     */
    private void remove(User user){
        if(roomId!=-1){
            int flag=map.esc(user, roomId);
            sendRoomMsgExceptSelf(buildCodeWithMsg(""+user.getId(), 12));
            long oldRoomId=roomId;
            roomId = -1;
            if(flag==0){
                sendMsg(buildCodeWithMsg(""+oldRoomId, 13));
            }
        }
        userList.remove(user);
    }
}

客户端

Client
package Client;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.Iterator;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
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.JScrollBar;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.text.BadLocationException;
import javax.swing.text.SimpleAttributeSet;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledDocument;


/**
 * 
 * @author lannooo
 *
 */
public class Client implements ActionListener{
    private JFrame frame;
    private Socket socket;
    private BufferedReader br;
    private PrintWriter pw;
    private String name;
    private HashMap<String, Integer> rooms_map;
    private HashMap<String, Integer> users_map;
    private JTextField host_textfield;
    private JTextField port_textfield;
    private JTextField text_field;
    private JTextField name_textfiled;
    private JLabel rooms_label;
    private JLabel users_label;
    private JList<String> roomlist;
    private JList<String> userlist;
    private JTextPane msgArea;
    private JScrollPane textScrollPane;
    private JScrollBar vertical;
    DefaultListModel<String> rooms_model;
    DefaultListModel<String> users_model;

    /*
     * 构造函数
     * 该客户端对象维护两个map,房间的hashmap和房间中用户的hashmap
     * 作为列表组件的数据模型
     */
    public Client(){
        rooms_map = new HashMap<>();
        users_map = new HashMap<>();
        initialize();
    }

    /**
     * 连接服务端,指定host和port
     * @param host
     * @param port
     * @return
     */
    public boolean connect(String host, int port){
        try {
            socket = new Socket(host, port);
            System.out.println("Connected to server!"+socket.getRemoteSocketAddress());
            br=new BufferedReader(new InputStreamReader(System.in));
            pw=new PrintWriter(socket.getOutputStream());
            /*
             * 创建一个接受和解析服务器消息的线程
             * 传入当前客户端对象的指针,作为句柄调用相应的处理函数
             */
            ClientThread thread = new ClientThread(socket, this);
            thread.start();

            return true;

        } catch (IOException e) {
            System.out.println("Server error");
            JOptionPane.showMessageDialog(frame, "服务器无法连接!");
            return false;
        }
    }

    /*当前进程作为只发送消息的线程,从命令行中获取输入*/
//  public void sendMsg(){
//      String msg;
//      try {
//          while(true){
//              msg = br.readLine();
//              pw.println(msg);
//              pw.flush();
//          }
//      } catch (IOException e) {
//          System.out.println("error when read msg and to send.");
//      }
//  }

    /**
     * 发给服务器的消息,先经过一定的格式构造再发送
     * @param msg
     * @param code
     */
    public void sendMsg(String msg, String code){
        try {
            pw.println("<code>"+code+"</code><msg>"+msg+"</msg>");
            pw.flush();
        } catch (Exception e) {
            //一般是没有连接的问题
            System.out.println("error in sendMsg()");
            JOptionPane.showMessageDialog(frame, "请先连接服务器!");
        }
    }

    /**
     * 窗口初始化
     */
    private void initialize() {
        /*设置窗口的UI风格和字体*/
        setUIStyle();
        setUIFont();

        JFrame frame = new JFrame("ChatOnline");
        JPanel panel = new JPanel();        /*主要的panel,上层放置连接区,下层放置消息区,
                                                  中间是消息面板,左边是room列表,右边是当前room的用户列表*/
        JPanel headpanel = new JPanel();    /*上层panel,用于放置连接区域相关的组件*/
        JPanel footpanel = new JPanel();    /*下层panel,用于放置发送信息区域的组件*/
        JPanel leftpanel = new JPanel();    /*左边panel,用于放置房间列表和加入按钮*/
        JPanel rightpanel = new JPanel();   /*右边panel,用于放置房间内人的列表*/

        /*最上层的布局,分中间,东南西北五个部分*/
        BorderLayout layout = new BorderLayout();
        /*格子布局,主要用来设置西、东、南三个部分的布局*/
        GridBagLayout gridBagLayout = new GridBagLayout();
        /*主要设置北部的布局*/
        FlowLayout flowLayout = new FlowLayout();
        /*设置初始窗口的一些性质*/
        frame.setBounds(100, 100, 800, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(panel);
        frame.setLayout(layout);
        /*设置各个部分的panel的布局和大小*/
        headpanel.setLayout(flowLayout);
        footpanel.setLayout(gridBagLayout);
        leftpanel.setLayout(gridBagLayout);
        rightpanel.setLayout(gridBagLayout);
        leftpanel.setPreferredSize(new Dimension(130, 0));
        rightpanel.setPreferredSize(new Dimension(130, 0));


        /*以下均是headpanel中的组件*/
        host_textfield = new JTextField("127.0.0.1");
        port_textfield = new JTextField("9999");
        name_textfiled = new JTextField("匿名");
        host_textfield.setPreferredSize(new Dimension(100, 25));
        port_textfield.setPreferredSize(new Dimension(70, 25));
        name_textfiled.setPreferredSize(new Dimension(150, 25));

        JLabel host_label = new JLabel("服务器IP");
        JLabel port_label = new JLabel("端口");
        JLabel name_label = new JLabel("昵称");

        JButton head_connect = new JButton("连接");
//      JButton head_change = new JButton("确认更改");
        JButton head_create = new JButton("创建房间");

        headpanel.add(host_label);
        headpanel.add(host_textfield);
        headpanel.add(port_label);
        headpanel.add(port_textfield);
        headpanel.add(head_connect);
        headpanel.add(name_label);
        headpanel.add(name_textfiled);
//      headpanel.add(head_change);
        headpanel.add(head_create);

        /*以下均是footpanel中的组件*/
        JButton foot_emoji = new JButton("表情");
        JButton foot_send = new JButton("发送");
        text_field = new JTextField();
        footpanel.add(text_field, new GridBagConstraints(0, 0, 1, 1, 100, 100, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        footpanel.add(foot_emoji, new GridBagConstraints(1, 0, 1, 1, 1.0, 1.0, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        footpanel.add(foot_send, new GridBagConstraints(2, 0, 1, 1, 1.0, 1.0, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));

        /*两边的格子中的组件*/
        rooms_label = new JLabel("当前房间数:0");
        users_label = new JLabel("房间内人数:0");
        JButton join_button = new JButton("加入房间");
        JButton esc_button = new JButton("退出房间");

        rooms_model = new DefaultListModel<>();
        users_model = new DefaultListModel<>();
//      rooms_model.addElement("房间1");
//      rooms_model.addElement("房间2");
//      rooms_model.addElement("房间3");
//      String fangjian = "房间1";
//      rooms_map.put(fangjian, 1);

        roomlist = new JList<>(rooms_model);
        userlist = new JList<>(users_model);

        JScrollPane roomListPane = new JScrollPane(roomlist);
        JScrollPane userListPane = new JScrollPane(userlist);

        leftpanel.add(rooms_label,  new GridBagConstraints(0, 0, 1, 1, 1, 1, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        leftpanel.add(join_button,  new GridBagConstraints(0, 1, 1, 1, 1, 1, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        leftpanel.add(esc_button,   new GridBagConstraints(0, 2, 1, 1, 1, 1, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        leftpanel.add(roomListPane, new GridBagConstraints(0, 3, 1, 1, 100, 100, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        rightpanel.add(users_label, new GridBagConstraints(0, 0, 1, 1, 1, 1, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));
        rightpanel.add(userListPane,new GridBagConstraints(0, 1, 1, 1, 100, 100, 
                GridBagConstraints.CENTER, GridBagConstraints.BOTH, new Insets(0, 0, 0, 0), 0, 0));

        /*中间的文本区组件*/
        msgArea = new JTextPane();
        msgArea.setEditable(false);
        textScrollPane = new JScrollPane();
        textScrollPane.setViewportView(msgArea);
        vertical = new JScrollBar(JScrollBar.VERTICAL);
        vertical.setAutoscrolls(true);
        textScrollPane.setVerticalScrollBar(vertical);

        /*设置顶层布局*/
        panel.add(headpanel, "North");
        panel.add(footpanel, "South");
        panel.add(leftpanel, "West");
        panel.add(rightpanel, "East");
        panel.add(textScrollPane, "Center");

        /*注册各种事件*/
        /*连接服务器*/
        head_connect.addActionListener(this);
        /*发送消息,如果没有连接则会弹窗提示*/
        foot_send.addActionListener(this);
        /*改名字*/
//      head_change.addActionListener(this);
        /*创建房间*/
        head_create.addActionListener(this);
        /*发送表情*/
        foot_emoji.addActionListener(this);
        /*加入room*/
        join_button.addActionListener(this);
        /*退出房间*/
        esc_button.addActionListener(this);

        /*最终显示*/
        frame.setVisible(true);
    }

    /**
     * 事件监听处理
     */
    @Override
    public void actionPerformed(ActionEvent e) {
        String cmd = e.getActionCommand();
        switch (cmd) {
        case "连接":  /*点击连接按钮*/
            String strhost = host_textfield.getText();
            String strport = port_textfield.getText();
            connect(strhost, Integer.parseInt(strport));
            String nameSeted = JOptionPane.showInputDialog("请输入你的昵称:"); /*提示输入昵称*/
            name_textfiled.setText(nameSeted);
            name_textfiled.setEditable(false);
            port_textfield.setEditable(false);
            host_textfield.setEditable(false);
            /*发送设置姓名的消息和列出用户列表的消息*/
            sendMsg(nameSeted, "setname");
            sendMsg("", "list");
            break;
//      case "确认更改":
//          String strname = name_textfiled.getText();
//          name = strname;
//          sendMsg(strname, "setname");
//          break;
        case "加入房间":    /*选择房间后,点击加入房间按钮*/
            String selected = roomlist.getSelectedValue();
            if(rooms_map.containsKey(selected)){
                sendMsg(""+rooms_map.get(selected), "join");
            }
            break;
        case "退出房间":    /*点击退出房间的按钮*/
            sendMsg("", "esc");
            break;
        case "发送":      /*点击发送消息的按钮*/
            String text = text_field.getText();
            text_field.setText("");
            sendMsg(text, "message");
            break;
        case "表情":      /*发送表情,新建一个表情窗口,并直接在表情窗口中处理消息发送*/
            IconDialog dialog = new IconDialog(frame, this);
            break;
        case "创建房间":    /*点击创建房间的按钮,弹出提示框数据房间名称*/
            String string = JOptionPane.showInputDialog("请输入你的房间名称");
            if(string==null || string.equals("")){
                string = name+(int)(Math.random()*10000)+"的房间";
            }
            sendMsg(string, "create");
            break;
        default:
            break;
        }

    }


    /*很多辅助和clientThread互动的*/

    /**
     * 加入用户,通过正则表达式,匹配消息内容中的用户信息
     * @param content
     */
    public void addUser(String content){
        if(content.length()>0){
            Pattern pattern = Pattern.compile("<name>(.*)</name><id>(.*)</id>");
            Matcher matcher = pattern.matcher(content);
            if(matcher.find()){
                /*
                 * 获得用户的name和id
                 * 加入用户列表
                 * 在消息区显示系统提示
                 */
                String name = matcher.group(1);
                String id = matcher.group(2);
                insertUser(Integer.parseInt(id), name);
                insertMessage(textScrollPane, msgArea, null, "系统:", name+"加入了聊天室");
            }
        }
        users_label.setText("房间内人数:"+users_map.size()); /*更新房间内的人数*/
    }

    /**
     * 删除用户
     * @param content
     */
    public void delUser(String content){
        if(content.length()>0){
            int id = Integer.parseInt(content);
            /*
             * 从维护的用户map中取得所有的用户名字,然后去遍历匹配的用户
             * 匹配到的用户名字从相应的数据模型中移除
             * 并从map中移除,并在消息框中提示系统消息
             */
            Set<String> set = users_map.keySet();
            Iterator<String> iter = set.iterator();
            String name=null;
            while(iter.hasNext()){
                name = iter.next();
                if(users_map.get(name)==id){
                    users_model.removeElement(name);
                    break;
                }
            }
            users_map.remove(name);
            insertMessage(textScrollPane, msgArea, null, "系统:", name+"退出了聊天室");
        }
        users_label.setText("房间内人数:"+users_map.size());
    }


    /**
     * 更新用户信息
     * @param content
     */
    public void updateUser(String content){
        if(content.length()>0){
            Pattern pattern = Pattern.compile("<id>(.*)</id><name>(.*)</name>");
            Matcher matcher = pattern.matcher(content);
            if(matcher.find()){
                String id = matcher.group(1);
                String name = matcher.group(2);
                insertUser(Integer.parseInt(id), name);
            }
        }
    }

    /**
     * 列出所有用户
     * @param content
     */
    public void listUsers(String content){
        String name = null;
        String id=null;
        Pattern rough_pattern=null;
        Matcher rough_matcher=null;
        Pattern detail_pattern=null;
        /*
         * 先用正则表达式匹配用户信息
         * 然后插入数据模型中
         * 并更新用户数据模型中的条目
         */
        if(content.length()>0){
            rough_pattern = Pattern.compile("<member>(.*?)</member>");
            rough_matcher = rough_pattern.matcher(content);
            while(rough_matcher.find()){
                String detail = rough_matcher.group(1);
                detail_pattern = Pattern.compile("<name>(.*)</name><id>(.*)</id>");
                Matcher detail_matcher = detail_pattern.matcher(detail);
                if(detail_matcher.find()){
                    name = detail_matcher.group(1);
                    id = detail_matcher.group(2);
                    insertUser(Integer.parseInt(id), name);
                }
            }
        }
        users_label.setText("房间内人数:"+users_map.size());
    }

    /**
     * 直接在textarea中显示消息
     * @param content
     */
    public void updateTextArea(String content){
        insertMessage(textScrollPane, msgArea, null, "系统:", content);
    }

    /**
     * 在textarea中显示其他用户的消息
     * 先用正则匹配,再显示消息
     * 其中还需要匹配emoji表情的编号
     * @param content
     */
    public void updateTextAreaFromUser(String content){
        if(content.length()>0){
            Pattern pattern = Pattern.compile("<from>(.*)</from><smsg>(.*)</smsg>");
            Matcher matcher = pattern.matcher(content);
            if(matcher.find()){
                String from = matcher.group(1);
                String smsg = matcher.group(2);
                String fromName = getUserName(from);
                if(fromName.equals(name))
                    fromName = "你";
                if(smsg.startsWith("<emoji>")){
                    String emojiCode = smsg.substring(7, smsg.length()-8);
//                  System.out.println(emojiCode);
                    insertMessage(textScrollPane, msgArea, emojiCode, fromName+"说:", null);
                    return ;
                }
                insertMessage(textScrollPane, msgArea, null, fromName+"说:", smsg);
            }
        }
    }

    /**
     * 显示退出的结果
     * @param content
     */
    public void showEscDialog(String content){
        JOptionPane.showMessageDialog(frame, content);
        /*清除消息区内容,清除用户数据模型内容和用户map内容,更新房间内人数*/
        msgArea.setText("");
        users_model.clear();
        users_map.clear();
        users_label.setText("房间内人数:0");

    }
    /**
     * 新增一个room
     * @param content
     */
    public void addRoom(String content){
        if(content.length()>0){
            Pattern pattern = Pattern.compile("<rid>(.*)</rid><rname>(.*)</rname>");
            Matcher matcher = pattern.matcher(content);
            if(matcher.find()){
                String rid = matcher.group(1);
                String rname = matcher.group(2);
                insertRoom(Integer.parseInt(rid), rname);
            }
        }
        rooms_label.setText("当前房间数:"+rooms_map.size());
    }

    /**
     * 删除一个room
     * @param content
     */
    public void delRoom(String content){
        if(content.length()>0){
            int delRoomId = Integer.parseInt(content);

            Set<String> set = rooms_map.keySet();
            Iterator<String> iter = set.iterator();
            String rname=null;
            while(iter.hasNext()){
                rname = iter.next();
                if(rooms_map.get(rname)==delRoomId){
                    rooms_model.removeElement(rname);
                    break;
                }
            }
            rooms_map.remove(rname);
        }
        rooms_label.setText("当前房间数:"+rooms_map.size());
    }

    /**
     * 列出目前所有的rooms
     * @param content
     */
    public void listRooms(String content){
        String rname = null;
        String rid=null;
        Pattern rough_pattern=null;
        Matcher rough_matcher=null;
        Pattern detail_pattern=null;
        if(content.length()>0){
            rough_pattern = Pattern.compile("<room>(.*?)</room>");
            rough_matcher = rough_pattern.matcher(content);
            while(rough_matcher.find()){
                String detail = rough_matcher.group(1);
                detail_pattern = Pattern.compile("<rname>(.*)</rname><rid>(.*)</rid>");
                Matcher detail_matcher = detail_pattern.matcher(detail);
                if(detail_matcher.find()){
                    rname = detail_matcher.group(1);
                    rid = detail_matcher.group(2);
                    insertRoom(Integer.parseInt(rid), rname);
                }
            }
        }
        rooms_label.setText("当前房间数:"+rooms_map.size());
    }
    /**
     * 插入一个room
     * @param rid
     * @param rname
     */
    private void insertRoom(Integer rid, String rname){
        if(!rooms_map.containsKey(rname)){
            rooms_map.put(rname, rid);
            rooms_model.addElement(rname);
        }else{
            rooms_map.remove(rname);
            rooms_model.removeElement(rname);
            rooms_map.put(rname, rid);
            rooms_model.addElement(rname);
        }
        rooms_label.setText("当前房间数:"+rooms_map.size());
    }
    /**
     * 插入一个user
     * @param id
     * @param name
     */
    private void insertUser(Integer id, String name){
        if(!users_map.containsKey(name)){
            users_map.put(name, id);
            users_model.addElement(name);
        }else{
            users_map.remove(name);
            users_model.removeElement(name);
            users_map.put(name, id);
            users_model.addElement(name);
        }
        users_label.setText("房间内人数:"+users_map.size());
    }

    /**
     * 获得用户的姓名
     * @param strId
     * @return
     */
    private String getUserName(String strId){
        int uid = Integer.parseInt(strId);
        Set<String> set = users_map.keySet();
        Iterator<String> iterator = set.iterator();
        String cur=null;
        while(iterator.hasNext()){
            cur = iterator.next();
            if(users_map.get(cur)==uid){
                return cur;
            }
        }
        return "";
    }

    /**
     * 获得用户所在房间的名称
     * @param strId
     * @return
     */
    private String getRoomName(String strId){
        int rid = Integer.parseInt(strId);
        Set<String> set = rooms_map.keySet();
        Iterator<String> iterator = set.iterator();
        String cur = null;
        while(iterator.hasNext()){
            cur = iterator.next();
            if(rooms_map.get(cur)==rid){
                return cur;
            }
        }
        return "";
    }

    /**
     * 打印一条消息,如果有图片就打印图片,否则打印content
     * @param scrollPane
     * @param textPane
     * @param icon_code
     * @param title
     * @param content
     */
    private void insertMessage(JScrollPane scrollPane, JTextPane textPane,
            String icon_code, String title, String content){
        StyledDocument document = textPane.getStyledDocument();     /*获取textpane中的文本*/
        /*设置标题的属性*/
        SimpleAttributeSet title_attr = new SimpleAttributeSet();
        StyleConstants.setBold(title_attr, true);
        StyleConstants.setForeground(title_attr, Color.BLUE);
        /*设置正文的属性*/
        SimpleAttributeSet content_attr = new SimpleAttributeSet();  
        StyleConstants.setBold(content_attr, false);  
        StyleConstants.setForeground(content_attr, Color.BLACK);
        Style style = null;
        if(icon_code!=null){
            Icon icon = new ImageIcon("icon/"+icon_code+".png");
            style = document.addStyle("icon", null); 
            StyleConstants.setIcon(style, icon);
        }

        try {  
            document.insertString(document.getLength(), title+"\n", title_attr);
            if(style!=null)
                document.insertString(document.getLength(), "\n", style);
            else
                document.insertString(document.getLength(), "    "+content+"\n", content_attr);

        } catch (BadLocationException ex) {  
            System.out.println("Bad location exception");
        }
        /*设置滑动条到最后*/
        vertical.setValue(vertical.getMaximum());
    }

    /**
     * 设置需要美化字体的组件
     */
    public static void setUIFont()
    {
        Font f = new Font("微软雅黑", Font.PLAIN, 14);
        String   names[]={ "Label", "CheckBox", "PopupMenu","MenuItem", "CheckBoxMenuItem",
                "JRadioButtonMenuItem","ComboBox", "Button", "Tree", "ScrollPane",
                "TabbedPane", "EditorPane", "TitledBorder", "Menu", "TextArea","TextPane",
                "OptionPane", "MenuBar", "ToolBar", "ToggleButton", "ToolTip",
                "ProgressBar", "TableHeader", "Panel", "List", "ColorChooser",
                "PasswordField","TextField", "Table", "Label", "Viewport",
                "RadioButtonMenuItem","RadioButton", "DesktopPane", "InternalFrame"
        }; 
        for (String item : names) {
             UIManager.put(item+ ".font",f); 
        }
    }
    /**
     * 设置UI风格为当前系统的风格
     */
    public static void setUIStyle(){
        String lookAndFeel =UIManager.getSystemLookAndFeelClassName();
        try {
            UIManager.setLookAndFeel(lookAndFeel);
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (UnsupportedLookAndFeelException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    /**
     * 测试用的main函数
     * @param args
     */
    public static void main(String[] args) {
        Client client = new Client();
    }


}
ClientThread
package Client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
 * 
 * @author lannooo
 *
 */
public class ClientThread extends Thread{
    private Socket socket;
    private Client client;
    private BufferedReader br;
    private PrintWriter pw;
    /**
     * 从过主线程传入的socket和client对象来构造
     * @param socket
     * @param client
     */
    public ClientThread(Socket socket, Client client){
        this.client = client;
        this.socket = socket;
        try {
            br=new BufferedReader(new InputStreamReader(socket.getInputStream()));

        } catch (IOException e) {
            System.out.println("cannot get inputstream from socket.");
        }
    }

    /**
     * 不断的读数据并处理
     * 调用主线程的方法来处理:client.method();
     */
    public void run() {
        try{
            br=new BufferedReader(new InputStreamReader(socket.getInputStream()));
            while(true){
                String msg = br.readLine();
                parseMessage(msg);
            }
        }catch (Exception e) {
            e.printStackTrace();
        }
    }
    /**
     * 处理从服务器收到的消息
     * @param message
     */
    public void parseMessage(String message){
        String code = null;
        String msg=null;
        /*
         * 先用正则表达式匹配code码和msg内容 
         */
        if(message.length()>0){
            Pattern pattern = Pattern.compile("<code>(.*)</code>");
            Matcher matcher = pattern.matcher(message);
            if(matcher.find()){
                code = matcher.group(1);
            }
            pattern = Pattern.compile("<msg>(.*)</msg>");
            matcher = pattern.matcher(message);
            if(matcher.find()){
                msg = matcher.group(1);
            }
            System.out.println(code+":"+msg);
            switch(code){
            case "1":   /*一个普通消息处理*/
                client.updateTextArea(msg);
                break;
            case "2":   /*退出消息*/
                client.showEscDialog(msg);
                break;
            case "3":   /*列出房间*/
                client.listRooms(msg);
                break;
            case "4":   /*其他用户的消息*/
                client.updateTextAreaFromUser(msg);
                break;
            case "5":   /*普通消息处理*/
                client.updateTextArea(msg);
                break;
            case "11":  /*添加用户*/
                client.addUser(msg);
                break;
            case "12":  /*删除用户*/
                client.delUser(msg);
                break;
            case "13":  /*删除房间*/
                client.delRoom(msg);
                break;
            case "15":  /*添加房间*/
                client.addRoom(msg);
                break;
            case "16":  /*更新用户名称*/
                client.updateUser(msg);
                break;
            case "21":  /*列出用户列表*/
                client.listUsers(msg);
                break;
            }
        }

    }
}
IconDialog(选择表情界面)
package Client;

import java.awt.Container;
import java.awt.Dialog;
import java.awt.FlowLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
/**
 * 
 * @author lannooo
 *
 */
public class IconDialog implements ActionListener {

    private JDialog dialog;
    private Client client;
    /**
     * 通过frame和客户端对象来构造
     * @param frame
     * @param client
     */
    public IconDialog(JFrame frame, Client client) {
        this.client = client;
        dialog = new JDialog(frame, "请选择表情", true);
        /*16个表情*/
        JButton[] icon_button = new JButton[16];
        ImageIcon[] icons = new ImageIcon[16];
        /*获得弹出窗口的容器,设置布局*/
        Container dialogPane = dialog.getContentPane();
        dialogPane.setLayout(new GridLayout(0, 4));
        /*加入表情*/
        for(int i=1; i<=15; i++){
            icons[i] = new ImageIcon("icon/"+i+".png");
            icons[i].setImage(icons[i].getImage().getScaledInstance(50, 50, Image.SCALE_DEFAULT));
            icon_button[i] = new JButton(""+i, icons[i]);
            icon_button[i].addActionListener(this);
            dialogPane.add(icon_button[i]);
        }
        dialog.setBounds(200,266,266,280);
        dialog.show();
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        /*构造emoji结构的消息发送*/
        String cmd = e.getActionCommand();
        System.out.println(cmd);
        dialog.dispose();
        client.sendMsg("<emoji>"+cmd+"</emoji>", "message");
    }

}
  • 7
    点赞
  • 81
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值