引言
综合应用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
*