多用户即时通信系统(文字版山寨QQ)
项目开发流程简介
项目结构
服务器端
客户端
项目说明
功能实现
- 已经实现功能:
- 用户的登录和无异常退出
- 显示在线用户列表
- 群发消息
- 在线私聊消息
- 离线私聊消息(单条或多条)
- 在线文件发送
- 离线文件发送
- 服务器推送新闻
- 未实现功能:
- 用户注册
- 图形界面
- 数据库连接(暂时使用集合替代)
都是自己按照老韩的视频写的,离线功能参考了一些其他地方的
截图
具体代码
服务器端
common
- common_Message
package com.recorder.common; import java.io.Serializable; /** * @author 紫英 * @version 1.0 * @discription 消息类 */ public class Message implements Serializable { private static final long serialVersionUID = 1L; private String sender;//发送者 private String getter;//接收者 private String content;//消息内容 private String sendTime;//发送时间 private String mesType;//消息类型[可以在接口定义消息类型] //增加和文件相关的属性 private byte[] fileBytes;//存放文件的数组 private int fileLen = 0;//文件大小 private String src;//发送路径 private String dest;//目标路径 public byte[] getFileBytes() { return fileBytes; } public void setFileBytes(byte[] fileBytes) { this.fileBytes = fileBytes; } public int getFileLen() { return fileLen; } public void setFileLen(int fileLen) { this.fileLen = fileLen; } public String getSrc() { return src; } public void setSrc(String src) { this.src = src; } public String getDest() { return dest; } public void setDest(String dest) { this.dest = dest; } public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } public String getGetter() { return getter; } public void setGetter(String getter) { this.getter = getter; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public String getSendTime() { return sendTime; } public void setSendTime(String sendTime) { this.sendTime = sendTime; } public String getMesType() { return mesType; } public void setMesType(String mesType) { this.mesType = mesType; } }
- common_MessageType
package com.recorder.common; /** * @author 紫英 * @version 1.0 * @discription 消息类型接口 */ public interface MessageType { //在接口中定义一些常量,不同的常量值表示不同的消息类型 String MESSAGE_LOGIN_SUCCESS = "1";//表示登陆成功 String MESSAGE_LOGIN_FAILED = "2";//表示登陆失败 String MESSAGE_COMMON = "3";//表示普通消息包 String MESSAGE_COMMON_ALL = "4";//表示群发消息包 String MESSAGE_GET_ONLINEFRIEND = "5";//表示请求在线用户信息 String MESSAGE_RETURN_ONLINEFRIEND = "6";//表示返回在线用户信息 String MESSAGE_CLIENT_EXIT = "7";//表示客户端请求退出 String MESSAGE_FILE = "8";//表示文件发送 }
- common_User
package com.recorder.common; import java.io.Serializable; /** * @author 紫英 * @version 1.0 * @discription 用户类 */ public class User implements Serializable { private String userID; private String password; private static final long serialVersionUID = 1L; public String getUserID() { return userID; } public void setUserID(String userID) { this.userID = userID; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public User(String userID, String password) { this.userID = userID; this.password = password; } }
service
- service_QQServer
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import com.recorder.common.User; import com.sun.org.apache.bcel.internal.generic.NEW; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.ServerSocket; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.concurrent.ConcurrentHashMap; /** * @author 紫英 * @version 1.0 * @discription 服务器端, 监听9999端口 */ public class QQServer { private ServerSocket serverSocket = null; //创建一个集合用于存放合法用户 //使用ConcurrentHashMap没有线程安全问题(处理了线程同步问题),而hashmap没有处理线程安全问题 //所以在多线程中推荐使用ConcurrentHashMap private static ConcurrentHashMap<String, User> validUsers = new ConcurrentHashMap<>(); //存储发给离线用户的消息 public static ConcurrentHashMap<String, ArrayList<Message>> offlineUsers = new ConcurrentHashMap<>(); static { //在静态代码块初始化一些合法用户 validUsers.put("user", new User("user", "password")); validUsers.put("root", new User("root", "root")); validUsers.put("666", new User("666", "123456")); validUsers.put("100", new User("100", "123456")); validUsers.put("紫英", new User("紫英", "123456")); validUsers.put("天河", new User("天河", "123456")); validUsers.put("至尊宝", new User("至尊宝", "123456")); validUsers.put("紫霞", new User("紫霞", "123456")); } //添加离线用户 public static void addOfflineUser(String getter, ArrayList<Message> messages) { offlineUsers.put(getter, messages); } //获取离线消息 public static ArrayList<Message> getOfflineUser(String getter) { return offlineUsers.get(getter); } //判断是否是离线用户 public static boolean containsOfflineUser(String getter){ return offlineUsers.containsKey(getter); } //移除离线用户 public static void removeOfflineUser(String getter){ offlineUsers.remove(getter); } //验证用户是否为合法用户的方法 private boolean checkUser(String userId, String password) { User validUser = validUsers.get(userId); if (validUser == null) { //get返回null说明该用户不在合法用户集合中返回false return false; } if (!validUser.getPassword().equals(password)) { //用户名正确但是密码错误 return false; } /*if (!validUsers.isEmpty()) {//判断是否重复登录 for (int i = 0; i < validUsers.size(); i++) { if (validUser.equals(validUsers.get(i))) { return false; } } }*/ else return true;//过关斩将的方式 } public QQServer() { //启动新闻推送线程 try { System.out.println("服务端正在9999端口监听..."); new SendNews().start(); System.out.println("新闻推送线程启动..."); this.serverSocket = new ServerSocket(9999); while (true) { //即使已经有一个客户端连接还是要继续监听,所以用while Socket socket = serverSocket.accept();//如果没有客户端连接会阻塞在这里 //得到socket相关的对象输入流 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); //读取客户端发送来的user对象 User user = (User) ois.readObject(); //创建一个Message对象用于回复客户端 Message message = new Message(); //创建一个socket相关的对象输出流用于发送message ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); //验证 if (checkUser(user.getUserID(), user.getPassword())) {//调用方法判断是否为合法用户 //合法用户 message.setMesType(MessageType.MESSAGE_LOGIN_SUCCESS); oos.writeObject(message); //这时创建一个持有socket的线程用于和客户端保持通信 ServerConnetClientThread serverConnetClientThread = new ServerConnetClientThread(socket, user.getUserID()); //启动线程 serverConnetClientThread.start(); //将线程放入集合管理 ServerConnetClientThreadManager.addServerConnetClientThread(user.getUserID(), serverConnetClientThread); //登陆成功后先检测是否有离线消息 ArrayList<Message> arrayList = getOfflineUser(user.getUserID()); if (arrayList != null){ for (int i = 0; i < arrayList.size(); i++) { Message mes = arrayList.get(i); ObjectOutputStream objectOutputStream = new ObjectOutputStream(ServerConnetClientThreadManager. getServerConnetClientThread(user.getUserID()).getSocket().getOutputStream()); objectOutputStream.writeObject(mes); objectOutputStream.flush(); //消息发送完后移除 removeOfflineUser(mes.getGetter()); } } } else { //登陆失败 message.setMesType(MessageType.MESSAGE_LOGIN_FAILED); oos.writeObject(message); //关闭socket socket.close(); } } } catch (Exception e) { e.printStackTrace(); } finally { //如果退出了while循环说明服务器不再监听,这时关闭serverSocket try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
- service_SendNews
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import com.recorder.utils.Utility; import java.io.IOException; import java.io.ObjectOutputStream; import java.text.SimpleDateFormat; import java.util.*; /** * @author 紫英 * @version 1.0 * @discription 用于服务器端推送消息 */ public class SendNews extends Thread { @Override public void run() { while (true) { //实现一直推送 System.out.println("请输入要推送的内容:(回复TD退出推送线程)"); String news = Utility.readString(50); if ("TD".equals(news)){ break; } //创建一个消息对象对在线用户实施群发 Message message = new Message(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); message.setSendTime(sdf.format(System.currentTimeMillis())); message.setContent(news); message.setMesType(MessageType.MESSAGE_COMMON_ALL); message.setSender("服务器"); System.out.println("服务器推送消息:" + news); HashMap<String, ServerConnetClientThread> hm = ServerConnetClientThreadManager.getHm(); Set<String> keySet = hm.keySet(); Iterator<String> iterator = keySet.iterator(); while (iterator.hasNext()) { String getterId = iterator.next().toString(); try { ObjectOutputStream oos = new ObjectOutputStream(hm.get(getterId).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } } } System.out.println("推送线程已退出!"); } }
- service_ServerConnetClientThread
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Set; /** * @author 紫英 * @version 1.0 * @discription 该类的某个对象用于和某个客户端保持通信 */ public class ServerConnetClientThread extends Thread { private Socket socket; private String userId;//连接到服务器端的userId public ServerConnetClientThread(Socket socket, String userId) { this.socket = socket; this.userId = userId; } public Socket getSocket() { return socket; } @Override public void run() { //用于发送和接收消息 while (true) { System.out.println("服务端和客户端" + userId + "保持通信,读取数据..."); try { ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Message message = (Message) ois.readObject(); //根据message类型做相应的业务处理 if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINEFRIEND)) { System.out.println(message.getSender() + "请求拉取在线用户列表"); Message serverMessage = new Message(); serverMessage.setMesType(MessageType.MESSAGE_RETURN_ONLINEFRIEND); String onlineUserList = ServerConnetClientThreadManager.getOnlineUserList(); serverMessage.setContent(onlineUserList); serverMessage.setGetter(message.getSender()); //返回客户端 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); oos.writeObject(serverMessage); } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON)) { //先查看用户是否在线 if (!ServerConnetClientThreadManager.isOnline(message.getGetter())) { //线程中不存在就说明离线,将消息存在服务器的ConcurrentHashMap集合中 if (!QQServer.containsOfflineUser(message.getGetter())) { //集合中没有就添加 ArrayList<Message> messageArrayList = new ArrayList<>(); messageArrayList.add(message); QQServer.addOfflineUser(message.getGetter(), messageArrayList); } else { //集合中存在的话就往已经存在的(离线用户消息集合)里边放 ArrayList<Message> arrayList = QQServer.getOfflineUser(message.getGetter()); arrayList.add(message); QQServer.addOfflineUser(message.getGetter(), arrayList); } System.out.println("离线消息记录——【" + message.getSender() + "】对【" + message.getGetter() + "】" + "发送了一条消息"); } else { //在线的话 //消息转发 //先根据message取得消息接收方id,再获取对应的线程 ServerConnetClientThread serverConnetClientThread = ServerConnetClientThreadManager.getServerConnetClientThread(message.getGetter()); //得到socket对应的对象输出流 ObjectOutputStream oos = new ObjectOutputStream(serverConnetClientThread.getSocket().getOutputStream()); oos.writeObject(message); //如果用户不在线可以将消息保存到数据库,实现离线消息 } } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_ALL)) { //消息群发 //遍历线程集合取出keymap HashMap<String, ServerConnetClientThread> hm = ServerConnetClientThreadManager.getHm(); Set<String> keySet = hm.keySet(); Iterator<String> iterator = keySet.iterator(); while (iterator.hasNext()) { String getterId = iterator.next().toString(); if (!getterId.equals(message.getSender())) {//首先排除自己 ObjectOutputStream oos = new ObjectOutputStream(hm.get(getterId).getSocket().getOutputStream()); oos.writeObject(message); } } } else if (message.getMesType().equals(MessageType.MESSAGE_FILE)) { //文件消息 //先判断用户是否在线 if (!ServerConnetClientThreadManager.isOnline(message.getGetter())) { //如果不在线 if (!QQServer.containsOfflineUser(message.getGetter())) { //集合中没有就添加 ArrayList<Message> messageArrayList = new ArrayList<>(); messageArrayList.add(message); QQServer.addOfflineUser(message.getGetter(), messageArrayList); } else { //集合中存在的话就往已经存在的(离线用户消息集合)里边放 ArrayList<Message> arrayList = QQServer.getOfflineUser(message.getGetter()); arrayList.add(message); QQServer.addOfflineUser(message.getGetter(), arrayList); } System.out.println("离线消息记录——【" + message.getSender() + "】给【" + message.getGetter() + "】" + "发送文件【" + message.getSrc() + "】到" + message.getGetter() + "的【" + message.getDest() + "】"); } else { ObjectOutputStream oos = new ObjectOutputStream(ServerConnetClientThreadManager. getServerConnetClientThread(message.getGetter()).getSocket().getOutputStream()); oos.writeObject(message);//消息转发 } } else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) { System.out.println("客户端" + message.getSender() + "退出"); ServerConnetClientThreadManager.removeClient(message.getSender()); socket.close();//关闭socket break;//退出循环结束线程 } } catch (Exception e) { e.printStackTrace(); } } } }
- service_ServerConnetClientThreadManager
package com.recorder.service; import java.util.HashMap; import java.util.Iterator; /** * @author 紫英 * @version 1.0 * @discription 用于管理和客户端通信的线程 */ public class ServerConnetClientThreadManager { private static HashMap<String, ServerConnetClientThread> hm = new HashMap<>(); //查看线程是否存在(用户是否在线) public static boolean isOnline(String userId){ return hm.containsKey(userId); } //添加方法 public static void addServerConnetClientThread(String userId, ServerConnetClientThread serverConnetClientThread) { hm.put(userId, serverConnetClientThread); } public static HashMap<String, ServerConnetClientThread> getHm() { return hm; } //根据userId返回线程 public static ServerConnetClientThread getServerConnetClientThread(String userId) { return hm.get(userId); } //移除方法 public static void removeClient(String userId) { hm.remove(userId); } //返回在线用户列表 public static String getOnlineUserList() { String onlineUserList = ""; Iterator<String> iterator = hm.keySet().iterator(); while (iterator.hasNext()) { onlineUserList += iterator.next().toString() + " "; } return onlineUserList; } }
frame
package com.recorder.frame; import com.recorder.service.QQServer; /** * @author 紫英 * @version 1.0 * @discription 创建QQServer(),用于启动法服务器 */ public class QQFrame { public static void main(String[] args) { new QQServer(); System.out.println("服务器关闭"); } }
util
package com.recorder.utility; import java.util.*; /** * @author 紫英 * @version 1.0 * @discription 接受用户输入工具类 */ /** 工具类的作用: 处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。 */ /** */ public class Utility { //静态属性。。。 private static Scanner scanner = new Scanner(System.in); /** * 功能:读取键盘输入的一个菜单选项,值:1——5的范围 * @return 1——5 */ public static char readMenuSelection() { char c; for (; ; ) { String str = readKeyBoard(1, false);//包含一个字符的字符串 c = str.charAt(0);//将字符串转换成字符char类型 if (c != '1' && c != '2' && c != '3' && c != '4' && c != '5'&& c != '6') { System.out.print("选择错误,请重新输入:"); } else break; } return c; } /** * 功能:读取键盘输入的一个字符 * @return 一个字符 */ public static char readChar() { String str = readKeyBoard(1, false);//就是一个字符 return str.charAt(0); } /** * 功能:读取键盘输入的一个字符,如果直接按回车,则返回指定的默认值;否则返回输入的那个字符 * @param defaultValue 指定的默认值 * @return 默认值或输入的字符 */ public static char readChar(char defaultValue) { String str = readKeyBoard(1, true);//要么是空字符串,要么是一个字符 return (str.length() == 0) ? defaultValue : str.charAt(0); } /** * 功能:读取键盘输入的整型,长度小于2位 * @return 整数 */ public static int readInt() { int n; for (; ; ) { String str = readKeyBoard(10, false);//一个整数,长度<=10位 try { n = Integer.parseInt(str);//将字符串转换成整数 break; } catch (NumberFormatException e) { System.out.print("数字输入错误,请重新输入:"); } } return n; } /** * 功能:读取键盘输入的 整数或默认值,如果直接回车,则返回默认值,否则返回输入的整数 * @param defaultValue 指定的默认值 * @return 整数或默认值 */ public static int readInt(int defaultValue) { int n; for (; ; ) { String str = readKeyBoard(10, true); if (str.equals("")) { return defaultValue; } //异常处理... try { n = Integer.parseInt(str); break; } catch (NumberFormatException e) { System.out.print("数字输入错误,请重新输入:"); } } return n; } /** * 功能:读取键盘输入的指定长度的字符串 * @param limit 限制的长度 * @return 指定长度的字符串 */ public static String readString(int limit) { return readKeyBoard(limit, false); } /** * 功能:读取键盘输入的指定长度的字符串或默认值,如果直接回车,返回默认值,否则返回字符串 * @param limit 限制的长度 * @param defaultValue 指定的默认值 * @return 指定长度的字符串 */ public static String readString(int limit, String defaultValue) { String str = readKeyBoard(limit, true); return str.equals("")? defaultValue : str; } /** * 功能:读取键盘输入的确认选项,Y或N * 将小的功能,封装到一个方法中. * @return Y或N */ public static char readConfirmSelection() { System.out.println("请输入你的选择(Y/N): 请小心选择"); char c; for (; ; ) {//无限循环 //在这里,将接受到字符,转成了大写字母 //y => Y n=>N String str = readKeyBoard(1, false).toUpperCase(); c = str.charAt(0); if (c == 'Y' || c == 'N') { break; } else { System.out.print("选择错误,请重新输入:"); } } return c; } /** * 功能: 读取一个字符串 * @param limit 读取的长度 * @param blankReturn 如果为true ,表示 可以读空字符串。 * 如果为false表示 不能读空字符串。 * * 如果输入为空,或者输入大于limit的长度,就会提示重新输入。 * @return */ private static String readKeyBoard(int limit, boolean blankReturn) { //定义了字符串 String line = ""; //scanner.hasNextLine() 判断有没有下一行 while (scanner.hasNextLine()) { line = scanner.nextLine();//读取这一行 //如果line.length=0, 即用户没有输入任何内容,直接回车 if (line.length() == 0) { if (blankReturn) return line;//如果blankReturn=true,可以返回空串 else continue; //如果blankReturn=false,不接受空串,必须输入内容 } //如果用户输入的内容大于了 limit,就提示重写输入 //如果用户如的内容 >0 <= limit ,我就接受 if (line.length() < 1 || line.length() > limit) { System.out.print("输入长度(不能大于" + limit + ")错误,请重新输入:"); continue; } break; } return line; } }
客户端
common、util同服务器端
view
package com.recorder.view; import com.recorder.service.FileClientService; import com.recorder.service.MessageClientService; import com.recorder.service.UserClientService; import com.recorder.utility.Utility; /** * @author 紫英 * @version 1.0 * @discription */ public class QQviwe { private boolean loop = true;//是否显示菜单 private String key = " "; private UserClientService userClientService = new UserClientService();//用于校验用户信息 private MessageClientService messageClientService = new MessageClientService();//用于用户消息的转发 private FileClientService fileClientService = new FileClientService();//用于发送文件 public static void main(String[] args) { new QQviwe().mainView(); System.out.println("客户端退出登录..."); } public void mainView() { while (loop) { System.out.println("==========欢迎登陆文字版QQ=========="); System.out.println(" \t\t1.用户登录"); System.out.println(" \t\t2.退出系统"); System.out.println(" 请输入你的选择:"); key = Utility.readString(1);//获取一个选项 // 根据选项来处理不同的请求 switch (key) { case "1": System.out.println("请输入用户名:"); String userId = Utility.readString(50); System.out.println("请输入密 码:"); String pwd = Utility.readString(50); // 验证是否正确 if (userClientService.checkUser(userId, pwd)) { System.out.println("~~~User:\t" + userId + "\twelcome!~~~"); //登录成功进入二级菜单 while (loop) { System.out.println("==========欢迎登陆文字版QQ(" + userId + "の界面)=========="); System.out.println(" \t\t1.显示在线用户列表"); System.out.println(" \t\t2.群发消息"); System.out.println(" \t\t3.私聊消息"); System.out.println(" \t\t4.发送文件"); System.out.println(" \t\t5.退出系统"); System.out.println(" 请输入你的选择:\n"); key = Utility.readString(1);//获取一个选项 switch (key) { case "1": userClientService.getOnlineList(); break; case "2": System.out.println("请输入想对大家说的话:"); String s = Utility.readString(50); //调用方法 messageClientService.sendMessageAll(userId, s); break; case "3": System.out.println("请输入聊天对象ID(如不在线会发送离线消息):"); String getter = Utility.readString(50); System.out.println("请输入消息内容:"); String content = Utility.readString(200); //调用方法发送消息 messageClientService.sendMessage(userId, getter, content); break; case "4": System.out.println("请输入目标名称:"); String getterId = Utility.readString(50); System.out.println("请输入源文件路径:(x:\\xx.jpg)"); String src = Utility.readString(50); System.out.println("请输入目标文件路径:(x:\\xx.jpg)"); String dest = Utility.readString(50); fileClientService.sendFile(src,dest,userId,getterId); break; case "5": loop = false; userClientService.logout(); break; } } } else { System.out.println("用户名或密码有误!"); } break; case "2": loop = false; break; } } } }
service
- service_UserClientService
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import com.recorder.common.User; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.Socket; /** * @author 紫英 * @version 1.0 * @discription 用于校验用户信息(登陆注册)等服务 */ public class UserClientService { // 因为可能在其他的方用到用户信息所以做成成员属性 User user = new User(); //同理socket也需要在别的地方用到所以也做成属性 Socket socket = new Socket(); //写一个方法用于校验用户名密码是否正确 public boolean checkUser(String userId, String pwd) { boolean flag = false; //获取User对象信息,将键盘输入赋值给当前的user user.setUserID(userId); user.setPassword(pwd); //连接到服务器端发送user对象 try { socket = new Socket(InetAddress.getLocalHost(), 9999); //得到一个对象输出流来发送对象 ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream()); //发送user对象 oos.writeObject(user); //读取从服务器发来的Message对象 ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); Message message = (Message) ois.readObject(); //判断消息类型来决定是否登陆成功 if (message.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCESS)) { //登陆成功 //创建一个和服务器端保持通信的线程 (ClientConnetServerThread类) ClientConnetServerThread clientConnetServerThread = new ClientConnetServerThread(socket); //启动客户端的线程 clientConnetServerThread.start(); //为了以后客户端功能的拓展我们将线程放入集合中管理 ClientConnetServerThreadManager.addClientConnetServerThread(userId, clientConnetServerThread); flag = true; } else { //登陆失败,就不启动线程,关闭socket socket.close(); } } catch (Exception e) { e.printStackTrace(); } return flag; } //向服务器端请求用户列表 public void getOnlineList() { //发送一个message Message message = new Message(); message.setMesType(MessageType.MESSAGE_GET_ONLINEFRIEND); message.setSender(user.getUserID()); //获取当前线程的socket对应的对象输出流 try { ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager .getClientConnetServerThread(user.getUserID())/*通过用户id取得对应线程*/.getSocket().getOutputStream()); //发送message对象,请求在线用户列表 oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } } //向服务器发送结束线程消息 public void logout() { Message message = new Message(); message.setMesType(MessageType.MESSAGE_CLIENT_EXIT); //因为在上面的checkUser中已经给user复制了所以可以直接用 message.setSender(user.getUserID()); try { ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager. getClientConnetServerThread(user.getUserID()).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } System.out.println(user.getUserID() + "客户端退出"); System.exit(0); } }
- service_FileClientService
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import java.io.*; /** * @author 紫英 * @version 1.0 * @discription 文件传输服务 */ public class FileClientService { /*** * * @param src 源文件目录 * @param dest 目标目录 * @param sender 发送者 * @param getter 接收者 */ public void sendFile(String src, String dest, String sender, String getter) { //读取src路径的文件封装到message对象中 Message message = new Message(); message.setMesType(MessageType.MESSAGE_FILE); message.setSender(sender); message.setGetter(getter); message.setSrc(src); message.setDest(dest); //读取文件 FileInputStream fis = null; byte[] fileBytes = new byte[(int) new File(src).length()]; try { fis = new FileInputStream(src); fis.read(fileBytes);//将文件读取到程序的字节数组 //将文件对应的字节数组设置到message对象 message.setFileBytes(fileBytes); } catch (Exception e) { e.printStackTrace(); } finally { if (fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } //提示消息 System.out.println("\n" + sender + "给" + getter + "发送文件:" + src + "到" + getter + "电脑的:" + dest + "路径下"); //发送 try { ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager. getClientConnetServerThread(sender).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } } }
- service_MessageClientService
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import javax.swing.text.StringContent; import java.io.IOException; import java.io.ObjectOutputStream; import java.text.SimpleDateFormat; import java.util.Date; /** * @author 紫英 * @version 1.0 * @discription 用于发送消息 */ public class MessageClientService { /** * @param sender 发送方 * @param getter 接收方 * @param content 消息内容 */ public void sendMessage(String sender, String getter, String content) { Message message = new Message(); message.setSender(sender); message.setGetter(getter); message.setContent(content); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); message.setSendTime(sdf.format(System.currentTimeMillis())); message.setMesType(MessageType.MESSAGE_COMMON); /* System.out.println(message.getSendTime() + ":\n" + sender+ "对" + getter + "说" + content );*/ System.out.println(sender + "对" + getter + "的消息发送成功!"); try { ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager. getClientConnetServerThread(message.getSender()).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } } public void sendMessageAll(String sender, String content) { Message message = new Message(); message.setSender(sender); message.setContent(content); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); message.setSendTime(sdf.format(System.currentTimeMillis())); message.setMesType(MessageType.MESSAGE_COMMON_ALL); System.out.println(sender + "对大家发送的消息发送成功!"); try { ObjectOutputStream oos = new ObjectOutputStream(ClientConnetServerThreadManager. getClientConnetServerThread(message.getSender()).getSocket().getOutputStream()); oos.writeObject(message); } catch (IOException e) { e.printStackTrace(); } } }
- service_ClientConnetServerThread
package com.recorder.service; import com.recorder.common.Message; import com.recorder.common.MessageType; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.management.MemoryType; import java.net.Socket; import java.util.concurrent.TransferQueue; /** * @author 紫英 * @version 1.0 * @discription 和服务器端保持通信的线程,持有一个socket类 */ public class ClientConnetServerThread extends Thread { // 该线程需要持有一个socket private boolean loop = true; public boolean isLoop() { return loop; } public void setLoop(boolean loop) { this.loop = loop; } private Socket socket; //为了更方便的使用socket属性添加getset方法 public Socket getSocket() { return socket; } public void setSocket(Socket socket) { this.socket = socket; } public ClientConnetServerThread(Socket socket) { //使用构造器接收一个socket对象 this.socket = socket; } @Override public void run() { //Thread需要在后台和服务器端通信,因此使用while循环 while (loop) { System.out.println("客户端线程等待读取从服务器端发来的消息..."); try { ObjectInputStream ois = new ObjectInputStream(socket.getInputStream()); // 如果没有接收到服务器端发来的Message对象线程会阻塞在readObject()方法 Message message = (Message) ois.readObject(); //根据不同的message种类来写对应的业务逻辑 if (message.getMesType().equals (MessageType.MESSAGE_RETURN_ONLINEFRIEND)) {//如果读取到的message类型是返回在线用户列表 //取出message信息并显示 System.out.println("==========当前用户列表=========="); String[] onlineUser = message.getContent().split(" "); //规定返回用户信息用空格隔开 for (int i = 0; i < onlineUser.length; i++) { System.out.println("用户:\t【" + onlineUser[i] + "】\t在线"); } } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON)) { //如果读取到的message类型是私聊消息,显示在控制台 System.out.println( message.getSendTime() + ":\n【私聊消息】——【" + message.getSender() + "】对您说:" + message.getContent()); } else if (message.getMesType().equals(MessageType.MESSAGE_COMMON_ALL)) { System.out.println(message.getSendTime() + ":\n【群发消息】【" + message.getSender() + "】对大家说:" + message.getContent()); } else if (message.getMesType().equals(MessageType.MESSAGE_FILE)) { //可以让用户指定路径 System.out.println("\n【" + message.getSender() + "】给" + message.getGetter() + "发送文件:" + message.getSrc() + "到我电脑的:" + message.getDest() + "路径下"); //取出message对象的文件保存到本地磁盘 FileOutputStream fos = new FileOutputStream(message.getDest()); fos.write(message.getFileBytes()); fos.close(); System.out.println("\n文件保存成功!"); } } catch (Exception e) { e.printStackTrace(); } } } }
- service_ClientConnetServerThreadManager
package com.recorder.service; import java.util.HashMap; /** * @author 紫英 * @version 1.0 * @discription 管理客户端线程(集合) */ public class ClientConnetServerThreadManager { private static HashMap<String, ClientConnetServerThread> hm = new HashMap<>(); //添加到集合的方法 public static void addClientConnetServerThread(String userId, ClientConnetServerThread clientConnetServerThread) { hm.put(userId, clientConnetServerThread); } //根据userId获取对应线程 public static ClientConnetServerThread getClientConnetServerThread(String userId) { return hm.get(userId); } }
关于2021版idea并行程序的设置
1.
2.
3.
都看到这里了,有用的话点个赞吧!
本文来自博客园,作者:紫英626,转载请注明原文链接:https://www.cnblogs.com/recorderM/p/15862262.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
2021-02-03 常用快捷键以及dos命令