<Java>20 项目(多用户通讯系统)
本文最后更新于:2022年5月24日 早上
20 项目:多用户通讯系统
项目见(多用户通讯系统)
服务端启动位置(多用户通讯系统\项目:通讯系统(服务端)\src\com\melody\transmission\common\ClientConnect.java)
客户端启动位置(项目:通讯系统(客户端)\src\com\melody\transmission\Test1.java)
20.1 需求分析
- 用户登录
- 拉取在线用户列表
- 无异常退出(客户端、服务端)
- 私聊
- 群聊
- 发文件
- 服务器推送新闻
20.1.1 登录
服务端
- 客户端连接到服务器后,会得到一个
Socket
- 启动一个线程,该线程持有该
Socket
对象 - 为了更好地管理线程,需要使用集合来管理
客户端
- 和服务端通信时,使用对象方式,可以使用对象流来读写
- 客户端连接到服务端后,也会得到
Socket
- 启动一个线程,该线程持有该
Socket
对象 - 为了更好地管理线程,需要使用集合来管理
20.1.2 无异常退出
写下面内容的之前,我已经把代码都完成了。
韩老师的思路都是最基本的思路,这些其实应该不参考笔记自己去想。
想不通的地方才看答案(笔记),我认为是比较好的方法。
服务端
- 服务器端和某个客户端通信的线程接到一个退出请求后,关闭 socket 并退出线程
客户端
- 调用方法,向服务端发送登出请求,之后客户端调用方法正常退出
下略
没记
附录
多用户通讯系统
开始写的时候思路没整理好,最后乱七八糟的
又没什么注释,所以别往下翻了
通用部分(客户端 & 服务端)
MyTools.java:工具类
UserData.java:用户数据类,包含完整的用户信息。这些信息存储在服务端。每个客户端只可能在验证通过的场合,持有最多一个来自服务端的属于客户自己的该类对象
User.java:简易的用户类。只是用以传递一组账号和密码。一般由客户端发送往服务端。这些账号和密码未必是正确的。
Message.java:数据包。客户端和服务端的所有通信都以传递该数据包的形式进行。
MessageType.java:代码表。客户端和服务端各自持有一份相同的该表,用以对数据包进行识别。
-
MyTools.java
精简了一下放上来
package com; import java.io.*; import java.util.Arrays; import java.util.Scanner; public class MyTools { private static Scanner inp = new Scanner(System.in); //这个方法能读取一个 输入流 的内容 public static byte[] whatInputIs(InputStream is) throws IOException { ByteArrayOutputStream bis = new ByteArrayOutputStream(); int len = 0; byte[] bytes = new byte[1024]; while ((len = is.read(bytes, 0, bytes.length)) != -1) { bis.write(bytes, 0, len); } return bis.toByteArray(); } }
-
User.java
package com.melody.transmission.common; import java.io.Serializable; /** * @author Melody * @version 1.0 */ public class User implements Serializable { private String id; private String pw; public User(String id, String pw) { this.id = id; this.pw = pw; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getPw() { return pw; } public void setPw(String pw) { this.pw = pw; } }
-
UserData.java
package com.melody.transmission.common; import java.io.Serializable; import java.util.Objects; /** * @author Melody * @version 1.0 */ public class UserData implements Serializable { private static final long serialVersionUID = 1L; private final String ID; private String name; private String pw; private boolean online = false; private String[] friendID; public final StringBuilder leftMessage = new StringBuilder(); UserData(String name, String pw, String ID) { this.name = name; this.pw = pw; this.ID = ID; } public String getId() { return ID; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getPw() { return pw; } public void setPw(String pw) { this.pw = pw; } public boolean isOnline() { return online; } public void setOnline(boolean online) { this.online = online; } public String listed() { return ((online ? "·在线" : "离线中") + "\t用户名:(" + ID + ")" + name); } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserData userData = (UserData) o; return online == userData.online && Objects.equals(ID, userData.ID) && Objects.equals(name, userData.name) && Objects.equals(pw, userData.pw); } @Override public int hashCode() { return Objects.hash(ID, name, pw, online); } }
-
Message.java
package com.melody.transmission.common; import java.io.Serializable; import java.time.LocalDateTime; /** * @author Melody * @version 1.0 */ public class Message implements Serializable, MessageType { private static final long serialVersionUID = 1L; private String message; private String time; private String sender; private String receiver; private String messageType; private Object object; public Message(String message, String sender, String receiver, String messageType) { this.message = message; this.time = getDateTime(); this.sender = sender; this.receiver = receiver; this.messageType = messageType; } public Object getObject() { return object; } public void setObject(Object object) { this.object = object; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getTime() { return time; } public void setTime(String time) { this.time = time; } public String getSender() { return sender; } public void setSender(String sender) { this.sender = sender; } public String getReceiver() { return receiver; } public void setReceiver(String receiver) { this.receiver = receiver; } public String getMessageType() { return messageType; } public void setMessageType(String messageType) { this.messageType = messageType; } private static String getDate() { return getDate(LocalDateTime.now()); } private static String getDate(LocalDateTime ldt) { int mon = ldt.getMonthValue(); int day = ldt.getDayOfMonth(); return ldt.getYear() + "." + (mon < 10 ? "0" + mon : mon) + "." + (day < 10 ? "0" + day : day); } private static String time() { return time(LocalDateTime.now()); } private static String time(LocalDateTime ldt) { int hour = ldt.getHour(); int min = ldt.getMinute(); int sec = ldt.getSecond(); return (hour < 10 ? "0" + hour : hour) + ":" + (min < 10 ? "0" + min : min) + ":" + (sec < 10 ? "0" + sec : sec); } public static String getDateTime() { LocalDateTime ldt = LocalDateTime.now(); return getDate(ldt) + " " + time(); } }
-
MessageType.java
package com.melody.transmission.common; /** * @author Melody * @version 1.0 */ public interface MessageType { String LOGIN_SUCCESS = "lS"; String LOGIN_FAILED = "lF"; String LOGIN_USER_NOT_EXIST = "lU"; String LOGIN_WRONG_PW = "lP"; String LOGGED_SHOW_LIST = "11SL"; String LOGGED_SHOW_ONLINE_LIST = "11SO"; String CHAT = "CM"; String GROUP_CHAT = "GM"; String FILE = "CF"; String LOGGED_CHANGE_NAME = "15CN"; String LOGGED_CHANGE_PW = "16CP"; String LOGGED_CHANGE_HIDE = "17CH"; String LOGGED_LOGOUT = "19OT"; String REQUEST_ALLOWED = "RE"; String REQUEST_REJECTED = "RR"; String REPEAT_LOGIN = "EXC_RL"; }
服务端
ClientConnet.java:入口
ClientLogin.java:管理所有登录活动。该线程将持续监听用户登录。用户登录成功后启动一个专用的会话线程。
Serve.java:这就是那个专用会话线程。会持续监听用户请求,并作出反应。
ServeCollection.java:该类收集所有活动中的 Serve 线程。
UserDataEnum.java:记录所有用户数据的枚举类。数据库的替代方法(还没学数据库)
-
ClientConnet.java(入口)
package com.melody.transmission.common; import com.melody.transmission.login.ClientLogin; import java.net.Socket; import java.util.HashMap; /** * @author Melody * @version 1.0 */ public class ClientConnect { public final static String thisIP = "192.168.3.16"; public final static HashMap<String, UserData> data = new HashMap<>(); public final static HashMap<String, Socket> sockets = new HashMap<>(); static { for (UserDataEnum userData : UserDataEnum.values()) { data.put(userData.user.getId(), userData.user); } } public static void main(String[] args){ new Thread(new ClientLogin()).start(); } }
-
ClientLogin.java
package com.melody.transmission.login; import com.melody.transmission.common.*; import com.melody.transmission.connected.Serve; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * @author Melody * @version 1.0 */ public class ClientLogin implements Runnable { @Override public void run() { ServerSocket serverSocket = null; try { serverSocket = new ServerSocket(9000); while (true) { login(serverSocket); } } catch (IOException e) { e.printStackTrace(); } } private void login(ServerSocket serverSocket) throws IOException { Socket clientSocket = serverSocket.accept(); ObjectInputStream ois = new ObjectInputStream(clientSocket.getInputStream()); ObjectOutputStream oos = new ObjectOutputStream(clientSocket.getOutputStream()); String checkRes; User clientUser = null; Message toSend = new Message(null, InetAddress.getLocalHost().getHostAddress(), clientSocket.getInetAddress().getHostAddress(), null); toSend.setObject(null); try { clientUser = (User) ois.readObject(); checkRes = checkUserData(clientUser); } catch (Exception e) { checkRes = MessageType.LOGIN_FAILED; } toSend.setMessageType(checkRes); if (checkRes.equals(MessageType.LOGIN_SUCCESS)) { ClientConnect.data.get(clientUser.getId()).setOnline(true); toSend.setObject(ClientConnect.data.get(clientUser.getId())); oos.writeObject(toSend); Serve serve = new Serve(clientSocket, oos, ois, clientUser.getId()); new Thread(serve).start(); // ClientSocketsCollection.addSocket(clientUser.getId(), clientSocket); ServeCollection.add(clientUser.getId(), serve); } else { oos.writeObject(toSend); oos.close(); ois.close(); clientSocket.close(); } } private String checkUserData(User inp) { UserData inpIDUser = ClientConnect.data.get(inp.getId()); if (inpIDUser == null) { return MessageType.LOGIN_USER_NOT_EXIST; } else if (!inpIDUser.getPw().equals(inp.getPw())) { return MessageType.LOGIN_WRONG_PW; } Serve temp = ServeCollection.get(inpIDUser.getId()); if (temp != null){ try { temp.sendRepeatLoginException(); System.out.println(Message.getDateTime() + " " + temp.id + " 重复登陆"); temp.forceLogout(); } catch (IOException e) { return MessageType.LOGIN_FAILED; } } return MessageType.LOGIN_SUCCESS; } }
-
Serve.java(主干)
package com.melody.transmission.connected; import com.melody.transmission.common.*; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; /** * @author Melody * @version 1.0 */ public class Serve implements Runnable { private Socket clientSocket; private ObjectOutputStream oos; private ObjectInputStream ois; private boolean connecting = true; public final String id; public Serve(Socket clientSocket, ObjectOutputStream oos, ObjectInputStream ois, String id) { this.clientSocket = clientSocket; this.oos = oos; this.ois = ois; this.id = id; } @Override public void run() { System.out.println(Message.getDateTime() + " " + id + " 登录 " + clientSocket.getLocalAddress().getHostName()); ClientConnect.data.get(id).setOnline(true); while (connecting) { try { serve(); } catch (IOException | ClassNotFoundException e) { try { forceLogout(); System.out.println(Message.getDateTime() + " " + id + " 系统强行登出 " + e.toString()); } catch (IOException ex) { ex.printStackTrace(); } } } } private void serve() throws IOException, ClassNotFoundException { Message inp = (Message) ois.readObject(); Message toSend = new Message(null, ClientConnect.thisIP, clientSocket.getInetAddress().getHostAddress(), null); if (inp == null) { return; } else if (inp.getMessageType().equals(MessageType.LOGGED_SHOW_LIST)) { sendList(inp, toSend); } else if (inp.getMessageType().equals(MessageType.LOGGED_CHANGE_NAME)) { setNewName(inp, toSend); } else if (inp.getMessageType().equals(MessageType.LOGGED_CHANGE_HIDE)) { changeHide(inp, toSend); } else if (inp.getMessageType().equals(MessageType.LOGGED_CHANGE_PW)) { changPw(inp, toSend); } else if (inp.getMessageType().equals(MessageType.LOGGED_SHOW_ONLINE_LIST)) { sendOnlineList(inp, toSend); } else if (inp.getMessageType().equals(MessageType.CHAT)) { chat(inp, toSend); } else if (inp.getMessageType().equals(MessageType.GROUP_CHAT)) { groupChat(inp, toSend); } else if (inp.getMessageType().equals(MessageType.LOGGED_LOGOUT)) { logout(inp); } else if (inp.getMessageType().equals(MessageType.FILE)) { sendFile(inp, toSend); } } private void sendList(Message inp, Message toSend) throws IOException { StringBuilder sb = new StringBuilder(); for (UserData userData : ClientConnect.data.values()) { sb.append(userData.listed()); sb.append("\n"); } toSend.setMessage(sb.toString()); toSend.setMessageType(MessageType.REQUEST_ALLOWED); oos.writeObject(toSend); } private void sendOnlineList(Message inp, Message toSend) throws IOException { StringBuilder sb = new StringBuilder(); for (UserData userData : ClientConnect.data.values()) { if (userData.isOnline()) { sb.append(userData.listed()); sb.append("\n"); } } toSend.setMessage(sb.toString()); toSend.setMessageType(MessageType.REQUEST_ALLOWED); oos.writeObject(toSend); } private void setNewName(Message inp, Message toSend) throws IOException { String newName = inp.getMessage(); UserData temp = (UserData) inp.getObject(); if (!temp.getId().equals(id) || !temp.equals(ClientConnect.data.get(id))) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else { temp.setName(newName); ClientConnect.data.put(id, temp); toSend.setObject(ClientConnect.data.get(temp.getId())); toSend.setMessageType(MessageType.REQUEST_ALLOWED); } oos.writeObject(toSend); } private void changPw(Message inp, Message toSend) throws IOException { String newPw = inp.getMessage(); UserData temp = (UserData) inp.getObject(); if (!temp.getId().equals(id) || !temp.equals(ClientConnect.data.get(id))) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else { temp.setPw(newPw); ClientConnect.data.put(id, temp); toSend.setObject(ClientConnect.data.get(id)); toSend.setMessageType(MessageType.REQUEST_ALLOWED); } oos.writeObject(toSend); } private void changeHide(Message inp, Message toSend) throws IOException { UserData temp = (UserData) inp.getObject(); if (!temp.getId().equals(id) || !temp.equals(ClientConnect.data.get(id))) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else { temp.setOnline(!temp.isOnline()); ClientConnect.data.put(id, temp); toSend.setObject(ClientConnect.data.get(temp.getId())); toSend.setMessageType(MessageType.REQUEST_ALLOWED); } oos.writeObject(toSend); } private void chat(Message inp, Message toSend) throws IOException { User[] users = (User[]) inp.getObject(); if (!users[0].getId().equals(id) || !users[0].getPw().equals(ClientConnect.data.get(id).getPw())) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else if (users[1].getId().equals(id) || ClientConnect.data.get(users[1].getId()) == null) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else { UserData tar = ClientConnect.data.get(users[1].getId()); UserData form = ClientConnect.data.get(id); StringBuilder send = new StringBuilder(); toSend.setMessageType(MessageType.CHAT); send.append(inp.getTime()).append(" ").append(form.getName()).append(" 对 你 说:").append(inp.getMessage()); if (ServeCollection.get(tar.getId()) != null) { toSend.setMessage(send.toString()); ServeCollection.get(tar.getId()).oos.writeObject(toSend); } else { ClientConnect.data.get(tar.getId()).leftMessage.append(send).append("\n"); } toSend.setObject(new User(tar.getName(), null)); toSend.setMessageType(MessageType.REQUEST_ALLOWED); } oos.writeObject(toSend); } private void groupChat(Message inp, Message toSend) throws IOException { User user = (User) inp.getObject(); if (!user.getId().equals(id) || !user.getPw().equals(ClientConnect.data.get(id).getPw())) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else { toSend.setMessageType(MessageType.GROUP_CHAT); String word = "[群发]" + inp.getTime() + " " + ClientConnect.data.get(id).getName() + " 说:" + inp.getMessage(); toSend.setMessage(word); for (Serve serve : ServeCollection.toValue()) { serve.oos.writeObject(toSend); } toSend.setMessageType(MessageType.REQUEST_ALLOWED); } oos.writeObject(toSend); } private void sendFile(Message inp, Message toSend) throws IOException { User user = new User(id, inp.getSender()); if (!user.getPw().equals(ClientConnect.data.get(id).getPw())) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else if (inp.getReceiver().equals(id) || ClientConnect.data.get(inp.getReceiver()) == null) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } else { toSend.setMessageType(MessageType.FILE); toSend.setMessage(inp.getMessage()); toSend.setSender(id); toSend.setReceiver(inp.getReceiver()); toSend.setObject(inp.getObject()); try { ServeCollection.get(inp.getReceiver()).oos.writeObject(toSend); toSend.setMessageType(MessageType.REQUEST_ALLOWED); } catch (IOException e) { toSend.setMessageType(MessageType.REQUEST_REJECTED); } toSend.setObject(null); } oos.writeObject(toSend); } private void logout(Message inp) throws IOException { UserData temp = (UserData) inp.getObject(); if (!temp.getId().equals(id) || !temp.equals(ClientConnect.data.get(id))) { return; } else { ClientConnect.data.get(id).setOnline(false); ois.close(); oos.close(); clientSocket.close(); connecting = false; ServeCollection.remove(id); System.out.println(Message.getDateTime() + " " + id + " 用户登出 " + clientSocket.getInetAddress().getHostName()); } } public void forceLogout() throws IOException { ClientConnect.data.get(id).setOnline(false); ois.close(); oos.close(); clientSocket.close(); connecting = false; ServeCollection.remove(id); } public void sendRepeatLoginException() throws IOException { oos.writeObject(new Message(null, null, null, MessageType.REPEAT_LOGIN)); } }
-
ServeCollection.java
package com.melody.transmission.common; import com.melody.transmission.connected.Serve; import java.net.Socket; import java.util.Collection; import java.util.HashMap; /** * @author Melody * @version 1.0 */ public class ServeCollection { public static final HashMap<String, Serve> serves = new HashMap<>(); public static void add(String id, Serve serve){ serves.put(id, serve); } public static Serve get(String id) { return serves.get(id); } public static Collection<Serve> toValue(){ return serves.values(); } public static void remove(String id){ serves.remove(id); } }
-
UserDataEnum.java
package com.melody.transmission.common; /** * @author Melody * @version 1.0 */ public enum UserDataEnum { USER01("申鹤", "123456", "0001"), USER02("胡桃", "123456", "0002"), USER03("甘雨", "123456", "0003"), USER04("刻晴", "123456", "0004"), USER05("香菱", "123456", "0005"), USER06("凝光", "123456", "0006"), USER07("云堇", "123456", "0007"), USER08("留云借风真君", "123456", "0008"), USER09("降魔大圣", "123456", "0009"), USER10("歌尘浪市真君", "123456", "0010"), USER11("理水叠山真君", "123456", "0011"), USER12("摩拉克斯", "123456", "0012"), USER13("救苦渡厄真君", "123456", "0013"); public final UserData user; UserDataEnum(String name, String pw, String ID) { user = new UserData(name, pw, ID); } }
客户端
Test1.java:程序入口
ClientInterface.java:程序的主干
ClientThread.java:由这个线程收集服务端发来的数据,并记录
ClientThreadCollection.java:思路混乱的产物。现在我也不知道这是干嘛的(好在没几行代码)
Utility.java:工具类
-
Test1.java(入口)
package com.melody.transmission; import com.melody.transmission.common.ClientInterface; /** * @author Melody * @version 1.0 */ public class Test1 { public static void main(String[] args) { new ClientInterface().menu_0(); } }
-
ClientInterface.java(主干)
package com.melody.transmission.common; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.InetAddress; import java.net.Socket; import java.time.LocalDateTime; import java.util.Date; import java.util.Scanner; /** * @author Melody * @version 1.0 */ public class ClientInterface { private Scanner scanner = new Scanner(System.in); private String thisIP = "192.168.3.16"; private static String serverIP = "192.168.3.16"; private UserData loggedUser = null; private User user = null; private ClientThread ct; private Socket socket; private Message message; ObjectOutputStream oos; ObjectInputStream ois; public boolean running = true; public boolean cancelMenu = false; public File filePath = new File("d:\\"); //一级菜单、登录 public void menu_0() { boolean running0 = true; while (running0) { System.out.println("===========欢迎使用通讯系统(试做版)==========="); System.out.println("\t\t\t1. 用户登录"); System.out.println("\t\t\t9. 退出系统"); System.out.print("请输入:"); String inpString = Utility.readKeyBoard(1, false); System.out.println("--------------------------------------------"); try { if (inpString.equals("1")) { menuLogin_0_1(); } else if (inpString.equals("9")) { running0 = false; } else { throw new RuntimeException(); } } catch (RuntimeException | ClassNotFoundException e) { System.out.println("错误"); } catch (IOException d) { System.out.println("链接失败!"); } finally { System.out.println("--------------------------------------------"); } } System.out.println("再见~"); } //一级菜单、登录 private void menuLogin_0_1() throws IOException, ClassNotFoundException { System.out.println("-----------------用户登录-------------------"); System.out.print("请输入用户ID(4位):"); String id = Utility.readKeyBoard(4, false); System.out.print("请输入密码(6位):"); String pw = Utility.readKeyBoard(6, false); System.out.println(); System.out.println("--------------------------------------------"); user = new User(id, pw); loggedUser = tryLogin_0_1_1(user); if (loggedUser == null) { System.out.println("登录失败"); oos.close(); ois.close(); socket.close(); } else { System.out.println("欢迎回来," + loggedUser.getName()); isLoggedIn_1(); } } private UserData tryLogin_0_1_1(User user) throws IOException, ClassNotFoundException { socket = new Socket(InetAddress.getByName(serverIP), 9000); oos = new ObjectOutputStream(socket.getOutputStream()); ois = new ObjectInputStream(socket.getInputStream()); oos.writeObject(user); Message received = (Message) ois.readObject(); String reply = received.getMessageType(); if (reply.equals(MessageType.LOGIN_WRONG_PW)) { System.out.println("密码错误"); } else if (reply.equals(MessageType.LOGIN_USER_NOT_EXIST)) { System.out.println("用户名不存在"); } else if (reply.equals(MessageType.LOGIN_SUCCESS)) { return (UserData) received.getObject(); } else if (reply.equals(MessageType.LOGIN_FAILED)) { System.out.println("错误"); } return null; } //二级菜单 private void isLoggedIn_1() { ct = new ClientThread(socket, oos, ois, this); new Thread(ct).start(); running = true; showBasicMenu_1_(); System.out.println(loggedUser.leftMessage); cancelMenu = true; if (loggedUser.leftMessage.length() != 0) { System.out.println("----------------以上是留言----------------"); } while (running) { showBasicMenu_1_(); System.out.print("请选择:"); char inp = Utility.readChar(); System.out.println("--------------------------------------------"); switch (inp) { case '1': loggedGetList_1_1(); break; case '2': loggedChat_1_2(); break; case '3': loggedGroupChat_1_3(); break; case '4': loggedSendFile_1_4(); break; case '5': loggedChangeName_1_5(); break; case '6': loggedChangePw_1_6(); break; case '7': loggedChangeHide_1_7(); break; case '8': showOthersMenu_1_(); break; case '9': try { loggedLogout_1_9(); } catch (IOException e) { System.out.println("复位"); } break; case 'M': case 'm': break; case 'L': case 'l': loggedShowLeftMessage_1_L(); break; case 'H': case 'h': loggedShowChatMessage_1_H(); break; case 'C': case 'c': loggedClearMessage_1_C(); break; case 'D': case 'd': loggedFileDeal_1_D(); break; case 'F': case 'f': loggedShowFilePath_1_F(); break; case 'N': case 'n': loggedChangeFilePath_1_N(); break; default: System.out.println("错误:请检查输入内容"); break; } } } public void showBasicMenu_1_() { if (cancelMenu) { cancelMenu = false; return; } System.out.println("================" + loggedUser.getName() + "(" + loggedUser.getID() + ")================"); System.out.println("\t\t\t1. 获取用户列表"); System.out.println("\t\t\t2. 聊天"); System.out.println("\t\t\t3. 群聊"); System.out.println("\t\t\t4. 发送文件"); System.out.println("\t\t\t5. 修改昵称"); System.out.println("\t\t\t6. 修改密码"); System.out.println("\t\t\t7. 切换隐身状态(目前:" + (loggedUser.isOnline() ? "非隐身)" : "隐身★~)")); System.out.println("\t\t\t8. 其他"); System.out.println("\t\t\t9. 退出"); System.out.println("--------------------------------------------"); } private void showOthersMenu_1_() { System.out.println("================" + loggedUser.getName() + "(" + loggedUser.getID() + ")================"); System.out.println("\t\t\tC. 清空在线消息"); System.out.println("\t\t\tD. 查看文件"); System.out.println("\t\t\tH. 在线消息记录"); System.out.println("\t\t\tL. 查看离线留言"); System.out.println("\t\t\tF. 查看文件存放目录"); System.out.println("\t\t\tN. 更改文件存放目录"); System.out.println("\t\t\tM. 返回(显示一级菜单)"); cancelMenu = true; } private void loggedGetList_1_1() { message = new Message(null, loggedUser.getID(), serverIP, MessageType.LOGGED_SHOW_LIST); sendAndReceive(); if (message == null) { System.out.println("获取列表失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { ct.messageReceived = false; System.out.println("----------------成员列表如下----------------"); System.out.println(message.getMessage()); } else { System.out.println("获取列表失败"); } } private void loggedGetActiveList_1_1A() { message = new Message(null, loggedUser.getID(), serverIP, MessageType.LOGGED_SHOW_ONLINE_LIST); sendAndReceive(); if (message == null) { System.out.println("获取在线成员列表失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { ct.messageReceived = false; System.out.println("--------------在线成员列表如下--------------"); System.out.println(message.getMessage()); } else { System.out.println("获取在线成员列表失败"); } } private void loggedChat_1_2() { loggedGetActiveList_1_1A(); message = new Message(null, loggedUser.getID(), serverIP, MessageType.CHAT); System.out.print("请输入私聊对象:"); String tarId = Utility.readKeyBoard(10, true); if (tarId == null || tarId.equals("")) { System.out.println("取消"); return; } else if (tarId.equals(loggedUser.getID())) { System.out.println("不能私聊自己"); return; } System.out.print("发送内容:"); String word = Utility.readKeyBoard(100, true); if (word == null || word.equals("")) { System.out.println("取消"); return; } User[] temp = new User[2]; temp[0] = user; temp[1] = new User(tarId, null); message.setObject(temp); message.setMessage(word); showBasicMenu_1_(); sendAndReceive(); if (message == null) { System.out.println("\r访问失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { ct.sb.append("\n").append(message.getTime()).append(" 你对 ").append(((User) message.getObject()).getId()).append(" 说:").append(word); System.out.println(ct.sb); System.out.println("\r发送成功"); cancelMenu = true; } else if (message.getMessageType().equals(MessageType.REQUEST_REJECTED)) { System.out.println("\r请求被拒绝"); } else { System.out.println("\r发送失败"); } } private void loggedGroupChat_1_3() { message = new Message(null, loggedUser.getID(), serverIP, MessageType.GROUP_CHAT); System.out.print("发送内容:"); String word = Utility.readKeyBoard(100, true); if (word == null || word.equals("")) { System.out.println("取消"); return; } message.setObject(user); message.setMessage(word); showBasicMenu_1_(); sendAndReceive(); if (message == null) { System.out.println("\r访问失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { System.out.println("\r发送成功"); cancelMenu = true; } else if (message.getMessageType().equals(MessageType.REQUEST_REJECTED)) { System.out.println("\r请求被拒绝"); } else { System.out.println("\r发送失败"); } } private void loggedSendFile_1_4() { loggedGetActiveList_1_1A(); message = new Message(null, loggedUser.getID(), serverIP, MessageType.FILE); System.out.println("请输入要发送的用户:"); String tar = Utility.readKeyBoard(4, true); message.setReceiver(tar); if (tar == null || tar.equals("")) { System.out.println("取消"); return; } else if (tar.equals(loggedUser.getID())) { System.out.println("错误:不能发给自己"); return; } else if (tar.length() < 4) { System.out.println("错误:用户 ID 是 4 位字符"); } System.out.print("请输入文件完整路径:"); String file_Str = Utility.readKeyBoard(500, true); if (file_Str == null || file_Str.equals("")) { System.out.println("取消"); return; } cancelMenu = true; File file = null; byte[] data = null; try { file = new File(file_Str); if (!file.isFile()) { throw new RuntimeException(); } data = Utility.loadFile(file); } catch (Exception e) { showBasicMenu_1_(); System.out.println("错误:文件路径错误"); return; } message.setSender(loggedUser.getPw()); message.setObject(data); message.setMessage(file.getName()); sendAndReceive(); if (message == null) { System.out.println("发送失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { ct.messageReceived = false; System.out.println("文件发送成功"); } else if (message.getMessageType().equals(MessageType.REQUEST_REJECTED)) { System.out.println("请求被拒绝"); } else { System.out.println("请求失败"); } cancelMenu = false; } private void loggedChangeName_1_5() { message = new Message(null, loggedUser.getID(), serverIP, MessageType.LOGGED_CHANGE_NAME); System.out.print("请输入一个新名字(不超过10位):"); String newName = Utility.readKeyBoard(10, true); if (newName == null || newName.equals("")) { System.out.println("取消"); return; } message.setMessage(newName); message.setObject(loggedUser); loggedUser = null; sendAndReceive(); if (message == null) { System.out.println("访问失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { ct.messageReceived = false; loggedUser = (UserData) message.getObject(); System.out.println("修改完毕:(" + loggedUser.getID() + ")" + loggedUser.getName()); } else if (message.getMessageType().equals(MessageType.REQUEST_REJECTED)) { System.out.println("请求被拒绝"); } else { System.out.println("请求失败"); } } private void loggedChangePw_1_6() { message = new Message(null, loggedUser.getID(), serverIP, MessageType.LOGGED_CHANGE_PW); System.out.print("请输入原密码:"); String pw = Utility.readKeyBoard(20, true); if (pw == null || pw.equals("")) { System.out.println("取消"); return; } else if (!pw.equals(loggedUser.getPw())) { System.out.println("错误:密码错误"); return; } System.out.print("请输入新密码(6位):"); pw = Utility.readKeyBoard(6, true); if (pw == null || pw.equals("")) { System.out.println("取消"); return; } else if (pw.length() != 6) { System.out.println("错误:密码长度应该是 6 位"); return; } message.setMessage(pw); message.setObject(loggedUser); sendAndReceive(); if (message == null) { System.out.println("访问失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { ct.messageReceived = false; System.out.println("密码修改成功"); loggedUser = (UserData) message.getObject(); } else if (message.getMessageType().equals(MessageType.REQUEST_REJECTED)) { System.out.println("请求被拒绝"); } else { System.out.println("请求失败"); } } private void loggedChangeHide_1_7() { message = new Message(null, loggedUser.getID(), serverIP, MessageType.LOGGED_CHANGE_HIDE); message.setObject(loggedUser); // loggedUser = null; sendAndReceive(); if (message == null) { System.out.println("访问失败"); } else if (message.getMessageType().equals(MessageType.REQUEST_ALLOWED)) { loggedUser = (UserData) message.getObject(); } else if (message.getMessageType().equals(MessageType.REQUEST_REJECTED)) { System.out.println("请求被拒绝"); } else { System.out.println("请求失败"); } } private void loggedLogout_1_9() throws IOException { System.out.println("确认退出吗?(Y/N)"); if (Utility.readConfirmSelection() == 'Y') { message = new Message(null, loggedUser.getID(), serverIP, MessageType.LOGGED_LOGOUT); message.setObject(loggedUser); oos.writeObject(message); loggedUser = null; socket.close(); ct.running = false; running = false; } } private void loggedShowChatMessage_1_H() { System.out.println(ct.sb); cancelMenu = true; System.out.println("----------------没有更多了----------------"); } private void loggedShowLeftMessage_1_L() { System.out.println(loggedUser.leftMessage); cancelMenu = true; System.out.println("----------------没有更多了----------------"); } private void loggedClearMessage_1_C() { ct.sb.delete(0, ct.sb.length()); System.out.println("完成"); } private void loggedFileDeal_1_D() { boolean fileMenuRunning = true; while (fileMenuRunning) { showFileDealMenu_1_D_(); System.out.print("请选择:"); char inp = Utility.readChar(); System.out.println("--------------------------------------------"); switch (inp) { case '1': fileShowAllName_1_D_1(); break; case '2': fileShowNewestName_1_D_2(); break; case '3': fileDeleteNewestOne_1_D_3(); break; case '4': fileDownloadNewestOne_1_D_4(); break; case '5': fileDownloadAll_1_D_5(); break; case '6': loggedChangeFilePath_1_N(); break; case '9': fileMenuRunning = false; break; default: System.out.println("错误:请检查输入内容"); break; } } } private void showFileDealMenu_1_D_() { if (cancelMenu) { cancelMenu = false; return; } System.out.println("---------------文件管理----------------"); ct.showFilesRemain(); loggedShowFilePath_1_F(); System.out.println("-------------------------------------"); System.out.println("\t\t\t1. 查看所有文件"); System.out.println("\t\t\t2. 查看最新文件"); System.out.println("\t\t\t3. 删除最新的文件"); System.out.println("\t\t\t4. 下载最新的文件"); System.out.println("\t\t\t5. 全部下载"); System.out.println("\t\t\t6. 修改存放路径"); System.out.println("\t\t\t9. 返回上级目录"); } private void fileShowAllName_1_D_1() { showFileDealMenu_1_D_(); cancelMenu = true; if (ct.filesToDeal.length == 0) { System.out.println("没有未处理的文件了★~~"); return; } int n = 0; do { System.out.println((n + 1) + ". " + ct.fileName[n]); } while (++n < ct.fileName.length); } private void fileShowNewestName_1_D_2() { showFileDealMenu_1_D_(); cancelMenu = true; if (ct.filesToDeal.length == 0) { System.out.println("没有未处理的文件了★~~"); } else { System.out.println(ct.fileName.length + ". " + ct.fileName[ct.fileName.length - 1]); } } private void fileDeleteNewestOne_1_D_3() { showFileDealMenu_1_D_(); cancelMenu = true; if (ct.filesToDeal.length == 0) { System.out.println("没有未处理的文件了★~~"); return; } System.out.println(ct.fileName.length + ". " + ct.fileName[ct.fileName.length - 1]); System.out.println("确定删除吗?"); System.out.println("确认退出吗?(Y/N)"); if (Utility.readConfirmSelection() == 'N') { System.out.println("取消"); return; } if (ct.deleteNewestFile()) { System.out.println("删除成功"); } } private void fileDownloadNewestOne_1_D_4() { showFileDealMenu_1_D_(); cancelMenu = true; if (ct.filesToDeal.length == 0) { System.out.println("没有未处理的文件了★~~"); return; } String str = ct.fileName[ct.fileName.length - 1]; if (ct.downloadNewestFile()) { System.out.println(str + " 下载成功"); } } private void fileDownloadAll_1_D_5() { showFileDealMenu_1_D_(); cancelMenu = true; if (ct.filesToDeal.length == 0) { System.out.println("没有未处理的文件了★~~"); return; } do { fileDownloadNewestOne_1_D_4(); } while (ct.filesToDeal.length > 0); cancelMenu = false; } private void loggedShowFilePath_1_F() { System.out.println("文件保存目录如下:" + filePath); cancelMenu = true; } private void loggedChangeFilePath_1_N() { loggedShowFilePath_1_F(); System.out.print("请输入一个新的路径"); String newPath = Utility.readKeyBoard(500, true); if (newPath == null || newPath.equals("")) { System.out.println("取消"); cancelMenu = false; return; } File path; try { path = new File(newPath); path.mkdir(); } catch (Exception e) { System.out.println("错误:路径输入错误"); return; } filePath = path; cancelMenu = false; showBasicMenu_1_(); System.out.println("成功"); loggedShowFilePath_1_F(); } private void sendAndReceive() { long now = (new Date()).getTime(); ct.messageReceived = false; try { oos.writeObject(message); } catch (IOException e) { System.out.println("异常"); return; } do { if (new Date().getTime() - now >= 5000) { System.out.println("超时"); message = null; break; } else if (ct.messageReceived) { message = ct.message; break; } } while (true); ct.messageReceived = false; } private static String getDate() { return getDate(LocalDateTime.now()); } private static String getDate(LocalDateTime ldt) { return ldt.getYear() + "." + ldt.getMonthValue() + "." + ldt.getDayOfMonth(); } private static String getTime() { return getTime(LocalDateTime.now()); } private static String getTime(LocalDateTime ldt) { return ldt.getHour() + ":" + ldt.getMinute() + ":" + ldt.getSecond(); } private static String getDateTime() { LocalDateTime ldt = LocalDateTime.now(); return getDate(ldt) + " " + getTime(); } // private Message packageMessage(String str) { // return new Message(str, thisIP, serverIP, "0"); // } }
-
ClientThread.java
package com.melody.transmission.common; import java.io.File; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import java.util.Arrays; /** * @author Melody * @version 1.0 */ public class ClientThread implements Runnable { private Socket socket; private ObjectOutputStream oos; private ObjectInputStream ois; public Message message = null; public boolean messageReceived = false; public boolean running = true; public final StringBuilder sb = new StringBuilder(); private ClientInterface ci; // public byte[] fileToDeal; public boolean receivedFileDealt = true; public byte[][] filesToDeal = new byte[0][]; //这里改为创建一个本地缓存,在内存里存放路径更好 //但我懒得改了 public String[] fileName = new String[0]; @Override public void run() { try { while (running) { message = (Message) ois.readObject(); if (message.getMessageType().equals(MessageType.CHAT) || message.getMessageType().equals(MessageType.GROUP_CHAT)) { chatReceived(); } else if (message.getMessageType().equals(MessageType.FILE)) { filesReceived(); } messageReceived = true; } } catch (IOException | ClassNotFoundException e) { ci.running = false; System.out.print("\r已离线:"); } } public ClientThread(Socket socket, ObjectOutputStream oos, ObjectInputStream ois, ClientInterface ci) { this.socket = socket; this.oos = oos; this.ois = ois; this.ci = ci; } public Socket getSocket() { return socket; } private void chatReceived() { sb.append("\n"); sb.append(message.getMessage()); System.out.println("\r"); ci.showBasicMenu_1_(); System.out.print("\r "); System.out.print("\r" + sb); System.out.print("\n请选择:"); message.setMessageType(MessageType.REQUEST_ALLOWED); } private void filesReceived() { filesToDeal = Arrays.copyOf(filesToDeal, filesToDeal.length + 1); filesToDeal[filesToDeal.length - 1] = (byte[]) message.getObject(); fileName = Arrays.copyOf(fileName, fileName.length + 1); fileName[fileName.length - 1] = message.getMessage(); receivedFileDealt = false; showFilesRemain(); } public void showFilesRemain() { System.out.println("\r★您有 " + filesToDeal.length + " 个文件等待处理★"); // System.out.println("\r★按 D 键处理★"); if (filesToDeal.length == 0){ receivedFileDealt = true; } } public boolean deleteNewestFile() { try { filesToDeal = Arrays.copyOf(filesToDeal, filesToDeal.length - 1); fileName = Arrays.copyOf(fileName, fileName.length - 1); if (fileName.length != filesToDeal.length){ fileName = Arrays.copyOf(fileName, filesToDeal.length - 1); System.out.println("错误:文件或文件名丢失"); throw new RuntimeException(); } } catch (Exception e) { return false; } finally { showFilesRemain(); } return true; } public boolean downloadNewestFile() { File savePath = new File(ci.filePath, fileName[fileName.length - 1]); if (Utility.saveFile(filesToDeal[filesToDeal.length -1], savePath)){ return deleteNewestFile(); } else { System.out.println("错误:文件存储失败"); return false; } } }
-
ClientThreadCollection.java
package com.melody.transmission.common; import java.util.HashMap; /** * @author Melody * @version 1.0 */ public class ClientThreadCollection { public static final HashMap<String, ClientThread> threads = new HashMap<>(); public static void addSocket(String ip, ClientThread ct) { threads.put(ip, ct); } public static ClientThread get(String ip) { return threads.get(ip); } }
-
Utility.java(工具类,不是我写的)
package com.melody.transmission.common; /** * @author 韩顺平 * 工具类的作用: * 处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。 */ import javax.imageio.IIOException; import java.io.*; import java.util.Scanner; /** * */ 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') { 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("请重新输入(输入 Y/N):"); } } return c; } /** * 功能: 读取一个字符串 * * @param limit 读取的长度 * @param blankReturn 如果为true ,表示 可以读空字符串。 * 如果为false表示 不能读空字符串。 * <p> * 如果输入为空,或者输入大于limit的长度,就会提示重新输入。 * @return */ public 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; } public static byte[] receiveFile(InputStream is) throws IOException { ByteArrayOutputStream bis = new ByteArrayOutputStream(); int len = 0; byte[] bytes = new byte[1024]; while ((len = is.read(bytes, 0, bytes.length)) != -1) { bis.write(bytes, 0, len); } return bis.toByteArray(); } public static byte[] loadFile(File path) throws IOException { ByteArrayOutputStream bis = new ByteArrayOutputStream(); FileInputStream is = new FileInputStream(path); int len = 0; byte[] bytes = new byte[1024]; while ((len = is.read(bytes, 0, bytes.length)) != -1) { bis.write(bytes, 0, len); } bytes = bis.toByteArray(); bis.close(); return bytes; } public static boolean saveFile(byte[] data, File path) { int n = 1; File temp = path; while (temp.isFile()) { temp = new File(path.getParent(), "(拷贝" + n++ + ")" + path.getName()); } try { FileOutputStream is = new FileOutputStream(temp); is.write(data); is.close(); } catch (IOException e) { return false; } return true; } }