Java GUI编程实现在线聊天室

作者:lannooooooooooo 时间:2022-04-07 22:50:09 

引言

综合应用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");
}

}

来源:https://blog.csdn.net/qq_22187919/article/details/60465662

标签:Java,GUI,聊天室
0
投稿

猜你喜欢

  • 详解Java Proxy动态 代理机制

    2023-07-24 21:01:58
  • 浅谈Java如何实现一个基于LRU时间复杂度为O(1)的缓存

    2022-02-02 08:35:36
  • VSCode 搭建 Arm 远程调试环境的步骤详解

    2023-06-27 08:54:36
  • Java 关键字 速查表介绍

    2022-07-03 22:04:48
  • Android自定义带圆点的半圆形进度条

    2023-08-05 07:47:15
  • Java基础题新手练习(二)

    2022-03-10 00:11:57
  • C#设计模式之Facade外观模式解决天河城购物问题示例

    2023-06-10 08:28:36
  • Android开发之自定义加载动画详解

    2023-07-27 01:41:05
  • Java concurrency集合之LinkedBlockingDeque_动力节点Java学院整理

    2022-12-06 12:28:38
  • Spring-Security对HTTP相应头的安全支持方式

    2021-07-25 16:30:55
  • JVM完全解读之GC日志记录分析

    2022-09-22 12:43:08
  • Flutter网络请求的3种简单实现方法

    2023-06-21 10:53:22
  • java线程池对象ThreadPoolExecutor的深入讲解

    2023-05-15 06:49:51
  • C#使用log4net打日志

    2022-11-08 17:50:57
  • C# WinForm制作登录界面的实现步骤

    2022-09-06 18:32:54
  • JPA如何设置表名和实体名,表字段与实体字段的对应

    2022-07-14 14:36:27
  • 8种常见的Java不规范代码

    2023-05-24 10:59:53
  • spring解决循环依赖的方案示例

    2021-11-19 03:31:04
  • SpringBoot使用自动配置xxxAutoConfiguration

    2022-11-20 09:05:56
  • 详解java中的static关键字

    2023-09-24 01:44:12
  • asp之家 软件编程 m.aspxhome.com