1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > 多用户通讯系统(网络编程 多线程 IO流 面向对象)

多用户通讯系统(网络编程 多线程 IO流 面向对象)

时间:2023-08-09 09:15:10

相关推荐

多用户通讯系统(网络编程 多线程 IO流 面向对象)

多用户通讯系统

项目介绍工具类共同属性MessageType 接口Message类User类客服端登录界面类客服端子线程类管理子线程类用户服务聊天类(私聊/群聊)发送文件类服务端服务端主类服务端子线程类管理子线程类(服务端)发送新闻类总结第一次bug第二次bug

项目介绍

工具类

方便用于输入输出的限制操作

package utilss;/**工具类的作用:处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。*/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=>NString 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;}}

这个项目主要实现多个客户端和服务端进行各种交互的网络通讯系统。

主要实现的功能有以下一个:

显示在线用户列表私聊消息(在线/离线)群发消息(在线/离线)发送文件服务器发布公告

因为是要巩固之前学习的网络编程和多线程,IO等技术,就没有考虑界面,只是简单地实现了内核程序,表现形式主要以控制台为主。

共同属性

因为客服端和服务端要共同拥有一些属性才能实现对接,所以要在客服端和服务端创建相同的属性。

单设置一个common包 里面存共同属性类

MessageType 接口

这个接口存放一些常量,表示一些状态信息,方便使用。

package common;/*** 表示消息的类型*/public interface MessageType {String MESSAGE_LOGIN_SUCCEED = "1"; //表示登录成功String MESSAGE_LOGIN_FAIL = "2"; // 表示登录失败String MESSAGE_COMM_MES = "3"; //普通信息String MESSAGE_GET_ONLINE_FRIEND = "4"; //要求返回在线用户列表String MESSAGE_RET_ONLINE_FRIEND = "5"; //返回在线用户列表String MESSAGE_CLIENT_EXIT = "6"; //客户端请求退出String MESSAGE_TO_ALL_MES = "7"; //群发消息String MESSAGE_FILE_MES = "8"; //文件消息(发送文件)}

Message类

因为在客户端和服务端之间进行通讯过程中要传递消息,这里我们把消息封装成一个Message类,因为要进行IO流的信息传送,所以这个类必须被序列化(在客户端和服务端这类一定要保持一模一样)。

package common;import java.io.Serializable;//序列化public class Message implements Serializable {private static final long serialVersionUID = 1419003708355194092L;private String sender;//发送者private String getter;//接收者private String content;//内容private String sendTime;//发生时间private String mesType; //消息类型//文件相关扩展private byte[] fileBytes;private int fileLen = 0;private String desc;private String src;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 getDesc() {return desc; }public void setDesc(String desc) {this.desc = desc; }public String getSrc() {return src; }public void setSrc(String src) {this.src = src; }public String getMesType() {return mesType; }public void setMesType(String mesType) {this.mesType = mesType; }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; }}

User类

这个类用于存放每个用户存放的个人信息(在客户端和服务端这类一定要保持一模一样)

serialVersionUID适用于Java的序列化机制。简单来说,Java的序列化机制是通过判断类serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常,即是InvalidCastException。

IDEA可以快捷生成独一无二的序列编号。

具体的序列化过程是这样的:序列化操作的时候系统会把当前类的serialVersionUID写入到序列化文件中,当反序列化时系统会去检测文件中的serialVersionUID,判断它是否与当前类的serialVersionUID一致,如果一致就说明序列化类的版本与当前类版本是一样的,可以反序列化成功,否则失败

借鉴

package common;import java.io.Serializable;public class User implements Serializable {//serialVersionUID 序列化编号private static final long serialVersionUID = 6889194987676953061L;private String userID;private String passwd;public User(){};public User(String userID, String passwd) {this.userID = userID;this.passwd = passwd;}public String getUserID() {return userID; }public void setUserID(String userID) {this.userID = userID; }public String getPasswd() {return passwd; }public void setPasswd(String passwd) {this.passwd = passwd; }}

客服端

登录界面类

这个没啥好介绍的,就是控制台界面,和一些调用方法。

package view;import server.ChatClientServer;import server.FileClientServer;import server.UserClientServer;import utils.Utility;/*** 登录界面*/public class QQView {private boolean loop = true; //控制是否显示菜单private String key = ""; //接收用户的键盘输入private UserClientServer ucs = new UserClientServer();//是用于登录服务器,注册用法private ChatClientServer ccs = new ChatClientServer();//聊天private FileClientServer fcs = new FileClientServer();//发文件public static void main(String[] args) {new QQView().mainMenu();System.out.println("客户端退出系统......");}//显示主菜单private void mainMenu() {while (loop) {System.out.println("============欢迎登录我们网络通讯系统============");System.out.println("\t\t\t\t 1 登录系统");System.out.println("\t\t\t\t 9 退出系统");System.out.print("请输入你的选择: ");key = Utility.readString(1);switch (key) {case "1":System.out.print("请输入用户号: ");String userId = Utility.readString(50);System.out.print("请输入密 码: ");String pwd = Utility.readString(50);if (ucs.checkUser(userId,pwd)) {//进入二级菜单System.out.println("============欢迎用户 "+userId+" 登陆成功============");while (loop){System.out.println("=============网路通讯系统二级菜单=============");System.out.println("\t\t\t 1 显示在线用户列表");System.out.println("\t\t\t 2 群发消息");System.out.println("\t\t\t 3 私聊消息");System.out.println("\t\t\t 4 发送文件");System.out.println("\t\t\t 9 退出系统");System.out.print("请输入你的选择: ");key = Utility.readString(1);String content = "";switch (key){case "1":ucs.onLineUserList();break;case "2":System.out.print("请输入想对大家说的话:");content = Utility.readString(100);ccs.sendMessageToAll(userId,content);break;case "3":System.out.print("请输入接收方的ID(在线):");String getterId = Utility.readString(10);System.out.print("请输入您要发送的内容:");content = Utility.readString(100);ccs.sendMessageToOne(userId,getterId,content);break;case "4":System.out.print("请输入你要发送的对象:");String getterId1 = Utility.readString(10);System.out.print("请输入您要发送的文件路径:");String src = Utility.readString(30);System.out.print("请输入你要发送到对方什么位置:");String desc = Utility.readString(30);fcs.sendFileToOne(userId,getterId1,src,desc);break;case "9":loop = false;ucs.logout();break;}}}else {System.out.println("登陆失败......");}break;case "9":loop = false;break;}}}}

客服端子线程类

因为每个用户都是独立存在的,而客服端只有一个,所以要在主线程里面设置一个子线程用来控制对应的一个用户,在线程里面时刻进行接收服务端传来的message报。

package server;import common.Message;import common.MessageType;import java.io.FileOutputStream;import java.io.ObjectInputStream;import .Socket;public class ClientConnectServerThread extends Thread {//该线程需要持有socketprivate Socket socket;public ClientConnectServerThread(Socket socket) {this.socket = socket;}public Socket getSocket() {return socket;}@Overridepublic void run() {//因为Thread需要在后台和服务端通讯while (true) {try {System.out.println("客户端等待服务端传过来消息");ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Message message = (Message) ois.readObject(); //如果服务器没有发送message对象,线程就会阻塞在这里//判断这个Message的类型if (message.getMesType().equals(MessageType.MESSAGE_RET_ONLINE_FRIEND)) {//返回在线列表信息String[] onLineUsers = message.getContent().split(" ");System.out.println("\n===========当前现在用户列表==========");for (int i = 0; i < onLineUsers.length; i++) {System.out.println("用户:" + onLineUsers[i]);}} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//私聊System.out.println("\n" + message.getSender()+ " 对 " + message.getGetter() + " 说:" + message.getContent());} else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {//群聊System.out.println("\n" + message.getSender()+ " 对大家说:" + message.getContent());} else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)) {//发文件byte[] fileBytes = message.getFileBytes();String desc = message.getDesc();FileOutputStream fos = new FileOutputStream(desc);fos.write(fileBytes);fos.close();System.out.println("成功接收" + message.getSender() + "的文件");}} catch (Exception e) {e.printStackTrace();}}}}

管理子线程类

本项目只是在一个客户端实现了一个线程,以后若是需要在一个客户端里实现多个子线程(应用),因此需要用一个集合来统一管理多个子线程。对于本项目这个类是没有作用。

package server;import java.util.HashMap;/*** 管理客户端连接到服务端线程的一个类*/public class ManageClientConnectServerThread {//多个线程放到HashMap中 key 用户ID value 连接服务端线程private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();//将某个线程加入到集合中public static void addClientConnectServerThread(String id,ClientConnectServerThread thread){hm.put(id,thread);}//通过userID可以得到对应的线程public static ClientConnectServerThread getClientConnectServerThread(String id){return hm.get(id);}}

用户服务

package server;import common.Message;import common.MessageType;import common.User;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import .InetAddress;import .Socket;/*** 用户登录验证和用户注册等功能*/public class UserClientServer {//因为在其他地方要使用到user信息,所以做成成员private User user = new User();//socket也要在其他地方使用private Socket socket;//验证账户是否正确,并且连接服务器public boolean checkUser(String userID, String pwd) {boolean flag = false;//创建User对象user.setUserID(userID);user.setPasswd(pwd);try {//连接到服务器,发送user对象192.168.176.1socket = new Socket(InetAddress.getByName("192.168.176.1"), 9999);//得到对象流对象ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(user);//发送user对象//读取服务端回送的对象ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Message o = (Message) ois.readObject();//if (o.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {flag = true;//创建一个和服务器端保持通讯的线程ClientConnectServerThread thread = new ClientConnectServerThread(socket);thread.start();//将线程放到一个集合中,方便后续操作(对于客服端没用,因为只有一个子线程)ManageClientConnectServerThread.addClientConnectServerThread(userID, thread);} else {//如果失败,就不能启动和服务器的线程,关闭socketsocket.close();;}} catch (Exception e) {e.printStackTrace();}return flag;}//显示在线用户列表public void onLineUserList(){//发送一个messageMessage message = new Message();message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);message.setSender(user.getUserID());try {//发送给服务器ObjectOutputStream oos = //通过管理线程的到对应的socket,然后得到socket对应的输出流new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(user.getUserID()).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}//退出客户端,并给服务端退出的message对象public void logout(){Message message = new Message();message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);message.setSender(user.getUserID()); //要明确哪个客服端try {//ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());//因为现在一个客户端进程只有一个线程,所以这种写法也是对的,但是如果有多个线程那就不能用此socketObjectOutputStream oos =new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(user.getUserID()).getSocket().getOutputStream());oos.writeObject(message);System.out.println(user.getUserID()+"退出系统");System.exit(0);//结束进程} catch (IOException e) {e.printStackTrace();}}}

聊天类(私聊/群聊)

package server;import common.Message;import common.MessageType;import java.io.IOException;import java.io.ObjectOutputStream;import java.util.Date;/*** 建立聊天服务*/public class ChatClientServer {/*** @param senderId 发送者* @param getterId 接收者* @param content 内容*///私聊public void sendMessageToOne(String senderId, String getterId, String content) {//构建messageMessage message = new Message();message.setMesType(MessageType.MESSAGE_COMM_MES);message.setSender(senderId);message.setGetter(getterId);message.setContent(content);message.setSendTime(new Date().toString()); //发送时间System.out.println(senderId + " 对 " + getterId + " 说 " + content);try {//发送ObjectOutputStream oos =new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}//群聊public void sendMessageToAll(String senderId, String content) {Message message = new Message();message.setMesType(MessageType.MESSAGE_TO_ALL_MES);message.setSender(senderId);message.setContent(content);message.setSendTime(new Date().toString());System.out.println("我 对大家说 " + content);try {//发送ObjectOutputStream oos =new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}}

发送文件类

package server;import common.Message;import common.MessageType;import java.io.File;import java.io.FileInputStream;import java.io.IOException;import java.io.ObjectOutputStream;public class FileClientServer {/*** @param senderId 发送者* @param getterId 接收者* @param src发送路径* @param desc接收路径*/public void sendFileToOne(String senderId, String getterId, String src, String desc) {Message message = new Message();message.setMesType(MessageType.MESSAGE_FILE_MES);message.setSender(senderId);message.setGetter(getterId);message.setSrc(src);message.setDesc(desc);FileInputStream fis = null;byte[] bytes = new byte[(int) new File(src).length()];try {fis = new FileInputStream(src);fis.read(bytes);message.setFileBytes(bytes);} catch (IOException e) {e.printStackTrace();} finally {try {fis.close();} catch (IOException e) {e.printStackTrace();}}System.out.println("\n" + senderId + " 给 " + getterId + "发了文件");try {ObjectOutputStream oos =new ObjectOutputStream(ManageClientConnectServerThread.getClientConnectServerThread(senderId).getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}}

服务端

服务端主类

主要功能:代替MySQL存储账号信息,判断账号的正确性,启动一个子线程连接客服端,存储离线信息

package server;import common.Message;import common.MessageType;import common.User;import java.io.IOException;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import .ServerSocket;import .Socket;import java.util.ArrayList;import java.util.HashMap;/*** 这是服务端,在监听端口9999,等待客户端的连接,并保持通讯*/public class QQServer {private ServerSocket ss = null;//创建集合存放多个用户//HashMap没有处理线程安全,在多线程情况下是不安全的//ConcurrentHashMap处理线程安全(线程同步处理),在多线程情况下是安全的private static HashMap<String, User> validUsers = new HashMap<>(); //用集合代替Mysqlpublic static HashMap<String, ArrayList<Message>> notOnLineMessages = new HashMap<>(); //存储离线信息//启动服务端public static void main(String[] args) {new QQServer();}static {//静态代码块,伴随着类的加载而加载一次validUsers.put("100", new User("100", "12345"));notOnLineMessages.put("100", new ArrayList<>());validUsers.put("200", new User("200", "12345"));notOnLineMessages.put("200", new ArrayList<>());validUsers.put("300", new User("300", "12345"));notOnLineMessages.put("300", new ArrayList<>());validUsers.put("小灰洁", new User("小灰洁", "12345"));notOnLineMessages.put("小灰洁", new ArrayList<>());validUsers.put("sxy", new User("sxy", "12345"));notOnLineMessages.put("sxy", new ArrayList<>());validUsers.put("syj", new User("syj", "12345"));notOnLineMessages.put("syj", new ArrayList<>());}//判断账号和密码是否正确private boolean checkUser(String userID, String pwd) {User user = validUsers.get(userID);if (user == null) return false;if (user.getPasswd().equals(pwd)) return true;return false;}public QQServer() {//端口可以写在配置文件里try {System.out.println("服务端在9999端口监听...");//启动新闻推送new Thread(new SendNewsToAll()).start();ss = new ServerSocket(9999);while (true) {//和某个客户端连接后,继续监听Socket socket = ss.accept(); //用于连接客户端的socket//得到socket关联的对象输入流 接收用户发送的账户信息ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());User user = (User) ois.readObject();//得到socket关联的对象输出流 返回验证信息ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());Message message = new Message();//验证用户if (checkUser(user.getUserID(), user.getPasswd())) {message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);oos.writeObject(message);//判断是否有客服端在此客户端没上线时,发来离线信息,如果有就显示ArrayList<Message> messages = notOnLineMessages.get(user.getUserID());if (messages.size() != 0) {for (int i = 0; i < messages.size(); i++) {Message message1 = messages.get(i);ObjectOutputStream oos1 = new ObjectOutputStream(socket.getOutputStream());oos1.writeObject(message1);}}notOnLineMessages.remove(user.getUserID()); //移除离线相关信息//创建一个线程,和客户端保持通讯,该线程需要持有socket对象ServerConnectClientThread thread = new ServerConnectClientThread(socket, user.getUserID());thread.start();//因为一个客服端,建立多个子线程,所以为了方便管理,将所有子线程存入到集合中,方便使用ManageServerConnectClientThread.addServerConnectClientThread(user.getUserID(), thread);} else {//密码错误,也要进行信息反馈System.out.println("密码错误");message.setMesType(MessageType.MESSAGE_LOGIN_FAIL);oos.writeObject(message);oos.flush();//登录失败,要关闭socketsocket.close();}}} catch (Exception e) {e.printStackTrace();} finally {try {ss.close();} catch (IOException e) {e.printStackTrace();}}}}

服务端子线程类

功能:用于对子线程的各种操作,接收服务端子线程对应客服端发来的请求进行处理,显示在线用户列表,客服端下线,服务端子线程退出,私聊(在线/离线)和群聊,发文件

package server;import common.Message;import common.MessageType;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import .Socket;import java.util.HashMap;import java.util.Iterator;/*** 该类对应的对象和客户端保持通讯*/public class ServerConnectClientThread extends Thread {private Socket socket;private String userId;//连接到服务端的idpublic ServerConnectClientThread(Socket socket, String userId) {this.socket = socket;this.userId = userId;}public Socket getSocket() {return socket;}@Overridepublic void run() {//可以接收发送消息while (true) {try {System.out.println("服务端和客服端" + userId + "保持通信,读取数据...");ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());Message message = (Message) ois.readObject();if (message.getMesType().equals(MessageType.MESSAGE_GET_ONLINE_FRIEND)) {//在线用户列表System.out.println(message.getSender() + " 要在线用户列表");//得到用户列表String onLineUser = ManageServerConnectClientThread.getOnLineUser();//返回的数据信息Message message1 = new Message();message1.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND);message1.setContent(onLineUser);message1.setGetter(message.getSender());//写入到数据通道ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message1);} else if (message.getMesType().equals(MessageType.MESSAGE_CLIENT_EXIT)) {//客服端下线,服务端子线程退出System.out.println(message.getSender() + "退出系统");//将客服端对应的线程删除ManageServerConnectClientThread.deleteServerConnectClientThread(message.getSender());socket.close();break;} else if (message.getMesType().equals(MessageType.MESSAGE_COMM_MES)) {//私聊if (ManageServerConnectClientThread.getServerConnectClientThread(message.getGetter())==null){QQServer.notOnLineMessages.get(message.getGetter()).add(message);//离线消息存储}else {//根据message获取getterId,的到对应的线程ObjectOutputStream oos = newObjectOutputStream(ManageServerConnectClientThread.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());oos.writeObject(message);}} else if (message.getMesType().equals(MessageType.MESSAGE_TO_ALL_MES)) {//群发//遍历全部客户HashMap<String, ServerConnectClientThread> hm = ManageServerConnectClientThread.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()) {//获取在线用户IDString userId = iterator.next().toString();if (!userId.equals(message.getSender())){ObjectOutputStream oos =new ObjectOutputStream(hm.get(userId).getSocket().getOutputStream());oos.writeObject(message);}}}else if (message.getMesType().equals(MessageType.MESSAGE_FILE_MES)){//发文件if (ManageServerConnectClientThread.getServerConnectClientThread(message.getGetter())==null){QQServer.notOnLineMessages.get(message.getGetter()).add(message); //如果不在线}else {ObjectOutputStream oos = newObjectOutputStream(ManageServerConnectClientThread.getServerConnectClientThread(message.getGetter()).getSocket().getOutputStream());oos.writeObject(message);}}} catch (Exception e) {e.printStackTrace();}}}}

管理子线程类

用集合统一管理所有服务端子线程

package server;import java.util.HashMap;import java.util.Iterator;public class ManageServerConnectClientThread {private static HashMap<String, ServerConnectClientThread> hm = new HashMap<>();public static HashMap<String, ServerConnectClientThread> getHm() {return hm;}//添加线程对象到集合里面public static void addServerConnectClientThread(String userId, ServerConnectClientThread thread) {hm.put(userId,thread);}//删除线程对象public static void deleteServerConnectClientThread(String userId){hm.remove(userId);}public static ServerConnectClientThread getServerConnectClientThread(String userId){return hm.get(userId);}//返回在线用户列表public static String getOnLineUser(){//遍历HashMapIterator<String> iterator = hm.keySet().iterator();String onLineUserList = "";while (iterator.hasNext()){onLineUserList += iterator.next().toString()+" ";}return onLineUserList;}}

(服务端)发送新闻类

package server;import common.Message;import common.MessageType;import utilss.Utility;import java.io.IOException;import java.io.ObjectOutputStream;import java.util.Date;import java.util.HashMap;import java.util.Iterator;public class SendNewsToAll implements Runnable{@Overridepublic void run() {while (true){System.out.println("请输入服务器要推送的消息[输入exit表示退出]");String news = Utility.readString(1000);if ("exit".equals(news)){break;}//构建消息Message message = new Message();message.setMesType(MessageType.MESSAGE_TO_ALL_MES);message.setSender("server");message.setContent(news);message.setSendTime(new Date().toString());System.out.println("服务器推送给所有人消息:"+news);//遍历所有线程HashMap<String, ServerConnectClientThread> hm = ManageServerConnectClientThread.getHm();Iterator<String> iterator = hm.keySet().iterator();while (iterator.hasNext()){String onLineUserId = iterator.next().toString();ServerConnectClientThread thread = hm.get(onLineUserId);try {ObjectOutputStream oos = new ObjectOutputStream(thread.getSocket().getOutputStream());oos.writeObject(message);} catch (IOException e) {e.printStackTrace();}}}}}

总结

第一次bug

一直是登录失败,出现这种错误信息

发现主要原因是以下两点:借鉴

须要相同的包名相同的序列化ID

改过之后就可以登录了。

第二次bug

报的错误信息是这样的:java.io.EOFException

在网上查找相关错误信息,查了好久才有点眉目。借鉴

原因竟然是因为我使用的端口号8888这个socket的端口被阻塞了,需要换一个,然后我换成了9999,果然没出现了这个异常,程序正常运行。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。