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"); } }
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
赞 (0)