哪个素材网站做美工最好,做网站要服务器和什么软件,男女做那个网站动态图片,网页拒绝了您的访问文章目录 一、多用户通信系统1.1 介绍1.2 公共类1.2.1 封装消息类1.2.2 用户类1.2.3 消息类型类1.2.4 控制台读取内容 二、用户登录2.1 客户端2.1.1 菜单界面 QQView2.1.2 验证用户UserClientService2.1.3 线程类 ClientConnectServerThread2.1.4 线程集合类 2.2 服务端2.2.1 服… 文章目录 一、多用户通信系统1.1 介绍1.2 公共类1.2.1 封装消息类1.2.2 用户类1.2.3 消息类型类1.2.4 控制台读取内容 二、用户登录2.1 客户端2.1.1 菜单界面 QQView2.1.2 验证用户UserClientService2.1.3 线程类 ClientConnectServerThread2.1.4 线程集合类 2.2 服务端2.2.1 服务端构造器2.2.2 服务端2.2.3 线程类 ServerConnectClientThread2.2.4 线程集合 三、拉取在线用户3.0 扩展类3.1 客户端3.1.1 UserClientService类3.1.2 ClientConnectServerThread线程类 3.2 服务端3.2.1 ServerConnectClientThread类3.2.2 ManagerServerConnectServerThread类 3.3 测试 四、无异常退出系统4.1 分析4.2 客户端4.2.1 UserClientService 退出 4.3 服务端4.3.1 ServerConnectClientThread 线程类4.3.2 ManagerServerConnectServerThread 线程集合类 一、多用户通信系统
1.1 介绍
需要技术
Java面向对象编程网络编程多线程IO流数据集合
需求分析
用户登录拉取在线用户列表无异常退出私聊群聊发文件服务器推送新闻
当客户端A和服务端建立连接后两边都会建立一个Socket也就是一边一个Socket
当客户端B和服务端建立连接后两边也都会建立一个Socket此时服务端有两个socket一个服务端A的另一个是服务端B的
我们在通讯的时候怎么保证客户端的两个Socket一直被持有(占有)呢
我们启动一个socket就启动了一个线程通讯的其实是线程中的Socket 假如客户端B写了一个数据到Socket里面希望群发一个消息也就是希望获取到服务端中所有线程里的Socket为此我们可以将服务端的Socket通过一个集合来管理将来服务端的线程很多 除此之外我们的客户端A可能与服务端有多个连接比如一条连接是发送文本信息的一条连接是发送文件的一条信息是视频聊天的…此时服务端与客户端便是多个通道连接一条通道连接很难把功能一次性实现此时客户端A也需要一个管理线程的集合 服务端的工作逻辑
当有客户端连接到服务端后会得到一个Socket启动一个线程该线程会持有Socket对象该Socket是线程的一个属性为了将来更好的管理多个线程以后会涉及到将消息推送给多个客户端需要使用一个集合来管理
客户端的工作逻辑
和服务端通信时使用对象方式可以使用对象流来读写当客户端连接到服务端后也会得到socket我们也会启动一个线程并且该线程会持有此socket为了将来更好管理线程需要使用一个集合来管理线程
1.2 公共类
1.2.1 封装消息类
/*** 封装消息* 表示客户端和服务端通信时的消息对象* 发送消息流程客户端A -》 服务端 -》 客户端B 假如服务器瘫痪聊天便不可以使用* 如果客户端A与客户端B在同一个局域网 客户端A -》客户端B*/
Data
public class Message implements Serializable {private static final long serialVersionUID -3567747187962510012L;/*** 消息类型:发送文件、纯文本、视频聊天....*/private String mesType;/**发送者*/private String sender;/***接收者*/private String getter;/*** 消息内容*/private String content;/*** 发送时间*/private String sendTime;}1.2.2 用户类
/*** 客户信息*/
Data
AllArgsConstructor
NoArgsConstructor
public class User implements Serializable {private static final long serialVersionUID 4300366482842276408L;private String userId; //用户idprivate String passwd; //用户密码
}1.2.3 消息类型类
/*** 消息类型* 不同行亮的值表示不同的消息类型*/
Getter
public enum MessageType {/*** 登录成功*/MESSAGE_LOGIN_SUCCEED(1),/*** 登录失败*/MESSAGE_LOGIN_FAIL(2);private final String code;MessageType(String code) {this.code code;}public static String find(Integer code) {for (MessageType value : MessageType.values()) {if (code.toString().equals(value.getCode())) {return value.getCode();}}return null;}
}1.2.4 控制台读取内容
public class Utility {private static Scanner scanner;static {scanner new Scanner(System.in);}public Utility() {}public static char readMenuSelection() {while (true) {String str readKeyBoard(1, false);char c str.charAt(0);if (c 1 || c 2 || c 3 || c 4 || c 5) {return c;}System.out.print(选择错误请重新输入);}}public static char readChar() {String str readKeyBoard(1, false);return str.charAt(0);}public static char readChar(char defaultValue) {String str readKeyBoard(1, true);return str.length() 0 ? defaultValue : str.charAt(0);}public static int readInt() {while (true) {String str readKeyBoard(2, false);try {int n Integer.parseInt(str);return n;} catch (NumberFormatException var3) {System.out.println(数字输入错误请重新输入);}}}public static int readInt(int defaultValue) {while (true) {String str readKeyBoard(2, true);if (str.equals()) {return defaultValue;}try {int n Integer.parseInt(str);return n;} catch (NumberFormatException var4) {System.out.print(数字输入错误请重新输入);}}}private static String readKeyBoard(int limit, boolean blankReturn) {String line ;while (scanner.hasNextLine()) {line scanner.nextLine();if (line.length() 0) {if (blankReturn) {return line;}} else {if (line.length() 1 line.length() limit) {break;}System.out.println(输入长度不大于 limit 错误请重新输入);}}return line;}public static String readString(int limit) {return readKeyBoard(limit, false);}public static char readConfirmSelection(){while (true){String strreadKeyBoard(1,false).toUpperCase();char cstr.charAt(0);if(cY||cN){return c;}System.out.print(选择错误请重新输入);}}}二、用户登录
功能说明
暂时不使用数据库后面使用HashMap模拟数据库支持多个用户的登录
人为规定用户名/id100密码123456便可以登录其他用户不能登陆
信息的传递我们都以对象的形式来完成将客户端和服务端交流的信息封装成对象这便需要使用对象流
客户端向服务端发送一个User对象服务器端拿到User对象信息以后进行验证User对象是否合法然后服务端给客户端回复一个message对象
客户端拿到message对象之后我们可以判断登录成功了还是失败了
2.1 客户端
2.1.1 菜单界面 QQView
/*** 菜单界面*/
public class QQView {/*** 控制是否显示菜单*/private boolean loop true;/*** 接收用户的键盘输入*/private String key ;/*** 完成用户登录验证和用户注册等功能*/public UserClientService userClientService new UserClientService();public static void main(String[] args) {QQView qqView new QQView();qqView.mainMenu();System.out.println(退出客户端系统);}/*** 显示主菜单*/private void mainMenu() {while (loop) {System.out.println(***********欢迎登录网络通信系统*************);System.out.println(\t\t 1 登录系统);System.out.println(\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 password Utility.readString(50);//TODO 到服务端验证用户是否合法if (userClientService.checkUser(userId,password)) {//进入二级菜单System.out.println(String.format(网络通信系统二级菜单(用户%s), userId));while (loop) {System.out.println(String.format(\n网络通信系统二级菜单(用户%s), userId));System.out.println(\t\t 1.显示在线用户列表);System.out.println(\t\t 2.群发消息);System.out.println(\t\t 3.私聊消息);System.out.println(\t\t 4.发送文件);System.out.println(\t\t 9.退出系统);System.out.print(请输入你的选择:);key Utility.readString(1);switch (key) {case 1:break;case 2:break;case 3:break;case 4:break;case 9:loop false;System.out.println(退出系统);break;}}}else {System.out.println(登录服务器失败用户名或密码存在问题);}break;case 9:loop false;System.out.println(退出系统);}}}
}2.1.2 验证用户UserClientService
/*** 完成用户登录验证和用户注册等功能*/
Data
public class UserClientService {//其他地方也会使用user信息所以将其作为一个属性private User user new User();private Socket socket null;//根据userId和pwd到服务器验证该用户是否合法public boolean checkUser(String userId, String pwd) {//临时变量b用户是否合法的标志boolean b false;//TODO 创建User对象user.setUserId(userId);user.setPasswd(pwd);try {//TODO 连接到服务端发送User对象socket new Socket(InetAddress.getByName(127.0.0.1), 9999);//得到ObjectOutputStream对象流(序列化流也是字节流中一种)ObjectOutputStream oos new ObjectOutputStream(socket.getOutputStream());oos.writeObject(user);oos.flush();//TODO 读取从服务器回复的Message对象ObjectInputStream ois new ObjectInputStream(socket.getInputStream());Message msg (Message) ois.readObject();if (MessageType.find(1).equals(msg.getMesType())) {//登录成功//一旦登录成功我们需要启动一个线程维护或者持有此socket保持此线程可以跟我们服务器端一直进行通信//不启动线程的话此Socket不好维护。如果我们有数据发送或者接收我们可以从这个线程里面进行拉取//为什么将Socket放入一个线程中管理// 1.如果不创建这个线程的话一个客户端会有多个socketsocket管理起来就比较麻烦// 2.需要socket不断的从数据通道中读写数据所以也必须做成一个线程ClientConnectServerThread ccst new ClientConnectServerThread(socket);//启动客户端的线程ccst.start();//为了后面客户端的扩展我们将线程放入到集合中管理ManagerClientConnectServerThread.addClientConnectServerThread(userId, ccst);b true;} else {//登录失败//我们是有Socket的但是没有线程即登录失败不能启动和服务器通信的线程//关闭socketsocket.close();}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}return b;}
}2.1.3 线程类 ClientConnectServerThread
EqualsAndHashCode(callSuper true)
Data
AllArgsConstructor
NoArgsConstructor
public class ClientConnectServerThread extends Thread {//该线程需要持有Socket属性private Socket socket;/***因为Thread需要在后台跟我们的服务器进行通信(保持一个联系)因此我们使用while循环来控制*/Overridepublic void run() {while(true){//一直读取从服务器端回收的消息System.out.println(客户端线程等待读取从服务端发送的消息....);try {ObjectInputStream ois new ObjectInputStream(socket.getInputStream());//如果服务器端没有发送消息过来这个地方会堵塞此线程会一直等待//这就是一个堵塞式网络编程效率是相对比较低的Message message (Message)ois.readObject();} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}
}2.1.4 线程集合类
/*** 管理客户端连接到服务端线程的一个类*/
public class ManagerClientConnectServerThread {//把多个线程放入一个HashMap中进行管理key是用户idvalue是客户端与服务端通信的线程private static HashMapString, ClientConnectServerThread hm new HashMap();//将某个线程加入到集合中public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {hm.put(userId, clientConnectServerThread);}public static ClientConnectServerThread getClientConnectServerThread(String userId) {return hm.get(userId);}
}2.2 服务端
2.2.1 服务端构造器
/*** 此类创建一个QQServer对象启动后台的服务*/
public class QQFrame {public static void main(String[] args) {//创建QQServer对象会启动QQServer构造器QQServer qqServer new QQServer();}
}2.2.2 服务端
/*** 这是服务器在监听9999等待客户端的连接并保持通信*/
Data
public class QQServer {//创建一个集合存放多个用户如果是此用户登录便认为是合法的//也可以使用ConcurrentHashMap可以在并发的环境下处理没有线程安全问题//HashMap是没有处理线程安全的因此在多线程情况下是不安全的private static HashMapString,User validUser new HashMap();private ServerSocket serverSocket null;/*** 进行类加载的时候会执行下面这个代码*/static {validUser.put(100,new User(100,123456));validUser.put(200,new User(200,123456));validUser.put(300,new User(300,123456));validUser.put(至尊宝,new User(至尊宝,123456));validUser.put(紫霞仙子,new User(紫霞仙子,123456));validUser.put(菩提老祖,new User(菩提老祖,123456));}/*** 这是一个循环监听的过程* 并不是客户端A发送完信息服务器接收到后此服务器就关闭而是一直监听因为还有可能其他客户端发送过来信息*/public QQServer() {System.out.println(服务端在9999端口监听....);ObjectInputStream ois null;ObjectOutputStream oos null;try {this.serverSocket new ServerSocket(9999);//监听是一直进行当和某个客户端连接后会继续监听因此使用while循环while (true) {//没有客户端连接9999端口时程序会堵塞等待连接Socket socket serverSocket.accept();ois new ObjectInputStream(socket.getInputStream());//如果服务器端没有发送消息过来这个地方会堵塞此线程会一直等待//读取客户端发送的User对象User user (User) ois.readObject();//创建Message对象准备恢复客户端Message message new Message();oos new ObjectOutputStream(socket.getOutputStream());//验证用户是否合法User userValid validUser.get(user.getUserId());if (userValid!null userValid.getUserId().equals(user.getUserId()) userValid.getPasswd().equals(user.getPasswd())) {//合法用户message.setMesType(MessageType.find(1));//给客户端进行回复
// ObjectOutputStream oos new ObjectOutputStream(socket.getOutputStream());oos.writeObject(message);oos.flush();//创建一个线程和客户端保持通信。//该线程需要持有Socket对象ServerConnectClientThread serverConnectClientThread new ServerConnectClientThread(user.getUserId(), socket);serverConnectClientThread.start();//把该线程对象放入到一个集合中ManagerServerConnectServerThread.addClientThread(user.getUserId(), serverConnectClientThread);} else {//登录失败message.setMesType(MessageType.find(2));oos.writeObject(message);oos.flush();socket.close();}}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();} finally {
// 如果服务端退出了while循环说明服务器端不再监听了因此需要关闭资源if (serverSocket ! null) {try {serverSocket.close();} catch (IOException e) {e.printStackTrace();}}if (ois !null){try {ois.close();} catch (IOException e) {e.printStackTrace();}}if (oos !null){try {oos.close();} catch (IOException e) {e.printStackTrace();}}}}
}2.2.3 线程类 ServerConnectClientThread
/*** 该类对应的对象和某个客户端保持通信*/
EqualsAndHashCode(callSuper true)
Data
AllArgsConstructor
NoArgsConstructor
public class ServerConnectClientThread extends Thread{/*** 可以区分此socket是和哪个用户进行关联的*/private String userId;//连接到服务端的这个用户idprivate Socket socket;/*** 线程处于run状态可以发送或者接收客户端的消息*/Overridepublic void run() {//不断的从socket中读数据和写数据while(true){System.out.println(服务端和客户端保持通信读取数据.... userId:userId);ObjectInputStream ois null;try {ois new ObjectInputStream(socket.getInputStream());//读取数据Message message (Message) ois.readObject();//后面会使用Message} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}//如果服务器端没有发送消息过来这个地方会堵塞此线程会一直等待//读取客户端发送的User对象}}
}2.2.4 线程集合
/*** 该类用于管理和客户端通信的线程*/
Data
public class ManagerServerConnectServerThread {private static HashMapString,ServerConnectClientThread hm new HashMap();/***添加线程对象到hm集合*/public static void addClientThread(String userId, ServerConnectClientThread clientConnectServerThread) {hm.put(userId, clientConnectServerThread);}/***从集合中获取对应线程对象*/public static ServerConnectClientThread getClientThread(String userId) {return hm.get(userId);}
}三、拉取在线用户
可以将所有在线的列表拉下来
如果登录成功的话客户端会有一个线程服务端会有一个线程两个线程都会持有自己的socket。
如果客户端要获得所有在线用户的列表只能向服务器发送请求索要在线用户列表因为只有服务器端才知道哪个用户上线了
实现这个功能其实就是客户端向服务端发送一个message对象服务端会读取到这个message看我们客户端想要什么东西message中会封装消息的类型此次请求的目的是什么之后服务端会给客户端回复一个Message并且会包含在线的用户列表
3.0 扩展类
/*** 消息类型* 不同常量的值表示不同的消息类型*/
Getter
public enum MessageType {/*** 登录成功*/MESSAGE_LOGIN_SUCCEED(1),/*** 登录失败*/MESSAGE_LOGIN_FAIL(2),/*** 普通信息对象*/MESSAGE_COMM_MES(3),/*** 获取在线用户* 要求服务器返回在线用户列表*/MESSAGE_GET_ONLINE_FRIEND(4),/*** 服务器返回在线用户列表*/MESSAGE_RETTURN_ONLINE_FRIEND(5),/*** 客户端请求退出*/MESSAGE_CLIENT_EXIT(6),;private final String code;MessageType(String code) {this.code code;}public static String find(Integer code) {for (MessageType value : MessageType.values()) {if (code.toString().equals(value.getCode())) {return value.getCode();}}return null;}
}3.1 客户端
3.1.1 UserClientService类
向服务端发送消息
/*** 完成用户登录验证和用户注册等功能*/
Data
public class UserClientService {//其他地方也会使用user信息所以将其作为一个属性private User user new User();private Socket socket null;/*** 向服务器端请求在线用户列表*/public void onlineFriendList(){//发送一个message并且消息的类型是MESSAGE_GET_ONLINE_FRIENDMessage message new Message();message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode());message.setSender(user.getUserId());//发送给服务器//得到当前线程的Socket对应的ObjectOutputStream//clientConnectServerThread线程一直在运行过程中监听从服务器传输过来的消息ClientConnectServerThread clientConnectServerThread ManagerClientConnectServerThread.getClientConnectServerThread(user.getUserId());try {ObjectOutputStream oos new ObjectOutputStream(clientConnectServerThread.getSocket().getOutputStream());oos.writeObject(message);oos.flush();} catch (IOException e) {e.printStackTrace();}}3.1.2 ClientConnectServerThread线程类
处理服务端发送过来的消息
EqualsAndHashCode(callSuper true)
Data
AllArgsConstructor
NoArgsConstructor
public class ClientConnectServerThread extends Thread {//该线程需要持有Socket属性private Socket socket;/***因为Thread需要在后台跟我们的服务器进行通信(保持一个联系)因此我们使用while循环来控制*/Overridepublic void run() {while(true){//一直读取从服务器端回收的消息System.out.println(客户端线程等待读取从服务端发送的消息....);try {ObjectInputStream ois new ObjectInputStream(socket.getInputStream());//如果服务器端没有发送消息过来这个地方会堵塞此线程会一直等待//这就是一个堵塞式网络编程效率是相对比较低的Message message (Message)ois.readObject();//判断message的类型然后做响应的业务处理if (message.getMesType().equals(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode())){//获取在线用户取出在线列表信息并显示String[] onlineUsers message.getContent().split( );System.out.println(当前在线用户列表如下);for (int i0;ionlineUsers.length;i){System.out.println(用户onlineUsers[i]);}}else{System.out.println(其他类型的message暂时不处理);}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}}}
}3.2 服务端
3.2.1 ServerConnectClientThread类
ServerConnectClientThread类会不断的从客户端与服务端的通道中读取数据
/*** 该类对应的对象和某个客户端保持通信*/
EqualsAndHashCode(callSuper true)
Data
AllArgsConstructor
NoArgsConstructor
public class ServerConnectClientThread extends Thread{/*** 可以区分此socket是和哪个用户进行关联的*/private String userId;//连接到服务端的这个用户idprivate Socket socket;/*** 线程处于run状态可以发送或者接收客户端的消息*/Overridepublic void run() {//不断的从socket中读数据和写数据while(true){System.out.println(服务端和客户端保持通信读取数据.... userId:userId);ObjectInputStream ois null;try {ois new ObjectInputStream(socket.getInputStream());//读取数据Message message (Message) ois.readObject();//根据Message的类型判断客户端想要执行什么操作if (MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode().equals(message.getMesType())){//拉取在线用户客户端要拉取在线用户列表Socket socket ManagerServerConnectServerThread.getClientThread(userId).getSocket();ObjectOutputStream oos new ObjectOutputStream(socket.getOutputStream());//构建Message发送给服务端Message returnMessage new Message();returnMessage.setMesType(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode());returnMessage.setContent(ManagerServerConnectServerThread.getOnlineUser());//说明要发送给谁returnMessage.setGetter(message.getSender());//返回给客户端oos.writeObject(returnMessage);oos.flush();}else {System.out.println(其他类型暂时不处理);}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}//如果服务器端没有发送消息过来这个地方会堵塞此线程会一直等待//读取客户端发送的User对象}}
}3.2.2 ManagerServerConnectServerThread类
完成获取在线用户功能
/*** 该类用于管理和客户端通信的线程*/
Data
public class ManagerServerConnectServerThread {private static HashMapString, ServerConnectClientThread hm new HashMap();/*** 添加线程对象到hm集合*/public static void addClientThread(String userId, ServerConnectClientThread clientConnectServerThread) {hm.put(userId, clientConnectServerThread);}/*** 从集合中获取对应线程对象*/public static ServerConnectClientThread getClientThread(String userId) {return hm.get(userId);}/*** 获取在线用户*/public static String getOnlineUser() {//集合遍历遍历hashMap的keyIteratorString iterator hm.keySet().iterator();String onlineUserList ;while (iterator.hasNext()) {onlineUserList iterator.next().toString() ;}return onlineUserList;}}3.3 测试
客户端信息 服务端信息 四、无异常退出系统
4.1 分析
为什么要实现无异常退出
正常的情况下应该是下图的情况
客户端相当于一个进程在进程中会有一个主线程main在主线程main中又开了另外一个线程和服务端进行通信此进程并循环的读取服务端发送过来的消息 假如说我们的main线程结束了但是我们和服务端通信的线程并没有结束还是在进行等待接收服务器回传过来的消息因此此线程没有结束那此进程也不会结束 然后就会出现下面的情况
提示已经退出系统但是依然还在运行 怎么解决这个问题
我们可以在主线程中调用一个方法给服务器端发送一个退出系统的消息Message然后调用System.exit(0)方法正常退出直接会将整个进程挂掉
给服务器发送一个退出系统的消息Message有什么作用
服务器中会有一个对应的线程不断的读取从客户端发送过来的消息如果我们发现客户端发送过来的消息是退出的消息我们将socket关闭并退出线程就可以了 4.2 客户端
4.2.1 UserClientService 退出
/*** 编写方法退出客户端并给服务端发送一个退出系统的Message对象*/
public void logout(){Message message new Message();message.setMesType(MessageType.MESSAGE_CLIENT_EXIT.getCode());// 要退出这个用户message.setSender(user.getUserId());ClientConnectServerThread clientConnectServerThread ManagerClientConnectServerThread.getClientConnectServerThread(user.getUserId());try {ObjectOutputStream oos new ObjectOutputStream(clientConnectServerThread.getSocket().getOutputStream());oos.writeObject(message);oos.flush();System.exit(0);} catch (IOException e) {e.printStackTrace();}
}4.3 服务端
4.3.1 ServerConnectClientThread 线程类
退出系统的使用一定要使用一个return或者break
假如说不使用的话while循环会一直进行也会一直执行 Message message (Message) ois.readObject();代码由于客户端已经关闭这个地方就会抛出大量的IO异常提示XX连接失败或者xxx已经关闭
/*** 该类对应的对象和某个客户端保持通信*/
EqualsAndHashCode(callSuper true)
Data
AllArgsConstructor
NoArgsConstructor
public class ServerConnectClientThread extends Thread {/*** 可以区分此socket是和哪个用户进行关联的*/private String userId;//连接到服务端的这个用户idprivate Socket socket;/*** 线程处于run状态可以发送或者接收客户端的消息*/Overridepublic void run() {//不断的从socket中读数据和写数据while (true) {System.out.println(服务端和客户端保持通信读取数据.... userId: userId);ObjectInputStream ois null;try {ois new ObjectInputStream(socket.getInputStream());//读取数据Message message (Message) ois.readObject();//根据Message的类型判断客户端想要执行什么操作if (MessageType.MESSAGE_GET_ONLINE_FRIEND.getCode().equals(message.getMesType())) {System.out.println(用户 userId 获取在线用户);//拉取在线用户客户端要拉取在线用户列表Socket socket ManagerServerConnectServerThread.getClientThread(userId).getSocket();ObjectOutputStream oos new ObjectOutputStream(socket.getOutputStream());//构建Message发送给服务端Message returnMessage new Message();returnMessage.setMesType(MessageType.MESSAGE_RETTURN_ONLINE_FRIEND.getCode());returnMessage.setContent(ManagerServerConnectServerThread.getOnlineUser());//说明要发送给谁returnMessage.setGetter(message.getSender());//返回给客户端oos.writeObject(returnMessage);oos.flush();} else if (MessageType.MESSAGE_CLIENT_EXIT.getCode().equals(message.getMesType())) {//说明客户端想要退出服务端要将socket关闭并退出线程就可以了//将客户端对应的线程从集合中删除ManagerServerConnectServerThread.remove(userId);//关闭socketsocket.close();System.out.println(用户userId退出系统);//退出循环return;} else {System.out.println(其他类型暂时不处理);}} catch (IOException | ClassNotFoundException e) {e.printStackTrace();}//如果服务器端没有发送消息过来这个地方会堵塞此线程会一直等待//读取客户端发送的User对象}}
}4.3.2 ManagerServerConnectServerThread 线程集合类
/*** 从集合中删除掉某个线程对象*/
public static void remove(String userId) {hm.remove(userId);
}