前言
自从开始弄起数据挖掘之后, 已经很久没写过技术类的博客了, 最近学校 JAVA 课设要求实现一个聊天室, 想想去年自己已经写了一个了, 但是有些要求到的功能我也没实现, 但看着原有的代码想了想加功能好像有那么点点难, 于是就想着重构, 也正好之前有看到别人写的 CS 架构的代码, 感觉扩展性还不错, 就试着写了写, 写完这个聊天室后, 还同时写了一个教学白板, 那个白板基于这个聊天室的代码仅仅花了三四个小时就完成了! 所以, 有一个好的架构还是很重要的. 下面就开始介绍我重构后的聊天室(代码已上传到 GitHub https://github.com/leo6033/Chat_Room )
功能介绍
1. 用 Java 图形用户界面编写聊天室服务器端和客户端, 支持多个客户端连接到一个服务器. 每个客户端能够输入账号, 包括注册功能.
2. 可以实现群聊(聊天记录显示在所有客户端界面).
3. 完成好友列表在各个客户端上显示, 包括头像和用户名.
4. 可以实现私人聊天, 用户可以选择某个其他用户, 单独发送信息, 同时实现了文件传输, 还能发送窗口振动.
5. 服务器能够群发系统消息, 能够对用户私发消息, 能够强行让某些用户下线.
6. 客户端的上线下线要求能够在其他客户端上面实时刷新.
7. 服务器能够查看在线用户和注册用户
(加了下划线的是课设要求之外的)
整体思路
数了数, 总共写了 27 个类, 看起来还是蛮多的, 但是仔细看一看还是很简单的, 我将在下面对其中部分进行解释
工具类
在我之前写的几个 socket 通信有关的项目里, 客户端和服务器传输的都是字符串, 而这次, 我把要传输的内容封装成了两个类 Response 和 Request, 客户端向服务器发起请求, 服务器向客户端回应, 通过两个类中包含的请求类型来判断需要进行的操作, 传输采用 ObjectStream. 仔细以看其实会发现, 这两个类内容很相似
Request
- public class Request implements Serializable {
- private static final long serialVersionUID = -1237018286305074249L;
- /** 请求传送的数据类型 */
- private ResponseType type;
- /** 请求动作 */
- private String action;
- /** 请求域中的数据, name-value */
- private Map<String, Object> attributesMap;
- public Request(){
- this.attributesMap = new HashMap<String, Object>();
- }
- public ResponseType getType() {
- return type;
- }
- public void setType(ResponseType type) {
- this.type = type;
- }
- public String getAction() {
- return action;
- }
- public void setAction(String action) {
- this.action = action;
- }
- public Map<String, Object> getAttributesMap() {
- return attributesMap;
- }
- public Object getAttribute(String name){
- return this.attributesMap.get(name);
- }
- public void setAttribute(String name, Object value){
- this.attributesMap.put(name, value);
- }
- public void removeAttribute(String name){
- this.attributesMap.remove(name);
- }
- public void clearAttribute(){
- this.attributesMap.clear();
- }
- }
- Request
- Response
- public class Response implements Serializable {
- private static final long serialVersionUID = 1689541820872288991L;
- /** 响应状态 */
- private ResponseStatus status;
- /** 响应数据的类型 */
- private ResponseType type;
- private Map<String, Object> dataMap;
- /** 响应输出流 */
- private OutputStream outputStream;
- public Response(){
- this.status = ResponseStatus.OK;
- this.dataMap = new HashMap<String, Object>();
- }
- public ResponseStatus getStatus() {
- return status;
- }
- public void setStatus(ResponseStatus status) {
- this.status = status;
- }
- public ResponseType getType() {
- return type;
- }
- public void setType(ResponseType type) {
- this.type = type;
- }
- public Map<String, Object> getDataMap() {
- return dataMap;
- }
- public void setDataMap(Map<String, Object> dataMap) {
- this.dataMap = dataMap;
- }
- public OutputStream getOutputStream() {
- return outputStream;
- }
- public void setOutputStream(OutputStream outputStream) {
- this.outputStream = outputStream;
- }
- public void setData(String name, Object value){
- this.dataMap.put(name, value);
- }
- public Object getData(String name){
- return this.dataMap.get(name);
- }
- public void removeData(String name){
- this.dataMap.remove(name);
- }
- public void clearData(){
- this.dataMap.clear();
- }
- }
- Response
在以上两个类中, 传输的内容会包括文件和消息, 对于文件和消息, 我们需要直到发送者和接受者是谁, 需要知道发送时间等等, 所以同样封装成了两个类
FileInfo
- public class FileInfo implements Serializable {
- private static final long serialVersionUID = -5394575332459969403L;
- /** 消息接收者 */
- private User toUser;
- /** 消息发送者 */
- private User fromUser;
- /** 源文件名 */
- private String srcName;
- /** 发送时间 */
- private Date sendTime;
- /** 目标地 IP */
- private String destIp;
- /** 目标地端口 */
- private int destPort;
- /** 目标文件名 */
- private String destName;
- public User getToUser() {
- return toUser;
- }
- public void setToUser(User toUser) {
- this.toUser = toUser;
- }
- public User getFromUser() {
- return fromUser;
- }
- public void setFromUser(User fromUser) {
- this.fromUser = fromUser;
- }
- public String getSrcName() {
- return srcName;
- }
- public void setSrcName(String srcName) {
- this.srcName = srcName;
- }
- public Date getSendTime() {
- return sendTime;
- }
- public void setSendTime(Date sendTime) {
- this.sendTime = sendTime;
- }
- public String getDestIp() {
- return destIp;
- }
- public void setDestIp(String destIp) {
- this.destIp = destIp;
- }
- public int getDestPort() {
- return destPort;
- }
- public void setDestPort(int destPort) {
- this.destPort = destPort;
- }
- public String getDestName() {
- return destName;
- }
- public void setDestName(String destName) {
- this.destName = destName;
- }
- }
- FileInfo
- Message
- public class Message implements Serializable {
- private static final long serialVersionUID = 1820192075144114657L;
- /** 消息接收者 */
- private User toUser;
- /** 消息发送者 */
- private User fromUser;
- /** 消息内容 */
- private String message;
- /** 发送时间 */
- private Date sendTime;
- public User getToUser() {
- return toUser;
- }
- public void setToUser(User toUser) {
- this.toUser = toUser;
- }
- public User getFromUser() {
- return fromUser;
- }
- public void setFromUser(User fromUser) {
- this.fromUser = fromUser;
- }
- public String getMessage() {
- return message;
- }
- public void setMessage(String message) {
- this.message = message;
- }
- public Date getSendTime() {
- return sendTime;
- }
- public void setSendTime(Date sendTime) {
- this.sendTime = sendTime;
- }
- }
- Message
- User
User 类则用于存储用户信息, 因为会用于传输, 需实现序列化传输
- public class User implements Serializable {
- private static final long serialVersionUID = 5942011574971970871L;
- private long id;
- private String password;
- private String nickname;
- private int head;
- private char sex;
- public User(String password, String nickname, char sex, int head){
- this.password = password;
- this.sex = sex;
- this.head = head;
- if(nickname.equals("")||nickname==null)
- {
- this.nickname = "未命名";
- }else{
- this.nickname = nickname;
- }
- }
- public User(long id, String password){
- this.id = id;
- this.password = password;
- }
- public long getId(){
- return id;
- }
- public void setId(long id){
- this.id = id;
- }
- public void setPassword(String password){
- this.password = password;
- }
- public String getPassword(){
- return password;
- }
- public void setSex(char sex){
- this.sex=sex;
- }
- public char getSex(){
- return this.sex;
- }
- public void setNickname(String nickname){
- this.nickname = nickname;
- }
- public String getNickname(){
- return this.nickname;
- }
- public void setHead(int head){
- this.head = head;
- }
- public int getHead(){
- return this.head;
- }
- public ImageIcon getHeadIcon(){
- ImageIcon image = new ImageIcon("images/"+head+".png");
- return image;
- }
- @Override
- public int hashCode() {
- final int prime = 31;
- int result = 1;
- result = prime * result + head;
- result = prime * result + (int)(id ^ (id>> 32));
- result = prime * result + ((nickname == null) ? 0 : nickname.hashCode());
- result = prime * result + ((password == null) ? 0 : password.hashCode());
- result = prime * result + sex;
- return result;
- }
- @Override
- public boolean equals(Object obj) {
- if(this == obj)
- return true;
- if(obj == null)
- return false;
- if(getClass() != obj.getClass())
- return false;
- User other = (User) obj;
- if(head != other.head || id != other.id || sex != other.sex)
- return false;
- if(nickname == null){
- if(other.nickname != null)
- return false;
- }else if(!nickname.equals(other.nickname))
- return false;
- if(password == null){
- if(other.password != null)
- return false;
- }else if(!password.equals(other.password))
- return false;
- return true;
- }
- @Override
- public String toString() {
- return this.getClass().getName()
- + "[id=" + this.id
- + ",pwd=" + this.password
- + ",nickname=" + this.nickname
- + ",head=" + this.head
- + ",sex=" + this.sex
- + "]";
- }
- }
- User
剩余的类就不一一介绍了, 如果有需要可以到我的 https://github.com/leo6033/Chat_Room 上找到源代码.
Server 端
服务器端的代码用到的类如上所示, 其中 entity 中的两个类和 ServerInfoFrame 仅用于界面, 所以不会进行介绍.
UserService
用于用户账号管理, 预先创建几个账号, 然后存到文件中, 每次服务器执行时, 都会将文件中的账号信息读入, 同时新创建的用户账号也会存入到文件中去.
- public class UserService {
- private static int idCount = 3; //id
- /** 新增用户 */
- public void addUser(User user){
- user.setId(++idCount);
- List<User> users = loadAllUser();
- users.add(user);
- saveAllUser(users);
- }
- /** 用户登录 */
- public User login(long id, String password){
- User result = null;
- List<User> users = loadAllUser();
- for (User user : users) {
- if(id == user.getId() && password.equals(user.getPassword())){
- result = user;
- break;
- }
- }
- return result;
- }
- /** 根据 ID 加载用户 */
- public User loadUser(long id){
- User result = null;
- List<User> users = loadAllUser();
- for (User user : users) {
- if(id == user.getId()){
- result = user;
- break;
- }
- }
- return result;
- }
- /** 加载所有用户 */
- @SuppressWarnings("unchecked")
- public List<User> loadAllUser() {
- List<User> list = null;
- ObjectInputStream ois = null;
- try {
- ois = new ObjectInputStream(
- new FileInputStream(
- DataBuffer.configProp.getProperty("dbpath")));
- list = (List<User>)ois.readObject();
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- IOUtil.close(ois);
- }
- return list;
- }
- private void saveAllUser(List<User> users) {
- ObjectOutputStream oos = null;
- try {
- oos = new ObjectOutputStream(
- new FileOutputStream(
- DataBuffer.configProp.getProperty("dbpath")));
- // 写回用户信息
- oos.writeObject(users);
- oos.flush();
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- IOUtil.close(oos);
- }
- }
- /** 初始化几个测试用户 */
- public void initUser(){
- User user = new User("admin", "Admin", 'm', 0);
- user.setId(1);
- User user2 = new User("123", "yong", 'm', 1);
- user2.setId(2);
- User user3 = new User("123", "anni", 'f', 2);
- user3.setId(3);
- List<User> users = new CopyOnWriteArrayList<User>();
- users.add(user);
- users.add(user2);
- users.add(user3);
- this.saveAllUser(users);
- }
- public static void main(String[] args){
- new UserService().initUser();
- List<User> users = new UserService().loadAllUser();
- for (User user : users) {
- System.out.println(user);
- }
- }
- }
- UserService
- DataBuffer
用于服务器端从文件中读取数据, 进行缓存
- public class DataBuffer {
- // 服务器端套接字
- public static ServerSocket serverSocket;
- // 在线用户的 IO Map
- public static Map<Long, OnlineClientIOCache> onlineUserIOCacheMap;
- // 在线用户 Map
- public static Map<Long, User> onlineUsersMap;
- // 服务器配置参数属性集
- public static Properties configProp;
- // 已注册用户表的 Model
- public static RegistedUserTableModel registedUserTableModel;
- // 当前在线用户表的 Model
- public static OnlineUserTableModel onlineUserTableModel;
- // 当前服务器所在系统的屏幕尺寸
- public static Dimension screenSize;
- static{
- // 初始化
- onlineUserIOCacheMap = new ConcurrentSkipListMap<Long,OnlineClientIOCache>();
- onlineUsersMap = new ConcurrentSkipListMap<Long, User>();
- configProp = new Properties();
- registedUserTableModel = new RegistedUserTableModel();
- onlineUserTableModel = new OnlineUserTableModel();
- screenSize = Toolkit.getDefaultToolkit().getScreenSize();
- // 加载服务器配置文件
- try {
- configProp.load(Thread.currentThread()
- .getContextClassLoader()
- .getResourceAsStream("serverconfig.properties"));
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- DataBuffer
- RequestProcessor
这时服务器端最重要的一个类了, 用于处理客户端发来的消息, 并进行回复, 对于每一项操作的实现原理无非就是服务器处理内部数据或是向指定客户端发送消息, 详细看代码注释
- public class RequestProcessor implements Runnable {
- private Socket currentClientSocket; // 当前正在请求服务器的客户端 Socket
- public RequestProcessor(Socket currentClientSocket){
- this.currentClientSocket = currentClientSocket;
- }
- public void run() {
- boolean flag = true; // 是否不间断监听
- try{
- OnlineClientIOCache currentClientIOCache = new OnlineClientIOCache(
- new ObjectInputStream(currentClientSocket.getInputStream()),
- new ObjectOutputStream(currentClientSocket.getOutputStream()));
- while(flag){ // 不停地读取客户端发过来的请求对象
- // 从请求输入流中读取到客户端提交的请求对象
- Request request = (Request)currentClientIOCache.getOis().readObject();
- System.out.println("Server 读取了客户端的请求:" + request.getAction());
- String actionName = request.getAction(); // 获取请求中的动作
- if(actionName.equals("userRegiste")){ // 用户注册
- registe(currentClientIOCache, request);
- }else if(actionName.equals("userLogin")){ // 用户登录
- login(currentClientIOCache, request);
- }else if("exit".equals(actionName)){ // 请求断开连接
- flag = logout(currentClientIOCache, request);
- }else if("chat".equals(actionName)){ // 聊天
- chat(request);
- }else if("shake".equals(actionName)){ // 振动
- shake(request);
- }else if("toSendFile".equals(actionName)){ // 准备发送文件
- toSendFile(request);
- }else if("agreeReceiveFile".equals(actionName)){ // 同意接收文件
- agreeReceiveFile(request);
- }else if("refuseReceiveFile".equals(actionName)){ // 拒绝接收文件
- refuseReceiveFile(request);
- }
- }
- }catch(Exception e){
- e.printStackTrace();
- }
- }
- /** 拒绝接收文件 */
- private void refuseReceiveFile(Request request) throws IOException {
- FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");
- Response response = new Response(); // 创建一个响应对象
- response.setType(ResponseType.REFUSERECEIVEFILE);
- response.setData("sendFile", sendFile);
- response.setStatus(ResponseStatus.OK);
- // 向请求方的输出流输出响应
- OnlineClientIOCache ocic = DataBuffer.onlineUserIOCacheMap.get(sendFile.getFromUser().getId());
- this.sendResponse(ocic, response);
- }
- /** 同意接收文件 */
- private void agreeReceiveFile(Request request) throws IOException {
- FileInfo sendFile = (FileInfo)request.getAttribute("sendFile");
- // 向请求方 (发送方) 的输出流输出响应
- Response response = new Response(); // 创建一个响应对象
- response.setType(ResponseType.AGREERECEIVEFILE);
- response.setData("sendFile", sendFile);
- response.setStatus(ResponseStatus.OK);
- OnlineClientIOCache sendIO = DataBuffer.onlineUserIOCacheMap.get(sendFile.getFromUser().getId());
- this.sendResponse(sendIO, response);
- // 向接收方发出接收文件的响应
- Response response2 = new Response(); // 创建一个响应对象
- response2.setType(ResponseType.RECEIVEFILE);
- response2.setData("sendFile", sendFile);
- response2.setStatus(ResponseStatus.OK);
- OnlineClientIOCache receiveIO = DataBuffer.onlineUserIOCacheMap.get(sendFile.getToUser().getId());
- this.sendResponse(receiveIO, response2);
- }
- /** 客户端退出 */
- public boolean logout(OnlineClientIOCache oio, Request request) throws IOException{
- System.out.println(currentClientSocket.getInetAddress().getHostAddress()
- + ":" + currentClientSocket.getPort() + "走了");
- User user = (User)request.getAttribute("user");
- // 把当前上线客户端的 IO 从 Map 中删除
- DataBuffer.onlineUserIOCacheMap.remove(user.getId());
- // 从在线用户缓存 Map 中删除当前用户
- DataBuffer.onlineUsersMap.remove(user.getId());
- Response response = new Response(); // 创建一个响应对象
- response.setType(ResponseType.LOGOUT);
- response.setData("logoutUser", user);
- oio.getOos().writeObject(response); // 把响应对象往客户端写
- oio.getOos().flush();
- currentClientSocket.close(); // 关闭这个客户端 Socket
- DataBuffer.onlineUserTableModel.remove(user.getId()); // 把当前下线用户从在线用户表 Model 中删除
- iteratorResponse(response);// 通知所有其它在线客户端
- return false; // 断开监听
- }
- /** 注册 */
- public void registe(OnlineClientIOCache oio, Request request) throws IOException {
- User user = (User)request.getAttribute("user");
- UserService userService = new UserService();
- userService.addUser(user);
- Response response = new Response(); // 创建一个响应对象
- response.setStatus(ResponseStatus.OK);
- response.setData("user", user);
- oio.getOos().writeObject(response); // 把响应对象往客户端写
- oio.getOos().flush();
- // 把新注册用户添加到 RegistedUserTableModel 中
- DataBuffer.registedUserTableModel.add(new String[]{
- String.valueOf(user.getId()),
- user.getPassword(),
- user.getNickname(),
- String.valueOf(user.getSex())
- });
- }
- /** 登录 */
- public void login(OnlineClientIOCache currentClientIO, Request request) throws IOException {
- String idStr = (String)request.getAttribute("id");
- String password = (String) request.getAttribute("password");
- UserService userService = new UserService();
- User user = userService.login(Long.parseLong(idStr), password);
- Response response = new Response(); // 创建一个响应对象
- if(null != user){
- if(DataBuffer.onlineUsersMap.containsKey(user.getId())){ // 用户已经登录了
- response.setStatus(ResponseStatus.OK);
- response.setData("msg", "该 用户已经在别处上线了!");
- currentClientIO.getOos().writeObject(response); // 把响应对象往客户端写
- currentClientIO.getOos().flush();
- }else { // 正确登录
- DataBuffer.onlineUsersMap.put(user.getId(), user); // 添加到在线用户
- // 设置在线用户
- response.setData("onlineUsers",
- new CopyOnWriteArrayList<User>(DataBuffer.onlineUsersMap.values()));
- response.setStatus(ResponseStatus.OK);
- response.setData("user", user);
- currentClientIO.getOos().writeObject(response); // 把响应对象往客户端写
- currentClientIO.getOos().flush();
- // 通知其它用户有人上线了
- Response response2 = new Response();
- response2.setType(ResponseType.LOGIN);
- response2.setData("loginUser", user);
- iteratorResponse(response2);
- // 把当前上线的用户 IO 添加到缓存 Map 中
- DataBuffer.onlineUserIOCacheMap.put(user.getId(),currentClientIO);
- // 把当前上线用户添加到 OnlineUserTableModel 中
- DataBuffer.onlineUserTableModel.add(
- new String[]{String.valueOf(user.getId()),
- user.getNickname(),
- String.valueOf(user.getSex())});
- }
- }else{ // 登录失败
- response.setStatus(ResponseStatus.OK);
- response.setData("msg", "账号或密码不正确!");
- currentClientIO.getOos().writeObject(response);
- currentClientIO.getOos().flush();
- }
- }
- /** 聊天 */
- public void chat(Request request) throws IOException {
- Message msg = (Message)request.getAttribute("msg");
- Response response = new Response();
- response.setStatus(ResponseStatus.OK);
- response.setType(ResponseType.CHAT);
- response.setData("txtMsg", msg);
- if(msg.getToUser() != null){ // 私聊: 只给私聊的对象返回响应
- OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
- sendResponse(io, response);
- }else{ // 群聊: 给除了发消息的所有客户端都返回响应
- for(Long id : DataBuffer.onlineUserIOCacheMap.keySet()){
- if(msg.getFromUser().getId() == id ){ continue; }
- sendResponse(DataBuffer.onlineUserIOCacheMap.get(id), response);
- }
- }
- }
- /* 广播 */
- public static void board(String str) throws IOException {
- User user = new User(1,"admin");
- Message msg = new Message();
- msg.setFromUser(user);
- msg.setSendTime(new Date());
- DateFormat df = new SimpleDateFormat("HH:mm:ss");
- StringBuffer sb = new StringBuffer();
- sb.append("").append(df.format(msg.getSendTime())).append(" ");
- sb.append("系统通知 \ n"+str+"\n");
- msg.setMessage(sb.toString());
- Response response = new Response();
- response.setStatus(ResponseStatus.OK);
- response.setType(ResponseType.BOARD);
- response.setData("txtMsg", msg);
- for (Long id : DataBuffer.onlineUserIOCacheMap.keySet()) {
- sendResponse_sys(DataBuffer.onlineUserIOCacheMap.get(id), response);
- }
- }
- /* 踢除用户 */
- public static void remove(User user_) throws IOException{
- User user = new User(1,"admin");
- Message msg = new Message();
- msg.setFromUser(user);
- msg.setSendTime(new Date());
- msg.setToUser(user_);
- StringBuffer sb = new StringBuffer();
- DateFormat df = new SimpleDateFormat("HH:mm:ss");
- sb.append("").append(df.format(msg.getSendTime())).append(" ");
- sb.append("系统通知您 \ n"+"您被强制下线"+"\n");
- msg.setMessage(sb.toString());
- Response response = new Response();
- response.setStatus(ResponseStatus.OK);
- response.setType(ResponseType.REMOVE);
- response.setData("txtMsg", msg);
- OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
- sendResponse_sys(io, response);
- }
- /* 私信 */
- public static void chat_sys(String str,User user_) throws IOException{
- User user = new User(1,"admin");
- Message msg = new Message();
- msg.setFromUser(user);
- msg.setSendTime(new Date());
- msg.setToUser(user_);
- DateFormat df = new SimpleDateFormat("HH:mm:ss");
- StringBuffer sb = new StringBuffer();
- sb.append("").append(df.format(msg.getSendTime())).append(" ");
- sb.append("系统通知您 \ n"+str+"\n");
- msg.setMessage(sb.toString());
- Response response = new Response();
- response.setStatus(ResponseStatus.OK);
- response.setType(ResponseType.CHAT);
- response.setData("txtMsg", msg);
- OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
- sendResponse_sys(io, response);
- }
- /** 发送振动 */
- public void shake(Request request)throws IOException {
- Message msg = (Message) request.getAttribute("msg");
- DateFormat df = new SimpleDateFormat("HH:mm:ss");
- StringBuffer sb = new StringBuffer();
- sb.append(" ").append(msg.getFromUser().getNickname())
- .append("(").append(msg.getFromUser().getId()).append(")")
- .append(df.format(msg.getSendTime())).append("\n 给您发送了一个窗口抖动 \ n");
- msg.setMessage(sb.toString());
- Response response = new Response();
- response.setStatus(ResponseStatus.OK);
- response.setType(ResponseType.SHAKE);
- response.setData("ShakeMsg", msg);
- OnlineClientIOCache io = DataBuffer.onlineUserIOCacheMap.get(msg.getToUser().getId());
- sendResponse(io, response);
- }
- /** 准备发送文件 */
- public void toSendFile(Request request)throws IOException{
- Response response = new Response();
- response.setStatus(ResponseStatus.OK);
- response.setType(ResponseType.TOSENDFILE);
- FileInfo sendFile = (FileInfo)request.getAttribute("file");
- response.setData("sendFile", sendFile);
- // 给文件接收方转发文件发送方的请求
- OnlineClientIOCache ioCache = DataBuffer.onlineUserIOCacheMap.get(sendFile.getToUser().getId());
- sendResponse(ioCache, response);
- }
- /** 给所有在线客户都发送响应 */
- private void iteratorResponse(Response response) throws IOException {
- for(OnlineClientIOCache onlineUserIO : DataBuffer.onlineUserIOCacheMap.values()){
- ObjectOutputStream oos = onlineUserIO.getOos();
- oos.writeObject(response);
- oos.flush();
- }
- }
- /** 向指定客户端 IO 的输出流中输出指定响应 */
- private void sendResponse(OnlineClientIOCache onlineUserIO, Response response)throws IOException {
- ObjectOutputStream oos = onlineUserIO.getOos();
- oos.writeObject(response);
- oos.flush();
- }
- /** 向指定客户端 IO 的输出流中输出指定响应 */
- private static void sendResponse_sys(OnlineClientIOCache onlineUserIO, Response response)throws IOException {
- ObjectOutputStream oos = onlineUserIO.getOos();
- oos.writeObject(response);
- oos.flush();
- }
- }
- RequestProcessor
Client 端
个人感觉做这类项目时, 难点是在客户端, 之前考虑了很久关于界面的切换, 因为涉及到了登陆界面, 注册界面, 聊天界面, 所以如何将客户端的 socket 与这几个界面联系起来是个值得思考的问题. 同时, 也思考了好久好友列表的展示方法, 最后想到了 TIM. 下面介绍一下其中的几个类
ClientThread
客户端线程, 一个线程表示一个用户, 处理服务器发来的消息, 在里面用了 currentFrame 这个变量来表示当前窗口.
- public class ClientThread extends Thread {
- private JFrame currentFrame; // 当前窗体
- public ClientThread(JFrame frame){
- currentFrame = frame;
- }
- public void run() {
- try {
- while (DataBuffer.clientSeocket.isConnected()) {
- Response response = (Response) DataBuffer.ois.readObject();
- ResponseType type = response.getType();
- System.out.println("获取了响应内容:" + type);
- if (type == ResponseType.LOGIN) {
- User newUser = (User)response.getData("loginUser");
- DataBuffer.onlineUserListModel.addElement(newUser);
- ChatFrame.onlineCountLbl.setText(
- "在线用户列表("+ DataBuffer.onlineUserListModel.getSize() +")");
- ClientUtil.appendTxt2MsgListArea("[系统消息] 用户"+newUser.getNickname() + "上线了!\n");
- }else if(type == ResponseType.LOGOUT){
- User newUser = (User)response.getData("logoutUser");
- DataBuffer.onlineUserListModel.removeElement(newUser);
- ChatFrame.onlineCountLbl.setText(
- "在线用户列表("+ DataBuffer.onlineUserListModel.getSize() +")");
- ClientUtil.appendTxt2MsgListArea("[系统消息] 用户"+newUser.getNickname() + "下线了!\n");
- }else if(type == ResponseType.CHAT){ // 聊天
- Message msg = (Message)response.getData("txtMsg");
- ClientUtil.appendTxt2MsgListArea(msg.getMessage());
- }else if(type == ResponseType.SHAKE){ // 振动
- Message msg = (Message)response.getData("ShakeMsg");
- ClientUtil.appendTxt2MsgListArea(msg.getMessage());
- new JFrameShaker(this.currentFrame).startShake();
- }else if(type == ResponseType.TOSENDFILE){ // 准备发送文件
- toSendFile(response);
- }else if(type == ResponseType.AGREERECEIVEFILE){ // 对方同意接收文件
- sendFile(response);
- }else if(type == ResponseType.REFUSERECEIVEFILE){ // 对方拒绝接收文件
- ClientUtil.appendTxt2MsgListArea("[文件消息] 对方拒绝接收, 文件发送失败!\n");
- }else if(type == ResponseType.RECEIVEFILE){ // 开始接收文件
- receiveFile(response);
- }else if(type == ResponseType.BOARD){
- Message msg = (Message)response.getData("txtMsg");
- ClientUtil.appendTxt2MsgListArea(msg.getMessage());
- }else if(type == ResponseType.REMOVE){
- ChatFrame.remove();
- }
- }
- } catch (IOException e) {
- //e.printStackTrace();
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- /** 发送文件 */
- private void sendFile(Response response) {
- final FileInfo sendFile = (FileInfo)response.getData("sendFile");
- BufferedInputStream bis = null;
- BufferedOutputStream bos = null;
- Socket socket = null;
- try {
- socket = new Socket(sendFile.getDestIp(),sendFile.getDestPort());// 套接字连接
- bis = new BufferedInputStream(new FileInputStream(sendFile.getSrcName()));// 文件读入
- bos = new BufferedOutputStream(socket.getOutputStream());// 文件写出
- byte[] buffer = new byte[1024];
- int n = -1;
- while ((n = bis.read(buffer)) != -1){
- bos.write(buffer, 0, n);
- }
- bos.flush();
- synchronized (this) {
- ClientUtil.appendTxt2MsgListArea("[文件消息] 文件发送完毕!\n");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }finally{
- IOUtil.close(bis,bos);
- SocketUtil.close(socket);
- }
- }
- /** 接收文件 */
- private void receiveFile(Response response) {
- final FileInfo sendFile = (FileInfo)response.getData("sendFile");
- BufferedInputStream bis = null;
- BufferedOutputStream bos = null;
- ServerSocket serverSocket = null;
- Socket socket = null;
- try {
- serverSocket = new ServerSocket(sendFile.getDestPort());
- socket = serverSocket.accept(); // 接收
- bis = new BufferedInputStream(socket.getInputStream());// 缓冲读
- bos = new BufferedOutputStream(new FileOutputStream(sendFile.getDestName()));// 缓冲写出
- byte[] buffer = new byte[1024];
- int n = -1;
- while ((n = bis.read(buffer)) != -1){
- bos.write(buffer, 0, n);
- }
- bos.flush();
- synchronized (this) {
- ClientUtil.appendTxt2MsgListArea("[文件消息] 文件接收完毕! 存放在["
- + sendFile.getDestName()+"]\n");
- }
- } catch (IOException e) {
- e.printStackTrace();
- }finally{
- IOUtil.close(bis,bos);
- SocketUtil.close(socket);
- SocketUtil.close(serverSocket);
- }
- }
- /** 准备发送文件 */
- private void toSendFile(Response response) {
- FileInfo sendFile = (FileInfo)response.getData("sendFile");
- String fromName = sendFile.getFromUser().getNickname()
- + "(" + sendFile.getFromUser().getId() + ")";
- String fileName = sendFile.getSrcName()
- .substring(sendFile.getSrcName().lastIndexOf(File.separator)+1);
- int select = JOptionPane.showConfirmDialog(this.currentFrame,
- fromName + "向您发送文件 [" + fileName+ "]!\n 同意接收吗?",
- "接收文件", JOptionPane.YES_NO_OPTION);
- try {
- Request request = new Request();
- request.setAttribute("sendFile", sendFile);
- if (select == JOptionPane.YES_OPTION) {
- JFileChooser jfc = new JFileChooser();
- jfc.setSelectedFile(new File(fileName));
- int result = jfc.showSaveDialog(this.currentFrame);
- if (result == JFileChooser.APPROVE_OPTION){
- // 设置目的地文件名
- sendFile.setDestName(jfc.getSelectedFile().getCanonicalPath());
- // 设置目标地的 IP 和接收文件的端口
- sendFile.setDestIp(DataBuffer.ip);
- sendFile.setDestPort(DataBuffer.RECEIVE_FILE_PORT);
- request.setAction("agreeReceiveFile");
- // receiveFile(response);
- ClientUtil.appendTxt2MsgListArea("[文件消息] 您已同意接收来自"
- + fromName +"的文件, 正在接收文件 ...\n");
- } else {
- request.setAction("refuseReceiveFile");
- ClientUtil.appendTxt2MsgListArea("[文件消息] 您已拒绝接收来自"
- + fromName +"的文件!\n");
- }
- } else {
- request.setAction("refuseReceiveFile");
- ClientUtil.appendTxt2MsgListArea("[文件消息] 您已拒绝接收来自"
- + fromName +"的文件!\n");
- }
- ClientUtil.sendTextRequest2(request);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- ClientThread
- ClientUtil
用于客户端向服务器发送消息
- public class ClientUtil {
- /** 发送请求对象, 主动接收响应 */
- public static Response sendTextRequest(Request request) throws IOException {
- Response response = null;
- try {
- // 发送请求
- DataBuffer.oos.writeObject(request);
- DataBuffer.oos.flush();
- System.out.println("客户端发送了请求对象:" + request.getAction());
- if(!"exit".equals(request.getAction())){
- // 获取响应
- response = (Response) DataBuffer.ois.readObject();
- System.out.println("客户端获取到了响应对象:" + response.getStatus());
- }else{
- System.out.println("客户端断开连接了");
- }
- } catch (IOException e) {
- throw e;
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- return response;
- }
- /** 发送请求对象, 不主动接收响应 */
- public static void sendTextRequest2(Request request) throws IOException {
- try {
- DataBuffer.oos.writeObject(request); // 发送请求
- DataBuffer.oos.flush();
- System.out.println("客户端发送了请求对象:" + request.getAction());
- } catch (IOException e) {
- throw e;
- }
- }
- /** 把指定文本添加到消息列表文本域中 */
- public static void appendTxt2MsgListArea(String txt) {
- ChatFrame.msgListArea.append(txt);
- // 把光标定位到文本域的最后一行
- ChatFrame.msgListArea.setCaretPosition(ChatFrame.msgListArea.getDocument().getLength());
- }
- }
- ClientUtil
总结
大体上的细节我就介绍这些, 剩下的大部分都是界面相关的代码, 我把整个项目放到 https://github.com/leo6033/Chat_Room 上了, 感觉现在用的这个框架可以适应学校内布置的涉及到 CS 架构的一切任务, 学会了, 别人要好几天搞定的自己几个小时就行了, 而且看起来还会比别人的舒服的多. 下一篇将会介绍利用这个框架实现另一个项目 -- 教学白板.
来源: https://www.cnblogs.com/csu-lmw/p/10981374.html