大学Java基础课程设计——网络聊天室

不登高山,不知天之高也;不临深溪,不知地之厚也。

| @Author:TTODS

目录

      • 项目简介
      • 系统设计与实现
        • 聊天室系统的总体设计
        • 服务器端功能设计
        • 客户端功能设计
        • 数据包
        • 用户操作处理流程
        • 客户端界面设计
      • 数据库设计
      • 源码展示
        • 服务器端代码
        • 客户端代码
        • 数据包代码
      • 项目总结
      • 附加说明

项目简介

项目名称:TT聊天室。
项目功能:此项目通过连接数据库实现了用户的注册登录功能,支持用户创建房间或者加入别人的房间,并在房间内进行多人实时聊天。
此项目是我本学期Java基础课程的课程设计,综合Java基础课程中的文件输入输出、多线程、Swing编程、网络编程、数据库编程等技术实现了聊天室的基本功能。

系统设计与实现

聊天室系统的总体设计

聊天室系统的采用Client-Server(C/S)模式,服务器与客户端之间使用数据包(data package)作为交换的消息。用户通过聊天室客户端的用户界面向服务器发送请求(request)包,服务器通过接收客户端发送的请求包,根据请求内容与数据库查询结果给予应答向客户端返回一个数据包,然后客户端接收服务器的回应(response),根据回应的内容将操作的结果通过界面反馈给用户。聊天室系统的总体设计如下图(图1):
大学Java基础课程设计——网络聊天室_第1张图片

图1 聊天室系统的总体设计图

服务器端功能设计

服务器大致由以下部分组成(如图2):
大学Java基础课程设计——网络聊天室_第2张图片


图2 服务器的组成部分

  • 已连接的客户端列表:只要客户端连接了服务器,就会将客户端的socket传入这个列表中,便于后面服务器向每个连接的服务器发送消息(如:在线房间数更新,该列表中包括所有登录了的客户端socket)。
private HashSet<Socket> clients;//已连接的客户端
  • 房间列表:用户创建的房间都会被抽象为一个ChatRoom对象,存入该列表中,使用了观察者模式,服务器收到客户端聊天信息会发到指定的ChatRoom,再由该ChatRoom对象广播给该房间内的客户端(ChatRoom对象中含一个Socket列表存放该房间内的客户端Socket);
private LinkedList<ChatRoom> chatRooms;// 当前开放的房间列表
  • 数据库连接:控制数据库的连接,查询与更新。
private Connection con = null; // 数据库连接
  • 服务器数据处理器:服务器要针对每一个客户端单独开设一个线程,监听客户端请求,为减小服务器的压力,采用线程池管理。
private ServerDataHandler sDataHandler;// 服务器数据处理器,运用线程池,处理多个客户端请求
  • 客户端连接监听器:监听客户端的连接,一旦检测到新的客户端连接就让服务器数据处理器在线程池中创建一个新的线程。
	private AcceptListener acceptListener;//客户端连接监听器

客户端功能设计

客户端大致由以下部分组成(如图3):
大学Java基础课程设计——网络聊天室_第3张图片


图3 客户端的组成部分

  • 客户端窗口:使用swing显示客户端的用户界面,并与用户进行交互。
	ClientFrame clientFrame;// 用户界面
  • 客户端信息接收器:使用一个线程接收服务器的应答。
	ClientReceiver clientReceiver;// 客户端接收器,用来随时接收服务器发来的消息
  • 客户端用户:记录用户当前的状态与属性,为界面显示提供基本信息。
	TTUser user;// 当前登录用户的引用

数据包

由于本系统客户端与服务器消息传输使用的是字符缓冲流,且服务器要通过此消息区分不同的请求,针对此需求,我设计了一个数据包(DataPack)类,这个类有点类似网络上常用的JSON数据,可以通过字符串解析为DataPack对象,但是它比JSON简单,它分为三个域(如图4):
大学Java基础课程设计——网络聊天室_第4张图片


图4 数据包的基本结构

  • Type : 一个枚举型的标识,标名该请求/应答得类型。
	final private RequestType type;
//请求的所有类型
	 public static enum RequestType{
		signUp,signIn,signOut,enterRoom,createRoom,exitRoom,sendMessage,updateRoomCnt,updateUserCnt,rename
		}
  • Status : 服务器回应时的状态(成功/失败),客户端基于此状态判断请求是否成功。
	final private String status;
  • Body : 请求的附带数据,比如登录时,客户端要将用户名和密码发送给服务器,若密码验证成功,服务器要将在数据库查询到的用户昵称发回给客户端,客户端便可以借此完成用户信息的初始化。
	final private String body;	

用户操作处理流程

  • 用户注册

大学Java基础课程设计——网络聊天室_第5张图片


表1 用户注册模块

用户点击注册按钮后,程序会从界面收集表单的完整性,若有未填的数据项会给出提示,当数据符合要求时就会将数据整合成请求包的形式发送给服务器。服务器返回的结果会被客户端接收器接收,根据回应给用户以界面反馈。

  • 用户登录

大学Java基础课程设计——网络聊天室_第6张图片


表2 用户登录模块

用户点击登录按钮后,程序会从界面收集表单的完整性,若有未填的数据项会给出提示,当数据符合要求时就会将数据整合成请求包的形式发送给服务器。服务器返回的结果会被客户端接收器接收,根据回应给用户以界面反馈。

  • 创建房间

大学Java基础课程设计——网络聊天室_第7张图片


表3 创建房间模块

服务器收到创建房间请求时,会将房间对象加入自己的房间列表中,方便进一步对房间进行消息推送。

  • 进入房间

大学Java基础课程设计——网络聊天室_第8张图片


表4 加入房间模块

客户端socket被放入房间的客户端列表后,该房间内客户端向服务器发送消息请求后,该消息会由服务器分到该房间,再由该房间对该房间内所有的客户端转发(广播)。

  • 退出房间

大学Java基础课程设计——网络聊天室_第9张图片


表5 退出房间模块

用户点击退出的房间按钮,服务器处理后,客户端离开房间不再接收该房间的推送,界面返回房间操作界面。

  • 消息发送

大学Java基础课程设计——网络聊天室_第10张图片


表6 聊天信息发送模块

房间内某一客户端发送的聊天信息,经服务器处理会推送到该房间内所有的客户端。

  • 用户昵称修改

大学Java基础课程设计——网络聊天室_第11张图片


表7 用户昵称修改模块

用户注册时,会默认注册名为用户昵称,登陆后可以修改。

客户端界面设计

注册界面:用户可以通过此界面完成注册操作,用户输入用户名和密码便可以完成注册,若已有账号,可以选择按“To sign in”按钮前往登录界面。
大学Java基础课程设计——网络聊天室_第12张图片
登录界面:用户可以通过此界面完成登录操作,用户输入用户名和密码便可以完成登录,若没有账号,可以选择按“Create an account”按钮前往注册界面。
大学Java基础课程设计——网络聊天室_第13张图片
创建/加入房间的界面:此界面是用户登录后显示的界面,用户可以选择创建房间或者加入别人的房间来进入聊天界面,也可以选择退出登录来切换账号,此外,该界面还提供软件基本信息(在线房间数,当前系统时间等)的显示和用户昵称的修改等功能。
大学Java基础课程设计——网络聊天室_第14张图片
聊天界面:此界面是聊天界面,用户可编辑消息发送给当前房间内的所有用户。还可以通过点击“exit room”按钮来退出房间(若是房主退出房间意味着该房间关闭,所有该房间内的用户都会被强制退出房间);
大学Java基础课程设计——网络聊天室_第15张图片

数据库设计

由于这是Java基础的课程设计,数据库这方面没有花功夫,只是使用了一个简单的用户表用来存放用户的账号密码
大学Java基础课程设计——网络聊天室_第16张图片

源码展示

服务器端代码

  • 服务器主体类(TTServer.java)
package ttserver;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.*;
import java.util.*;
import java.util.Date;

public class TTServer {
	private LinkedList<ChatRoom> chatRooms;// 当前开放的房间列表
	private ServerDataHandler sDataHandler;// 服务器数据处理器,运用线程池,处理多个客户端请求
	private Connection con = null; // 数据库连接
	private ServerSocket serverSocket; //服务器socket
	private AcceptListener acceptListener; //连接监听器
	private HashSet<Socket> clients;   //已连接的客户端集合(实际上是登陆后的客户端集合)
	private BufferedWriter logWriter = null; //日志字符输出流
	static HashMap<Character, ArrayList<String>> illegalWordList; //敏感词列表
	//敏感词加载
	static {
		try {
			illegalWordList = new HashMap<>();
			loadIllegalWordList();
		} catch (Exception e) {
			// TODO 自动生成的 catch 块
			System.out.println("敏感词列表加载失败");
		}
	}

	public TTServer() {
		// TODO 自动生成的构造函数存根
		try {
			// 连接数据库
			connectMySQL();
			// 打开连接
			serverSocket = new ServerSocket(9000);
			// 加载敏感词列表

			// 启动日志[!附加模式]
			logWriter = new BufferedWriter(new FileWriter("TTchatRoom.log", true));
			// 初始化房间列表
			chatRooms = new LinkedList<>();
			//初始化连接池
			clients=new HashSet<>();
			// 启动连接监听器
			acceptListener = new AcceptListener();
			acceptListener.start();
			// 启动请求处理器
			sDataHandler = new ServerDataHandler(this);

			System.out.println(new Date().toLocaleString() + ": 服务器启动成功");
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			System.err.println("服务器启动失败,请检查是否该端口是否已被其他程序占用");
			e.printStackTrace();
		}
	}

	// 连接数据库
	public void connectMySQL() {
		// 声明Connection对象

		// 驱动程序名
		String driver = "com.mysql.cj.jdbc.Driver";
		// URL指向要访问的数据库名 test
		String url = "jdbc:mysql://localhost:3306/ttchatroom?serverTimezone=UTC";
		// MySQL配置时的用户名
		String user = "root";
		// MySQL配置时的密码
		String password = "";
		try {
			// 加载驱动程序
			Class.forName(driver);
			// 1.getConnection()方法,连接MySQL数据库!!
			con = DriverManager.getConnection(url, user, password);

			if (!con.isClosed())
				System.out.println("成功以 " + user + " 身份连接到数据库!!!");
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
		}
	}

	//获取开放房间的列表
	public LinkedList<ChatRoom>getChatRoomList() {
		return this.chatRooms;
	}
	//获取已连接的客户端socket集合
	public 	HashSet<Socket> getConnectedClientSet() {
		return this.clients;
	}
	// sql查询,返回结果集
	public synchronized ResultSet SQLSelect(String sql) throws Exception {
		System.out.println(sql);
		Statement statement = con.createStatement();
		ResultSet rs = statement.executeQuery(sql);
		return rs;
	}

	// sql更新,返回修改是否成功
	public synchronized boolean SQLUpdate(String sql) throws Exception {
		PreparedStatement sta = con.prepareStatement(sql);
		int rows = sta.executeUpdate();
		if (rows == 0)
			return false;
		return true;
	}

	// 加载敏感词列表
	static void loadIllegalWordList() throws Exception {
		BufferedReader bur = new BufferedReader(new FileReader("IllegalWordList.txt"));
		String s = null;
		while ((s = bur.readLine()) != null) {
			if (!illegalWordList.containsKey(s.charAt(0))) {
				illegalWordList.put(s.charAt(0), new ArrayList<String>());
			}
			illegalWordList.get(s.charAt(0)).add(s);
		}
		bur.close();
	}

	// 替换敏感词
	static String replaceIllegalWord(String s) {
		for (int i = 0; i < s.length(); i++) {
			if (illegalWordList.containsKey(s.charAt(i))) {
				for (String word : illegalWordList.get(s.charAt(i))) {
					int len = word.length();
					if (i + len <= s.length() && s.substring(i, i + len).equals(word)) {
						char[] replaceWord = new char[len];
						Arrays.fill(replaceWord, '*');
						s = s.substring(0, i) + new String(replaceWord) + s.substring(i + len);
						i += len - 1;
					}
				}
			}
		}
		return s;
	}

	//获取当前房间数量
	public int getRoomCnt() {
		return chatRooms.size();
	}

	// 注册房间,在房间创建时调用
	public synchronized void registerRoom(ChatRoom chatRoom) {
		chatRooms.add(chatRoom);

	}

	// 将客户端注册到房间
	public synchronized void registerClient(Long roomId, Socket client) {
		for (ChatRoom chatRoom : chatRooms) {
			if (chatRoom.getRoomId() == roomId) {
				chatRoom.registerClient(client);
			}
		}
	}

	// 将客户端从聊天室中移除
	public synchronized void removeClientFromRoom(Long roomId, Socket client) {
		if(clients.contains(client)) clients.remove(client);
		for (ChatRoom chatRoom : chatRooms) {
			if (chatRoom.getRoomId() == roomId) {
				chatRoom.removeClient(client);
			}
		}
	}
	
	//将客户端移除连接列表
	public synchronized void removeClient(Socket client) {
		clients.remove(client);
	}

	// 分发一条消息到相应的房间,再由房间对象广播消息
	public synchronized void sendMessge(long roomId, String message) {
		for (ChatRoom chatRoom : chatRooms) {
			if (chatRoom.getRoomId() == roomId) {
				chatRoom.broadcast(message);
				break;
			}
		}

	}

	// 移除房间,在房间关闭时使用(使房间中的所有客户端,房间为空时,房间会自动清除)
	public synchronized void removeRoom(long roomId) {
		int i = 0;
		for (; i < chatRooms.size(); i++) {
			if (chatRooms.get(i).getRoomId() == roomId) {
				chatRooms.get(i).broadcast("exitRoom", "roomClosed", "房主退出了房间,房间关闭");
				chatRooms.remove(i);
				break;
			}
		}
		
	}

	public ServerSocket getServerSocket() {

		return serverSocket;
	}

	// 服务器连接监听器
	class AcceptListener extends Thread {
		@Override
		public void run() {
			// TODO 自动生成的方法存根
			while (true) {
				try {
					Socket clientSocket = serverSocket.accept();
					sDataHandler.addClient(clientSocket);
					clients.add(clientSocket);
					// 把这个客户端加入ServerDataHandler的处理队列
				} catch (IOException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}
	}

	//记录日志
	public synchronized void writeLog(String log) throws Exception {
		logWriter.write("[" + (new Date().toLocaleString()) + "]" + log);
		logWriter.newLine();
		logWriter.flush();
	}

	//启动服务器
	public static void main(String[] args) {
		new TTServer();
	}
}

  • 聊天室类(ChatRoom.java)
package ttserver;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.LinkedList;

public class ChatRoom {
	final private long roomId ; //房间编号
	TTServer server;
	LinkedList <Socket> clients=null; //房间内的客户端列表
	
	//构造方法
	public ChatRoom(TTServer server,long roomId){
		this.roomId = roomId;
		clients = new LinkedList<>();
		this.server = server;
	}
	
	public long getRoomId() {
		return roomId;
	}
	
	//注册房间
	public synchronized void registerClient(Socket client) {
		clients.add(client);
	}
	
	//移除房间内的某个客户端,移除后房间变空则从服务器房间列表中移除此房间
	public synchronized void removeClient(Socket client) {
		for(int i=0;i<clients.size();i++) {
			if(clients.get(i)==client) {
				clients.remove(i);
				break;
			}
		}
		if(clients.size()==0) server.removeRoom(roomId);;
	}
	
	//获取房间内的用户数
	public int  getUserCnt() {
		return clients.size();
	}
	
	//在房间内广播消息(转发玩家发送的聊天消息)
	public void broadcast(String message) {
		for(Socket client : clients) {
			try {
				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
				
				bw.write("{type:sendMessage,status:success,body:{"+message+"}}");
				bw.newLine();
				bw.flush();
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
	}
	//在房间内广播一个数据包
	public void broadcast(String type,String status,String body) {
		for(Socket client : clients) {
			try {
				BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
				
				bw.write("{type:"+type+",status:"+status+",body:{"+body+"}}");
				bw.newLine();
				bw.flush();
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		}
	}
}
  • 服务器数据处理器(ServerDataHandler.java)
package ttserver;

import java.io.*;
import java.net.Socket;
import java.sql.ResultSet;
import java.util.concurrent.*;

import dataPack.*;
import dataPack.DataPack.RequestType;

public class ServerDataHandler {
	private TTServer server;
	private ExecutorService pool; //线程池

	public ServerDataHandler(TTServer s) {
		// TODO 自动生成的构造函数存根
		server = s;
		pool = Executors.newFixedThreadPool(8);
	}

	// 增加连接的客户端对象
	public void addClient(Socket client) {
		Runnable task = new Runnable() {
			public void run() {
				BufferedReader br = null;
				BufferedWriter bw = null;
				String userName = null;// 从数据库获取
				long userId = -1;// 从数据库获取
				long roomId = -1;
				try {
					br = new BufferedReader(new InputStreamReader(client.getInputStream()));
					bw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream()));
				} catch (IOException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
				while (!client.isClosed()) {
					String s = "";
					try {
						while ((s = br.readLine()) != null) {
							DataPack r = new DataPack(s);
							System.out.println(r);
							server.writeLog(r.toString());
							RequestType type = r.getType();
							if (type == RequestType.signUp) {
								// 注册请求
								String usrname = r.get("userName");
								String password = r.get("password");
								String sql = "insert into user(userName,password,name) values(" + usrname + ","
										+ password + "," + usrname + ")";
								//检查该用户名是否已被注册
								ResultSet rs = server.SQLSelect("select * from user where userName = "+usrname);
								if(rs.next()) {
									bw.write("{type:signUp,status:error,body:{该用户名已被注册}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:signUp,status:error,body:{该用户名已被注册}}");
								}else if (server.SQLUpdate(sql)) {
									bw.write("{type:signUp,status:success,body:{注册成功}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:signUp,status:success,body:{注册成功}}");
								} else {
									bw.write("{type:signUp,status:error,body:{注册失败,服务器内部错误}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:signUp,status:error,body:{注册失败,服务器内部错误}}");
								}

							} else if (type == RequestType.signIn) {
								// 登录请求
								// 验证密码
								String usrname = r.get("userName");
								String pswd = r.get("password");
								//检查用户名是否存在
								ResultSet rs = server.SQLSelect(
										"select password,id,name from user where userName='" + usrname + "'");
								if (!rs.next()) {
									bw.write("{type:signIn,status:error,body:{用户名不存在,请检查是否输入错误,如果您还没有账号,请先注册一个}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:signIn,status:error,body:{用户名不存在,请检查是否输入错误,如果您还没有账号,请先注册一个}}");
								} else if (rs.getString("password").equals(pswd)) {
									//检查密码是否正确
									userId = rs.getLong("id");
									userName = rs.getString("name");
									bw.write("{type:signIn,status:success,body:{userId:" + userId + ",userName:" + userName + "}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:signIn,status:success,body:{userId:" + userId + ",userName:" + userName + "}}");
									bw.write("{type:updateRoomCnt,status:,body:{num:"+server.getRoomCnt()+"}}");
									bw.newLine();
									bw.flush();
									bw.write("{type:rename,status:success,body:{newName:"+userName+"}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:rename,status:success,body:{newName:"+userName+"}}");
									
								} else {
									bw.write("{type:signIn,status:error,body:{用户名或密码错误}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:signIn,status:error,body:{用户名或密码错误}}");
								}
							} else if (type == RequestType.createRoom) {
								// 创建房间的请求
								roomId = userId;
								//将房间加入服务器的房间列表
								ChatRoom chatRoom = new ChatRoom(server, roomId);
								server.registerRoom(chatRoom);
								bw.write("{type:createRoom,status:success,body:{roomId:" + roomId + "}}");
								bw.newLine();
								bw.flush();
								server.writeLog("{type:createRoom,status:success,body:{roomId:" + roomId + "}}");
								//通知所有客户端更新在线房间数
								for(Socket client:server.getConnectedClientSet()) {
									BufferedWriter buw = new BufferedWriter(new OutputStreamWriter(client.getOutputStream())); 
									buw.write("{type:updateRoomCnt,status:,body:{num:"+server.getRoomCnt()+"}}");
									buw.newLine();
									buw.flush();
								}
							} else if (type == RequestType.signOut) {
								// 退出房间,如果该用户没有退出房间,就退出房间
								server.removeClientFromRoom(roomId, client);
								//移除连接列表
								server.removeClient(client);

							} else if (type == RequestType.enterRoom) {
								//通过roomId找到房间,并将客户端socket放入该房间的客户端列表
								roomId = Long.parseLong(r.get("roomId"));
								boolean f=false;
								for(ChatRoom cr : server.getChatRoomList()) {
									if(cr.getRoomId()==roomId) {
										f=true;
										break;
									}
									bw.write("{type:enterRoom,status:error,body:{该房间不存在}}");
									bw.newLine();
									bw.flush();
								}
								if(!f) continue;
								//成功加入房间,返回成功消息
								server.registerClient(roomId, client);
								bw.write("{type:enterRoom,status:success,body:{roomId:" + roomId + "}}");
								bw.newLine();
								bw.flush();
								server.writeLog("{type:enterRoom,status:success,body:{roomId:" + roomId + "}}");
								//在该房间内广播欢迎消息
								String helloMessage = "系统(房间号:" + roomId + "):欢迎" + userName + "加入房间!!!(づ ̄ 3 ̄)づ";
								server.sendMessge(roomId, helloMessage);
								//通知该房间的客户端,更新人数
								for(ChatRoom chatRoom:server.getChatRoomList()) {
									if(chatRoom.getRoomId()==roomId) {
										chatRoom.broadcast("updateUserCnt","","num:"+chatRoom.getUserCnt());
									}
								}
							} else if (type == RequestType.exitRoom) {
								bw.write("{type:exitRoom,status:success,body:{}}");
								bw.newLine();
								bw.flush();
								server.writeLog("{type:exitRoom,status:success,body:{}}");
								server.removeClientFromRoom(roomId, client);
								server.sendMessge(roomId, "系统(房间号:" + roomId + "):" + userName + "退出了房间");
								// 房主退出房间,关闭房间
								if (roomId == userId) {
									server.removeRoom(roomId);
								}else {
									//通知该房间的客户端,更新人数
									for(ChatRoom chatRoom:server.getChatRoomList()) {
										if(chatRoom.getRoomId()==roomId) {
											chatRoom.broadcast("updateUserCnt","","num:"+chatRoom.getUserCnt());
										}
									}
								}
							} else if (type == RequestType.sendMessage) {
								//敏感词屏蔽
								String content = r.get("content");
								String message = userName + ":" + TTServer.replaceIllegalWord(content);
								//交给房间转发消息给房间内容易客户端
								server.sendMessge(roomId, message);
							}else if(type==RequestType.rename) {
								//更新数据库信息
								String sql = "update `user` set name=\""+ r.get("newName")+"\" where id =  "+userId+";";
								if(server.SQLUpdate(sql)) {
									bw.write("{type:rename,status:success,body:{newName:"+r.get("newName")+"}}");
									bw.newLine();
									bw.flush();
									userName = r.get("newName");
									server.writeLog("{type:rename,status:success,body:{}}");
								}else {
									bw.write("{type:rename,status:error,body:{改名失败,请检查名字是否过长}}");
									bw.newLine();
									bw.flush();
									server.writeLog("{type:rename,status:error,body:{改名失败,请检查名字是否过长}}");
								}
							} 
							else {
								System.out.println("未知请求");
							}
						}

					} catch (Exception e) {
						// TODO 自动生成的 catch 块
						e.printStackTrace();
						System.out.println(userName+"退出了连接");
						//将该客户端的socket从连接集移除
						server.getConnectedClientSet().remove(client);
						//关闭监听线程
						break;
					}
				}
			}
		};
		//加入线程池
		pool.submit(task);
	}
}

客户端代码

  • 客户端主体(TTClient.java)
package ttclient;

import java.awt.event.*;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import dataPack.*;

public class TTClient {
	Socket socket;
	ClientReceiver clientReceiver;// 客户端接收器,用来随时接收服务器发来的消息
	ClientFrame clientFrame;// 用户界面
	TTUser user;// 当前登录用户的引用
	BufferedReader br=null;
	BufferedWriter bw = null;
	public TTClient() {
		// TODO 自动生成的构造函数存根
		try {
			// 连接服务器,本地测试暂时使用"local host"
			socket = new Socket("localhost", 9000);
			clientFrame = new ClientFrame(this);
			clientFrame.showSignInInterface();
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			clientReceiver = new ClientReceiver(this);
			clientReceiver.start();
			// 窗口关闭时,关闭输入输出
			clientFrame.addWindowListener(new WindowAdapter() {
				@Override
				public void windowClosing(WindowEvent e) {
					// TODO 自动生成的方法存根
					try {
						socket.close();
					} catch (IOException e1) {
						// TODO 自动生成的 catch 块
						e1.printStackTrace();
					}

				}
			});
		} catch (UnknownHostException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	
	TTUser getUser() {
		return user;
	}
	public Socket getSocket() {
		return socket;
	}
	//向服务器发送请求
	public void SendRequest(DataPack r) throws IOException {
		System.out.println(r.toString());
		BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
		bw.write(r.toString());
		bw.newLine();
		bw.flush();
	}

	// 注册账号
	void signUp(String userName, String password) {
		DataPack a  =new DataPack(DataPack.RequestType.signUp,"{userName:'"+userName+"',password:'"+password+"'}") ;
		try {
			SendRequest(a);
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}

	// 登录账号
	void signIn(String userName, String password) {
		// 向服务器发送登录请求,
		DataPack request  = new DataPack(DataPack.RequestType.signIn,"userName:"+userName+",password:"+password) ;
		try {
			//发送请求
			SendRequest(request);
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	void signOut() {
		DataPack request = new DataPack(DataPack.RequestType.signOut,"");
		try {
			//发送请求
			SendRequest(request);
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}

	// 创建房间
	void createRoom() {
		DataPack request = new DataPack(DataPack.RequestType.createRoom,"");
		try {
			SendRequest(request);
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
		}
		
	}

	// 进入房间
	void enterRoom(long roomId) {
		DataPack request = new DataPack(DataPack.RequestType.enterRoom,"roomId:"+roomId);
		try {
			SendRequest(request);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
	
	//退出房间
	void exitRoom() {
		DataPack request = new DataPack(DataPack.RequestType.exitRoom,"");
		try {
			SendRequest(request);
		}catch (Exception e) {
			// TODO: handle exception
			}
		}
	// 发送消息
	void sendMessage(String content,long roomId) {
		DataPack request = new DataPack(DataPack.RequestType.sendMessage,"content:"+content);
			try {
				SendRequest(request);
			} catch (IOException e) {
				// TODO 自动生成的 catch 块
				e.printStackTrace();
			}
		
	}

	void rename(String newName) {
		DataPack request = new DataPack(DataPack.RequestType.rename,"newName:"+newName);
		try {
			SendRequest(request);
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
	}
	// 接收消息
	void receiveMessage(String message) {
		// 界面上显示新消息
		clientFrame.receiveMessage(message);
	}
	
	
	public static void main(String[] args) {
		//启动客户端
		new TTClient();
	}
}

  • 客户端窗口(ClientFrame.java)
package ttclient;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;

import javax.swing.*;
import javax.swing.text.DefaultCaret;

public class ClientFrame extends JFrame {
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	// 窗口显示的四个模式
	final static int SIGN_IN = 0;
	final static int SIGN_UP = 1;
	final static int IN_THE_ROOM = 2;
	final static int OUT_OF_ROOM = 3;

	TTClient client;

	private Container contentPane;

	private JTextPane showArea = null;
	private JScrollPane showScrollPane = null;
	private JLabel timeLabel = null;
	private Calendar now;
	private JLabel NameLabel;//用户名显示
	private JLabel roomCntLabel;//房间计数,out_room_interface;
	private JLabel userCntLabel;//房间内人数计数,in_room_interface;
	private int CntLabelWidth,timeLableWidth,NameLableWidth,settingBtnWidth,exitBtnWidth;
	public ClientFrame(TTClient client) {
		this.client = client;
		// TODO 自动生成的构造函数存根
		setTitle("TTChatRoom");
		setIconImage(new ImageIcon("images/TTChatRoom.png").getImage());
		contentPane = getContentPane();
		contentPane.setBackground(Color.WHITE);
		timeLabel = new JLabel();
		timeLabel.setHorizontalAlignment(JLabel.CENTER);
		timeLabel.setFont(new Font("Times New Roman", Font.ITALIC, 15));
		timeLabel.setBounds(166, 0, 200, 30);
		roomCntLabel = new JLabel("", JLabel.CENTER);
		userCntLabel = new JLabel("",JLabel.CENTER);
		CntLabelWidth=timeLableWidth=NameLableWidth=190;
		settingBtnWidth=80;
		exitBtnWidth=150;
		NameLabel =  new JLabel("昵称", JLabel.CENTER);
		NameLabel.setFont(new Font("黑体", Font.ITALIC, 15));
		NameLabel.setBounds(timeLableWidth+CntLabelWidth, 0, NameLableWidth, 30);
		now = Calendar.getInstance();
		new updateTimebar().start();
		setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}

	public void showSignInInterface() {
		contentPane.removeAll();
		setBounds(700, 150, 500, 700);
		setLayout(null);
		setResizable(false);
		// 第一个子面板,显示应用图标
		JPanel iconPanel = new JPanel();
		iconPanel.setBackground(Color.white);
		iconPanel.setLayout(null);
		// 图标
		ImageIcon icon = new ImageIcon("images/TTChatRoom.png");
		// 图标缩放
		icon.setImage(icon.getImage().getScaledInstance(100, 100, Image.SCALE_DEFAULT));
		// 图标标签
		JLabel iconLabel = new JLabel(icon);
		iconLabel.setBounds(0, 0, 500, 120);
		// 文字标签
		JLabel titleLabel = new JLabel("Sign in to TTChatRoom", JLabel.CENTER);
		titleLabel.setFont(new Font("Times New Roman", 0, 30));
		titleLabel.setBounds(0, 120, 500, 30);
		iconPanel.add(iconLabel);
		iconPanel.add(titleLabel);
		iconPanel.setBounds(0, 30, 500, 180);

		// 第二个子面板,显示玩家登录表单
		JPanel formPanel = new JPanel();
		formPanel.setBackground(Color.white);
		GridLayout gL = new GridLayout(5, 1);
		gL.setVgap(8);
		formPanel.setLayout(gL);
		JLabel usernamrLabel = new JLabel("Username or email address");
		usernamrLabel.setFont(new Font("Times New Roman", Font.BOLD, 25));
		JTextField nameInput = new JTextField();
		nameInput.setFont(new Font("黑体", Font.BOLD, 25));
		JLabel passwordLabel = new JLabel("Password");
		passwordLabel.setFont(new Font("Times New Roman", Font.BOLD, 25));
		JButton signInBtn = new JButton("Sign in");
		signInBtn.setFont(new Font("Times New Roman", Font.BOLD, 25));
		signInBtn.setBackground(new Color(50, 201, 85));
		JPasswordField passwordInput = new JPasswordField();
		passwordInput.setFont(new Font("宋体", Font.BOLD, 25));
		signInBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// 处理登录的代码
				String userName = nameInput.getText();
				String passWord = new String(passwordInput.getPassword());
				if (userName.length() == 0) {
					showErrorMessage("请输入用户名!");
					return;
				} else if (passWord.length() == 0) {
					showErrorMessage("请输入密码!");
					return;
				}
				client.signIn(userName, passWord);
			}
		});
		formPanel.add(usernamrLabel);
		formPanel.add(nameInput);
		formPanel.add(passwordLabel);
		formPanel.add(passwordInput);
		formPanel.add(signInBtn);
		formPanel.setBounds(50, 200, 400, 300);

		// 第三个子面板,引导新用户注册
		JPanel signUpPanel = new JPanel();
		signUpPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
		JLabel guideLabel = new JLabel("New to TTChatRoom?");
		guideLabel.setFont(new Font("Times New Roman", Font.BOLD, 25));
		JButton toSignUpBtn = new JButton("Create an account.");
		toSignUpBtn.setBackground(new Color(63, 146, 210));
//			toSignUpBtn.setBackground(new Color(50,201,85));
		toSignUpBtn.setFont(new Font("Times New Roman", Font.BOLD, 25));
		toSignUpBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				showSignUpInterface();
			}
		});
		signUpPanel.add(guideLabel);
		signUpPanel.add(toSignUpBtn);
		signUpPanel.setBounds(50, 510, 400, 100);
		contentPane.add(iconPanel);
		contentPane.add(formPanel);
		contentPane.add(signUpPanel);
		contentPane.repaint();
		setVisible(true);
		
	}

	public void showSignUpInterface() {
		contentPane.removeAll();
		setBounds(700, 150, 500, 700);
		setLayout(null);
		setResizable(false);
		// 第一个子面板,显示应用图标
		JPanel iconPanel = new JPanel();
		iconPanel.setBackground(Color.white);
		iconPanel.setLayout(null);
		// 图标
		ImageIcon icon = new ImageIcon("images/TTChatRoom.png");
		// 图标缩放
		icon.setImage(icon.getImage().getScaledInstance(100, 100, Image.SCALE_DEFAULT));
		// 图标标签
		JLabel iconLabel = new JLabel(icon);
		iconLabel.setBounds(0, 0, 500, 120);
		// 文字标签
		JLabel titleLabel = new JLabel("Create your account", JLabel.CENTER);
		titleLabel.setFont(new Font("Times New Roman", 0, 30));
		titleLabel.setBounds(0, 120, 500, 30);
		iconPanel.add(iconLabel);
		iconPanel.add(titleLabel);
//			iconPanel.setBorder(BorderFactory.createLineBorder(Color.black));
		iconPanel.setBounds(0, 30, 500, 180);

		// 第二个子面板,显示玩家注册表单
		JPanel formPanel = new JPanel();
		formPanel.setBackground(Color.white);
		GridLayout gL = new GridLayout(7, 1);
		gL.setVgap(8);
		formPanel.setLayout(gL);
		JLabel usernameLabel = new JLabel("Username or email address");
		usernameLabel.setFont(new Font("Times New Roman", Font.BOLD, 25));
		JTextField nameInput = new JTextField();
		nameInput.setFont(new Font("Times New Roman", Font.BOLD, 25));
		JLabel passwordLabel = new JLabel("Password");
		passwordLabel.setFont(new Font("黑体", Font.BOLD, 25));
		JLabel passwordLabel1 = new JLabel("Confirm Password");
		passwordLabel1.setFont(new Font("黑体", Font.BOLD, 25));
		JButton signUpBtn = new JButton("Sign up for TTChatRoom");
		signUpBtn.setFont(new Font("Times New Roman", Font.BOLD, 25));
		signUpBtn.setBackground(new Color(63, 146, 210));
		JPasswordField passwordInput = new JPasswordField();
		passwordInput.setFont(new Font("宋体", Font.BOLD, 25));
		JPasswordField passwordInput1 = new JPasswordField();
		passwordInput1.setFont(new Font("宋体", Font.BOLD, 25));
		signUpBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// 处理注册的代码
				String userName = nameInput.getText();
				String pswd = new String(passwordInput.getPassword());
				String pswd1 = new String(passwordInput1.getPassword());
				if (userName.length() == 0) {
					showErrorMessage("请输入用户名!");
					return;
				}
				if (pswd.length() == 0) {
					showErrorMessage("请输入密码!");
					return;
				}
				if (pswd1.length() == 0) {
					showErrorMessage("请输入第二遍密码!");
					return;
				}
				for (int i = 0; i < userName.length(); i++) {
					char ch = userName.charAt(i);
					if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '@'
							|| ch == '_' || ch == '.')) {
						showErrorMessage("用户名中不可以有特殊字符,用户名应为大小写字母(a~z,A~Z),数字(0~9)和#、_、.等符号的组合。");
						return;
					}
				}
				if (!pswd.equals(pswd1)) {
					showErrorMessage("输入的两次密码不一样");
					return;
				}
				client.signUp(userName, pswd);
			}
		});
		formPanel.add(usernameLabel);
		formPanel.add(nameInput);
		formPanel.add(passwordLabel);
		formPanel.add(passwordInput);
		formPanel.add(passwordLabel1);
		formPanel.add(passwordInput1);
		formPanel.add(signUpBtn);
		formPanel.setBounds(50, 200, 400, 300);

		// 第三个子面板,引导用户回到登录界面
		JPanel signUpPanel = new JPanel();
		signUpPanel.setLayout(new FlowLayout(FlowLayout.CENTER));
		JLabel guideLabel = new JLabel("Already have an account?");
		guideLabel.setFont(new Font("Times New Roman", Font.BOLD, 25));
		JButton toSignInBtn = new JButton("To sign in.");
		toSignInBtn.setBackground(new Color(50, 201, 85));
		toSignInBtn.setFont(new Font("Times New Roman", Font.BOLD, 25));
		toSignInBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				showSignInInterface();
			}
		});
		signUpPanel.add(guideLabel);
		signUpPanel.add(toSignInBtn);
		signUpPanel.setBounds(50, 510, 400, 100);
		contentPane.add(iconPanel);
		contentPane.add(formPanel);
		contentPane.add(signUpPanel);
		
		contentPane.repaint();
		setVisible(true);
	}
	void showOutOfRoomInterface() {
		contentPane.removeAll();
		setBounds(getX(), getY(), 800, 600);
		contentPane.setLayout(null);
		// 子面板1 ,显示当前状态信息
		JPanel statusPanel = new JPanel();
		statusPanel.setLayout(null);
	
		roomCntLabel.setFont(new Font("黑体", Font.ITALIC, 15));
		roomCntLabel.setBounds(0, 0, CntLabelWidth, 30);
//		timeLabel = new JLabel();
//		timeLabel.setHorizontalAlignment(JLabel.CENTER);
//		timeLabel.setFont(new Font("黑体", Font.ITALIC, 15));
//		timeLabel.setBounds(216, 0, 266, 30);
//		NameLabel = new JLabel("昵称:"+client.getUser().getName(), JLabel.CENTER);
		ImageIcon signOutIcon = new ImageIcon("images/Sign out.png");
		signOutIcon = new ImageIcon(signOutIcon.getImage().getScaledInstance(28, 28, Image.SCALE_DEFAULT));
		JButton signOutBtn = new JButton("sign out", signOutIcon);
		signOutBtn.setBackground(Color.lightGray);
		signOutBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				client.signOut();
			}
		});
		signOutBtn.setBounds(NameLableWidth+CntLabelWidth+timeLableWidth+settingBtnWidth, 0, exitBtnWidth, 30);
		ImageIcon settingIcon = new ImageIcon("images/setting.png");
		settingIcon = new ImageIcon(settingIcon.getImage().getScaledInstance(28, 28, Image.SCALE_DEFAULT));
		JButton settingButton = new JButton("修改");
		settingButton.setBackground(Color.lightGray);
		settingButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				String newName = JOptionPane.showInputDialog("请输入新名称:");
				if(newName!=null&&newName.length()!=0) {
					client.rename(newName);
				}else {
					
				}
			}
		});
		settingButton.setBounds(NameLableWidth+timeLableWidth+CntLabelWidth,0,settingBtnWidth,30);
		statusPanel.add(settingButton);
		statusPanel.add(roomCntLabel);
		statusPanel.add(timeLabel);
		statusPanel.add(NameLabel);
		statusPanel.add(signOutBtn);
		statusPanel.setBounds(0, 0, 800, 30);

		// 子面板二,提供创建房间和加入房间按钮
		JPanel roomBtnsPanel = new JPanel();
		roomBtnsPanel.setLayout(null);
		roomBtnsPanel.setBackground(Color.white);
		// 创建房间按钮
		ImageIcon createIcon = new ImageIcon(
				new ImageIcon("images/createRoom1.png").getImage().getScaledInstance(150, 150, Image.SCALE_DEFAULT));
		JButton createRoomBtn = new JButton(createIcon);
		createRoomBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				client.createRoom();
			}
		});
		createRoomBtn.setBackground(Color.white);
		createRoomBtn.setBounds(120, 73, 160, 160);
		JLabel createLabel = new JLabel("创建房间", JLabel.CENTER);
		createLabel.setFont(new Font("黑体", Font.PLAIN, 20));
		createLabel.setBounds(120, 240, 160, 40);
		roomBtnsPanel.add(createRoomBtn);
		roomBtnsPanel.add(createLabel);
		// 加入房间按钮
		ImageIcon joinIcon = new ImageIcon(
				new ImageIcon("images/joinRoom.png").getImage().getScaledInstance(150, 150, Image.SCALE_DEFAULT));
		JButton enterRoomBtn = new JButton(joinIcon);
		enterRoomBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				String roomId = JOptionPane.showInputDialog("请输入房间号");
				try {
					long rd = Long.parseLong(roomId);
					client.enterRoom(rd);
				} catch (NumberFormatException exception) {
					// TODO: handle exception
					JOptionPane.showMessageDialog(enterRoomBtn, "请输入正确的房间号", "房间号错误", JOptionPane.ERROR_MESSAGE);
					return;
				}
			}
		});
		enterRoomBtn.setBackground(Color.white);
		enterRoomBtn.setBounds(320, 73, 160, 160);
		JLabel joinLabel = new JLabel("加入房间", JLabel.CENTER);
		joinLabel.setFont(new Font("黑体", Font.PLAIN, 20));
		joinLabel.setBounds(320, 240, 160, 40);
		roomBtnsPanel.add(joinLabel);
		roomBtnsPanel.add(enterRoomBtn);
		// 帮助按钮
		ImageIcon helpIcon = new ImageIcon(
				new ImageIcon("images/help.png").getImage().getScaledInstance(150, 150, Image.SCALE_DEFAULT));
		JButton helpBtn = new JButton(helpIcon);
		helpBtn.setBackground(Color.white);
		helpBtn.setBounds(520, 73, 160, 160);
		JLabel helpLabel = new JLabel("帮  助", JLabel.CENTER);
		helpLabel.setFont(new Font("黑体", Font.PLAIN, 20));
		helpLabel.setBounds(520, 240, 160, 40);
		helpBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				showErrorMessage("功能尚未完善");
			}
		});
		roomBtnsPanel.add(helpLabel);
		roomBtnsPanel.add(helpBtn);
		roomBtnsPanel.setBounds(0, 33, 800, 400);
		contentPane.add(statusPanel);
		contentPane.add(roomBtnsPanel);
		contentPane.repaint();
	}

	void showInRoomInterface() {
		contentPane.removeAll();
		setBounds(getX(),getY(), 800, 600);
		contentPane.setLayout(null);
		// 子面板1 ,显示当前状态信息
		JPanel statusPanel = new JPanel();
		statusPanel.setLayout(null);
		userCntLabel.setFont(new Font("黑体", Font.ITALIC, 15));
		userCntLabel.setBounds(0, 0, CntLabelWidth, 30);
//		userNameLabel = new JLabel("昵称:"+client.getUser().getName(), JLabel.CENTER);
		setVisible(true);
		ImageIcon signOutIcon = new ImageIcon("images/Sign out.png");
		signOutIcon = new ImageIcon(signOutIcon.getImage().getScaledInstance(28, 28, Image.SCALE_DEFAULT));
		JButton exitBtn = new JButton("exit room", signOutIcon);
		
		exitBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				client.exitRoom();
			}
		});
		ImageIcon settingIcon = new ImageIcon("images/setting.png");
		settingIcon = new ImageIcon(settingIcon.getImage().getScaledInstance(28, 28, Image.SCALE_DEFAULT));
		JButton settingButton = new JButton("修改");
		settingButton.setBackground(Color.lightGray);
		settingButton.setBounds(NameLableWidth+timeLableWidth+CntLabelWidth,0,settingBtnWidth,30);
		settingButton.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				showErrorMessage("房间内不能修改昵称哦");
			}
		});
		exitBtn.setBackground(Color.lightGray);
		exitBtn.setBounds(CntLabelWidth+timeLableWidth+NameLableWidth+settingBtnWidth, 0, exitBtnWidth, 30);
		statusPanel.add(settingButton);
		statusPanel.add(userCntLabel);
		statusPanel.add(timeLabel);
		statusPanel.add(NameLabel);
		statusPanel.add(exitBtn);
		statusPanel.setBounds(0, 0, 800, 30);
		// 子面板二,聊天信息面板
		JPanel showPanel = new JPanel();
		showPanel.setBounds(0, 32, 800, 450);
		showPanel.setLayout(null);
		showPanel.setBackground(Color.lightGray);
		showArea = new JTextPane();
		showScrollPane = new JScrollPane();
		showScrollPane.setViewportView(showArea);
		showScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
		DefaultCaret caret = (DefaultCaret)showArea.getCaret();
		caret.setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE);
		showArea.setBounds(0, 0, 780, 450);
		showArea.setEditable(false);
		showArea.setFont(new Font("黑体", Font.PLAIN, 18));
		showScrollPane.setBounds(0, 0, 795, 450);
		showPanel.add(showScrollPane);
		// 子面板三,输入面板
		JPanel inputPanel = new JPanel();
		inputPanel.setLayout(null);
		inputPanel.setBounds(0, 482, 800, 80);
		JTextField inpuTextField = new JTextField();
		inpuTextField.setBounds(20, 10, 650, 60);
		inpuTextField.setFont(new Font("黑体", Font.PLAIN, 20));
		ImageIcon submitIcon = new ImageIcon(
				new ImageIcon("images/submit.png").getImage().getScaledInstance(60, 60, Image.SCALE_DEFAULT));
		JButton submitBtn = new JButton(submitIcon);
		submitBtn.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent e) {
				// TODO 自动生成的方法存根
				String content = inpuTextField.getText().trim();
				if (content.length() == 0) {
					showErrorMessage("请不要发送空白消息");
					return;
				}
				client.sendMessage(content, client.getUser().getRoomId());
				inpuTextField.setText("");
			}
		});
//			submitBtn.setFont(new Font("黑体",Font.PLAIN,20));
		submitBtn.setBounds(680, 10, 80, 60);
		submitBtn.setBackground(Color.white);
		inputPanel.add(inpuTextField);
		inputPanel.add(submitBtn);
		contentPane.add(showPanel);
		contentPane.add(statusPanel);
		contentPane.add(inputPanel);
		contentPane.repaint();
	}

	void showErrorMessage(String errMessage) {
		JOptionPane.showMessageDialog(null, errMessage, "错误提示", JOptionPane.ERROR_MESSAGE);
	}

	void receiveMessage(String message) {
		if (showArea != null) {
			String oldString = showArea.getText();
			StringBuilder mess  = new StringBuilder();
			for(int i=0;i<message.length();i++) {
				if(i!=0&&i%42==0) {
					mess.append("\n");
				}
				mess.append(message.charAt(i));
			}
			if (oldString.length() != 0)
				showArea.setText(oldString + "\n" + mess);
			else {
				showArea.setText(""+mess);
			}
		}
	}
	
	void updateRoomCnt(int num) {
		if(roomCntLabel!=null) {
			roomCntLabel.setText("当前活跃房间数:"+num);
			roomCntLabel.repaint();
		}
	} 
	void updateUserCnt(int num) {
		if(userCntLabel!=null) {
			userCntLabel.setText("当前房间("+ client.getUser().getRoomId()+" )人数:"+num);
			userCntLabel.repaint();
		}
	}
	
	void updateUserName(String newName) {
		if(NameLabel!=null) {
		NameLabel.setText("昵称:"+newName);
		}
		System.out.println("updateName");
	}
	private class updateTimebar extends Thread {
		@Override
		public void run() {
			while(timeLabel!=null) {
				try {
					Thread.sleep(1000);
					now.setTime(new Date());
					timeLabel.setText(now.getTime().toLocaleString());
				} catch (InterruptedException e) {
					// TODO 自动生成的 catch 块
					e.printStackTrace();
				}
			}
		}
	}

	

	public static void main(String[] args) {
		new ClientFrame(null).showSignUpInterface();
	}
}

  • 客户端接收器(ClientReceiver.java)
package ttclient;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;
import javax.swing.JOptionPane;
import dataPack.DataPack.*;
import dataPack.DataPack;

public class ClientReceiver extends Thread {
	TTClient client;
	Socket clientSocket;
	ClientFrame clientFrame;
	public ClientReceiver(TTClient client) {
		// TODO 自动生成的构造函数存根
		this.client = client;
		clientFrame = client.clientFrame;
		clientSocket = client.getSocket();
	}

	@Override
	public void run() {
		// TODO 自动生成的方法存根
		BufferedReader br = null;
		try {
			br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			e.printStackTrace();
		}
		String content = "";
		try {
			while (!clientSocket.isClosed()&&(content = br.readLine()) != null&&!clientSocket.isClosed()) {
				System.out.println(" receiver: "+content);
				DataPack response = new DataPack(content);
				DataPack.RequestType type = response.getType();
				String status = response.getStatus();
				if(type==RequestType.sendMessage) {
					//接收消息,显示
					client.receiveMessage(response.getBody());
				}else if(type==RequestType.signIn) {
					//登录成功,换界面
					if(status.equals("success")) {
						client.user = new TTUser(Long.parseLong(response.get("userId")),response.get("userName"));
						clientFrame .showOutOfRoomInterface();
					}else {
						//登录失败,给出提示
						JOptionPane.showMessageDialog(clientFrame,response.getBody(),"登录失败",JOptionPane.ERROR_MESSAGE);
					}
				}else if(type==RequestType.signUp) {
					//注册成功
					if(status.equals("success")) {
						JOptionPane.showMessageDialog(clientFrame,"注册成功,快去登录试试吧!");
					}else if(response.getBody()!=null)
						clientFrame.showErrorMessage(response.getBody());
					else clientFrame.showErrorMessage("注册失败");
				}else if(type==RequestType.signOut) {
					//退出登录成功返回登录界面
					if(status.equals("success")) {
						clientFrame.showSignInInterface();
					}else {
						//失败
						clientFrame.showErrorMessage(response.getBody());
					}
				}else if(type==RequestType.createRoom) {
					//创建房间成功,自动进入房间
					if(status.equals("success")) {
						long roomId = Long.parseLong(response.get("roomId"));
						client.enterRoom(roomId);
						}else {
						//失败
					}
				}else if(type==RequestType.enterRoom) {
					//进入房间成功,切换界面
					if(status.equals("success")) {
						client.getUser().setRoomNum(Integer.parseInt(response.get("roomId")));
						System.out.println("进入房间成功,开始接收房间消息");
						clientFrame.showInRoomInterface();
					}else {
						//失败
						clientFrame.showErrorMessage(response.getBody());
					}
				}else if(type==RequestType.exitRoom) {
					//退出房间成功,切换界面
					if(status.equals("success")) {
						clientFrame.showOutOfRoomInterface();
					}else if(status.equals("roomClosed")){
						client.exitRoom();
						clientFrame.showErrorMessage(response.getBody());
					}
					else{
						//失败
					}
				}else if(type==RequestType.updateRoomCnt){
					//更新界面显示
					clientFrame.updateRoomCnt(Integer.parseInt(response.get("num")));
				}else if(type==RequestType.updateUserCnt) {
					//更新界面显示
					clientFrame.updateUserCnt(Integer.parseInt(response.get("num")));
				}else if(type==RequestType.rename) {
					if(status.equals("success")) {
						//更新用户状态
						client.getUser().setName(response.get("newName"));
						//更新界面显示
						clientFrame.updateUserName(response.get("newName"));
					}else {
						clientFrame.showErrorMessage(response.getBody());
					}
				}
				else {
					System.out.println("未知请求");
				}
			}

		} catch (IOException e) {
			// TODO 自动生成的 catch 块
			System.out.println("客户端接收器关闭");
		}

	}
}

  • 用户(TTUser.java)
package ttclient;

public class TTUser {
	private String name;//用户名
	private long id;//用户编号
	private long roomId;//当前所在的房间编号
	private boolean isRoomOwner;//是否为该房间的房主
	public TTUser(long Id,String name) {
		// TODO 自动生成的构造函数存根
		this.setId(Id);
		this.setName(name);
		setRoomNum(-1);
		setRoomOwner(false);
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public long getRoomId() {
		return roomId;
	}
	public void setRoomNum(long roomNum) {
		this.roomId = roomNum;
	}
	public boolean isRoomOwner() {
		return isRoomOwner;
	}
	public void setRoomOwner(boolean isRoomOwner) {
		this.isRoomOwner = isRoomOwner;
	}
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}	
}

数据包代码

  • 数据包类(DataPack.java)
package dataPack;

public class DataPack {
	//请求的所有类型
	 public static enum RequestType{
			signUp,signIn,signOut,enterRoom,createRoom,exitRoom,sendMessage,updateRoomCnt,updateUserCnt,rename
		}
	final private RequestType type;
	final private String body;
	final private String status;
	//通过字符串DataPack对象
	public DataPack(String dataPackString) {
		this.status = getStatus(dataPackString);
		this.type = getType(dataPackString);
		this.body = getBody(dataPackString);
	}
	//请求构造方法,通过类型和数据构成
	public DataPack(RequestType type,String data) {
		this.status = null;
		this.type = type;
		this.body = data;
	}
	
	//将字符串解析为DataPack对象
	public static  DataPack parse(String requestString) {
		return new DataPack(requestString);
	}

	public RequestType getType() {
		return type;
	}
	public String getBody() {
		return body;
	}
	public String getStatus() {
		return status;
	}
	
	//获取特定键对应的值
	public String get(String key) {
		int index= body.indexOf(key);
		if(index!=-1) {
			return body.substring(index+key.length()+1).split(",")[0].split("}")[0];
		}
		return null;
	}
	
	public static RequestType getType(String requestString) {
		RequestType type =null;
		int index  =requestString.indexOf("type:");
		if(index==-1) return type;
		 String typeString=requestString.substring(index+5).split(",")[0];
		 for(RequestType type1:RequestType.values()) {
			 if(typeString.equals(type1.toString()))
				 return type1;
		 }
		 return type;
	}
	
	public static String getBody(String dataPackString) {
		String body=null;
		int index = dataPackString.indexOf("body:{");
		if(index==-1) return null;
		body = dataPackString.substring(index+6,dataPackString.lastIndexOf("}}"));
		return body;
	}
	
	public static String getStatus(String responseString) {
		String status=null;
		int index = responseString.indexOf("status:");
		if(index==-1) return status;
		return responseString.substring(index+7).split(",")[0].split("}}")[0];
	}
	
	public String toString() {
		return "{type:"+getType()+",status:"+getStatus()+",body:{"+getBody()+"}}";
	}	
}

项目总结

这个项目实现了多人多房间的网络聊天功能,使用数据库记录玩家账户信息,实现了基本的注册登录功能,已存在的功能实现和界面的设计都是比较好的,但是缺少了足够强大的数据库来作为支撑,只能支持实时聊天,既不能保存用户群组信息,也不能在用户离线时接收消息,等待用户连接后再推送给用户。此项目只能像一个文字版的腾讯会仪一样,客户在约定好的时间进入特定的房间进行聊天。但是尽管如此,这个项目的可拓展性还是很强的,它拥有一套简单而强大的数据交换协议。
在技术方面,此项目使用了几乎在Java基础课程中学到的所有基础知识,使用了文件的输入输出、网络编程、数据库编程、多线程编程还使用了线程池技术来管理线程。此项目是通过对Java基础知识学习后第一次综合实战项目,也是对自己在前段时间学习效果的检测与考验,整体来说还算可以,但是仍然发现了个人在项目开发时有一些缺点,比如在需求分析时有时分析的不够深入,在软件设计过程中也存在犯错的地方。此外由于时间关系这个项目还有很多不完善的地方比如异常处理,还有很多代码逻辑有待优化。
一个阶段的结束意味着下一个阶段的开始,在以后的学习开发中要吸取从这次课程设计过程中的经验教训,戒骄戒躁,砥砺前行。

附加说明

此项目的所代码,依赖文件和数据库备份文件我都上传到了我的资源中:点击下载
大学Java基础课程设计——网络聊天室_第17张图片


- THE END -

你可能感兴趣的