网络编程综合项目-多用户通信系统

news/2024/4/27 22:35:39/文章来源:https://blog.csdn.net/m0_64637029/article/details/137126163

文章目录

    • 1.项目所用技术栈
          • 本项目使用了java基础,面向对象,集合,泛型,IO流,多线程,Tcp字节流编程的技术
    • 2.通信系统整体分析
        • 主要思路(自己理解)
          • 1.如果不用多线程
          • 2.使用多线程
          • 3.对多线程的新理解
    • 3.功能实现——用户登录
        • 1.实现传输数据的三个类Message和User和MessageType
          • 1.首先创建两个模块QQSever和QQClient
          • 2.完成两个模块共有类的编写
        • 2.实现用户登录界面框架
          • 1.导入工具类utils/Utility.java
          • 2.编写基本用户界面view/QQView.java
        • 3.实现客户端的登录部分
          • 1.qqclient/service/UserClientService.java
          • 2.qqclient/service/ManageClientConnectServerThread.java
          • 3.qqclient/service/ClientConnectServerThread.java
          • 4.修改QQView.java中的验证用户是否合法语句
        • 4.实现服务器端的登录部分
          • 1.qqserver/service/QQServer.java
          • 2.qqserver/service/ServerConnectClientThread.java
        • 5.登录阶段运行调试过程
          • 1.第一次运行,报错!(用户名密码正确时)
          • 解决方法
          • 2.第二次运行,报错!(用户名密码不正确时)
          • 原因
          • 解决方法
        • 6.实现多个合法用户可以登录
          • qqserver/service/QQServer.java更新
    • 4.功能实现——拉取在线用户
        • 1.功能完成
          • 1.qqcommon/MessageType.java更新
          • 2.qqclient/service/ClientConnectServerThread.java更新
          • 3.qqclient/service/UserClientService.java更新
          • 4.view/QQView.java更新
          • 5.qqserver/service/ManageClientThreads.java更新
            • 添加方法
          • 6.qqserver/service/QQServer.java更新
            • 添加方法
          • 7.qqserver/service/ServerConnectClientThread.java更新
            • try语句更新
        • 2.调试阶段
          • 1.代码冗余
          • 2.线程同步问题
    • 5.功能实现——无异常退出系统
        • 1.功能完成
          • 1.qqcommon/MessageType.java更新
          • 2.qqclient/service/ClientConnectServerThread.java更新
            • try语句更新
          • 3.qqclient/service/UserClientService.java更新
            • 添加三个方法
          • 4.view/QQView.java更新
          • 5.qqserver/service/QQServer.java更新
            • 添加两个方法
          • 6.qqserver/service/ManageClientThreads.java更新
            • 添加方法
          • 7.qqserver/service/ServerConnectClientThread.java更新
        • 2.调试阶段
          • 1.出现空指针异常
          • 2.数据未同步
          • 3.安全性提升
    • 6.功能实现——私聊功能
        • 1.功能完成
          • 1.qqclient/service/ClientConnectServerThread.java更新
          • 2.qqclient/service/UserClientService.java更新
            • 添加方法
          • 3.view/QQView.java更新
          • 4.qqserver/service/QQServer.java更新
            • 添加方法
          • 5.qqserver/service/ServerConnectClientThread.java更新
        • 2.调试阶段
          • 并未发现错误
    • 7.功能实现——群发功能
        • 1.功能完成
          • 1.qqcommon/MessageType.java更新
          • 2.qqclient/service/ClientConnectServerThread.java更新
          • 3.qqclient/service/UserClientServer.java更新
            • 添加方法
          • 4.qqserver/service/QQServer.java更新
          • 5.qqserver/service/QQServer.java更新
            • 添加方法
          • 6.qqserver/service/ServerConnectClientThread.java更新
        • 2.调试阶段
          • 未发现错误
    • 8.功能实现——发文件
        • 1.功能完成
          • 1.qqcommon/MessageType.java更新
          • 2.qqcommon/Message.java更新
          • 3.qqclient/service/ClientConnectServerThread.java更新
          • 4.qqclient/service/UserClientServer.java更新
            • 添加方法
          • 5.view/QQView.java更新
          • 6.qqserver/service/ServerConnectClientThread.java更新
        • 2.调试阶段
          • 1.传输文件大小膨胀
    • 9.功能实现——服务器端推送新闻
        • 1.功能完成
          • 1.qqserver/service/SendAllThread.java
          • 2.qqserver/service/ServerConnectClientThread.java更新
        • 2.调试阶段
          • 1.子线程群发问题

1.项目所用技术栈

本项目使用了java基础,面向对象,集合,泛型,IO流,多线程,Tcp字节流编程的技术

2.通信系统整体分析

image-20240112092954924

主要思路(自己理解)
1.如果不用多线程
  1. 客户端A连接并发送消息:服务端B通过 accept 方法接受客户端A的连接,然后读取数据。
  2. 服务端处理并响应:服务端B处理客户端A的数据,发送响应,然后继续监听新的消息或关闭连接。如果服务器继续监听来自A的数据,它将继续阻塞在读操作上。
  3. 客户端A不再发送数据:如果客户端A在发送了一些数据之后停止发送,并且服务器端正在等待读取更多数据,这时服务端将阻塞在对A的读操作上,因为它正在等待A发送更多数据。
  4. 客户端B尝试连接:由于服务端B正在处理客户端A的连接并阻塞在读操作上,它无法接受客户端B的连接请求。直到服务端B处理完A的请求并返回到 accept 方法,客户端B才能连接。
2.使用多线程
  1. 客户端A向服务器端B建立连接,连接成功,客户端A和服务器端各自有一个socket
  2. 客户端A向服务器发送User对象(包含用户名和密码),服务器端获取内容并验证,验证结束之后将结果返回给客户端A
  3. 客户端A收到结果之后,如果登录成功,则开启一个子线程,将socket放进去,使得子线程能够对其进行操作,然后子线程一直读取通道中的信息,如果没有信息则会阻塞。而主线程则会继续执行界面的操作,两者互不干涉
  4. 此时服务器端则会也开启一个线程,将socket放到线程中,然后持续读取与客户端A通道中的信息,以执行特定的操作,然后服务器端的主线程会继续进行监听,如果有其他的客户端链接则直接连接上
  5. 此时客户端B链接服务器端,服务器端提供链接并且验证User,如果正确则服务器端再开一个线程执行跟上面同样的操作,而主线程依然继续监听,这样就实现了多用户连接。
3.对多线程的新理解
  1. 多线程就相当于一个独立于主线程之外,可以运行的实例中的run方法
  2. 主线程可以实例化为多个子线程,然后调用run方法,对当前实例进行操作
  3. 当仅仅靠主线程无法实现目标时就要使用多线程并发执行,单独开一个线程,执行特定的任务
  4. 多线程的设计,首先要明确这个线程要完成什么功能,需要给他传递什么属性,然后就可以开始设计这个单线程,最后还要考虑这个线程是不是要并发执行,如果要并发执行,则就要考虑,对象锁或者类锁实现同步

3.功能实现——用户登录

1.实现传输数据的三个类Message和User和MessageType
1.首先创建两个模块QQSever和QQClient
2.完成两个模块共有类的编写
  1. qqcommon/Message.java

    package qqcommon;import java.io.Serializable;/*** @author 孙显圣* @version 1.0* 表示客户端和服务器端通讯时的消息对象*/
    public class Message implements Serializable { //也需要进行序列化private String sender; //发送者private String getter; //接受者private String content; //消息内容private String sendTime; //发送时间private String 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;}public String getMesType() {return mesType;}public void setMesType(String mesType) {this.mesType = mesType;}
    }
  2. qqcommon/MessageType.java

    package qqcommon;/*** @author 孙显圣* @version 1.0*/
    public interface MessageType {//在接口中定义了不同的常量//不同常量的值表示不同的消息类型String MESSAGE_LOGIN_SUCCEED = "1"; //表示登录成功String MESSAGE_LOGIN_FAIL = "2"; //表示登录失败
    }
  3. qqcommon/User.java

    package qqcommon;import java.io.Serializable;/*** @author 孙显圣* @version 1.0* 表示一个用户/客户信息*/
    public class User implements Serializable { //由于需要序列化所以需要实现接口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;}
    }
2.实现用户登录界面框架
1.导入工具类utils/Utility.java
package utils;/**工具类的作用:处理各种情况的用户输入,并且能够按照程序员的需求,得到用户的控制台输入。
*/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;}
}
2.编写基本用户界面view/QQView.java
package view;import utils.Utility;/*** @author 孙显圣* @version 1.0* 客户端的菜单界面*/
public class QQView {public static void main(String[] args) {new QQView().mainMenu();}private boolean loop = true; //控制主菜单循环执行//显示主菜单的方法private void mainMenu() {while (loop) { //循环显示菜单System.out.println("==========欢迎登录网络通信系统==========");System.out.println("          1 登录系统");System.out.println("          9 退出系统");System.out.print("请输入您的选择:");String s = Utility.readString(1); //读取一个字符//根据选择执行操作switch (s) {case "1":System.out.println("请输入用户号");String userId = Utility.readString(50);System.out.println("请输入密  码");String passwd = Utility.readString(50);//去服务端验证该用户是否合法//1.假设合法if (false) {//循环输出菜单while (loop) {System.out.println("==========网络通信系统二级菜单==========");System.out.println("          1 显示在线用户列表");System.out.println("          2 群发消息");System.out.println("          3 私聊消息");System.out.println("          4 发送文件");System.out.println("          9 退出系统");System.out.print("请输入您的选择:");String key = Utility.readString(1);//根据选择做出相应操作switch (key) {case "1":System.out.println("显示在线用户列表");break;case "2":System.out.println("群发消息");break;case "3":System.out.println("私聊消息");break;case "4":System.out.println("发送文件");break;case "9":System.out.println("==========用户退出系统==========");loop = false;break;}}}//2.不合法else {//退出这个switchSystem.out.println("==========用户名或密码不正确!==========");break;}break;case "9":System.out.println("==========用户退出系统==========");loop = false;break;}}}
}
3.实现客户端的登录部分
1.qqclient/service/UserClientService.java
package qqclient.service;import com.sun.org.apache.xpath.internal.operations.Variable;
import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;import java.io.*;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;/*** @author 孙显圣* @version 1.0* 完成用户登录验证和用户注册等等功能*/
public class UserClientService {private User user = new User(); //由于可能在其他地方需要使用到这个User对象,所以将其设置为这个类的属性//根据前端输入的用户名和密码,封装成User对象并且发送到服务器端,接受服务器端返回的Message对象,并根据mesType来确定是否符合要求public boolean checkUser(String userId, String pwd) throws IOException, ClassNotFoundException {//设置一个临时变量,用于返回值boolean res = false;//将用户名和密码封装到User对象中user.setUserId(userId);user.setPasswd(pwd);//获取客户端的socketSocket socket = new Socket(InetAddress.getLocalHost(), 9999);//获取客户端的输出流OutputStream outputStream = socket.getOutputStream();//将其转换成对象处理流ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);//将user对象发送objectOutputStream.writeObject(user);//获取从服务器端回复的Message对象//获取客户端的输入流InputStream inputStream = socket.getInputStream();//转换为对象处理流ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);//读取message对象Message o = (Message) objectInputStream.readObject(); //此时我们确定读取的一定是Message对象,所以将其向下转型//根据获取的mesType来确定是否成功if (o.getMesType().equals(MessageType.MESSAGE_LOGIN_SUCCEED)) {//创建一个和服务器端保持通信的线程ClientConnectServerThread clientConnectServerThread = new ClientConnectServerThread(socket);//启动客户端的线程,使其等待服务器的信息clientConnectServerThread.start();//为了后面客户端的扩展,放到一个集合中ManageClientConnectServerThread.addClientConnectServerThread(userId, clientConnectServerThread);//成功了,将返回值设置为trueres = true;} else {//如果登录失败则虽然没有启动线程但是还是开启了一个socket,所以要关闭socket.close();}return res;}}
2.qqclient/service/ManageClientConnectServerThread.java
package qqclient.service;import java.util.HashMap;/*** @author 孙显圣* @version 1.0* 该类管理客户端连接到服务器端的线程的类*/
public class ManageClientConnectServerThread {//把多个线程放到一个HashMap的集合中,key是用户id,value是线程private static HashMap<String, ClientConnectServerThread> hm = new HashMap<>();//将某个线程放到集合中public static void addClientConnectServerThread(String userId, ClientConnectServerThread clientConnectServerThread) {hm.put(userId, clientConnectServerThread);}//通过userId可以得到该线程public static ClientConnectServerThread getClientConnectServerThread(String userId) {return hm.get(userId);}}
3.qqclient/service/ClientConnectServerThread.java
package qqclient.service;import qqcommon.Message;import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;/*** @author 孙显圣* @version 1.0* 这个线程持有socket*/
public class ClientConnectServerThread extends Thread {private Socket socket;//该构造器可以接受一个Socket对象public ClientConnectServerThread(Socket socket) {this.socket = socket;}//更方便的得到Socketpublic Socket getSocket() {return socket;}//因为线程需要在后台一直保持和服务器的通信,因此使用while循环@Overridepublic void run() {while (true) {System.out.println("客户端线程,等待读取从服务器端发送的信息");try {//获取该线程socket的对象输入流ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());//读取信息Message o = (Message) objectInputStream.readObject(); //如果没有数据传进来,则这个线程则会阻塞} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}}
}
4.修改QQView.java中的验证用户是否合法语句
4.实现服务器端的登录部分
1.qqserver/service/QQServer.java
package qqserver.service;import qqcommon.Message;
import qqcommon.MessageType;
import qqcommon.User;import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;/*** @author 孙显圣* @version 1.0* 这是服务器,监听9999,等待客户端的连接并且保持通信*/
public class QQServer {private ServerSocket ss = null;public QQServer() {System.out.println("服务端在9999端口监听。。。");try {ss = new ServerSocket(9999); //开一个9999端口监听User对象} catch (IOException e) {throw new RuntimeException(e);}//由于可能会有很多的客户端发送信息,所以要使用循环监听,并且返回不同的sockettry {while (true) {//每次有用户连接都获取socketSocket socket = ss.accept();//读取客户端的User对象ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());User o  = (User) objectInputStream.readObject();//创建一个Message用于回复客户端Message message = new Message();//输出流ObjectOutputStream objectOutputStream = null;//对其进行验证,先写死if (o.getUserId().equals("100") && o.getPasswd().equals("123456")) {message.setMesType(MessageType.MESSAGE_LOGIN_SUCCEED);//获取输出流回复客户端objectOutputStream = new ObjectOutputStream(socket.getOutputStream());objectOutputStream.writeObject(message);//回复完客户端之后,需要创建一个线程,用来管理socket用来保持与客户端的通信ServerConnectClientThread serverConnectClientThread = new ServerConnectClientThread(socket, o.getUserId());serverConnectClientThread.start();//使用集合来管理线程ManageClientThreads.addClientThread(o.getUserId(), serverConnectClientThread);}else {//如果登录失败,就不能启动线程,将失败的消息返回给客户端则关闭socketmessage.setMesType(MessageType.MESSAGE_LOGIN_FAIL);objectOutputStream.writeObject(message);socket.close();}}} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);} finally {try {//如果最终退出了循环,说明不再需要服务器端监听,所以,关闭ServerSocketss.close();} catch (IOException e) {throw new RuntimeException(e);}}}
}
2.qqserver/service/ServerConnectClientThread.java
package qqserver.service;import qqcommon.Message;import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;/*** @author 孙显圣* @version 1.0* 该类对应的一个对象和某个客户端保持连接,*/
public class ServerConnectClientThread extends Thread{//管理一个socket,和对应的用户idprivate Socket socket;private String userId;public ServerConnectClientThread(Socket socket, String userId) {this.socket = socket;this.userId = userId;}//保持这个socket的运行@Overridepublic void run() {while (true) {System.out.println("服务端和客户端保持通信,读取数据。。。");try {//读取数据ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());Message o = (Message) objectInputStream.readObject(); //由于之前已经接受过User对象了,现在就是接受的Message对象} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}}
}
5.登录阶段运行调试过程
1.第一次运行,报错!(用户名密码正确时)

Connect reset。。。。

解决方法
  1. 在两个序列化的类添加这行代码:private static final long serialVersionUID = 1L;
  2. 修改之后,密码正确的时候可以正常显示
2.第二次运行,报错!(用户名密码不正确时)

image-20240112170539595

原因

image-20240112170627654

在执行else语句时,由于没有运行if,所以是空的

解决方法

image-20240112170834454

由于if和else都会用到,所以提出来在外边初始化

成功运行image-20240112170946821

6.实现多个合法用户可以登录
qqserver/service/QQServer.java更新
  1. 添加以下内容:

        //创建一个集合,存放多个用户,如果是这些用户登录,就认为是合法的//可以使用ConcurrentHashMap,这样就避免了线程安全问题,HashMap线程不安全的private static ConcurrentHashMap<String, User> vaildUsers = new ConcurrentHashMap<>();//使用静态代码块初始化static {vaildUsers.put("100", new User("100", "123456"));vaildUsers.put("200", new User("200", "123456"));vaildUsers.put("300", new User("300", "123456"));vaildUsers.put("400", new User("400", "123456"));}//验证用户是否有效的方法private boolean checkUser(User user) {String userId = user.getUserId(); //获取键String passwd = user.getPasswd(); //获取密码//过关斩将//首先查找键是否存在if (!vaildUsers.containsKey(userId)) {return false;}if (!vaildUsers.get(userId).getPasswd().equals(passwd)) {return false;}return true;}
    
  2. 修改验证逻辑image-20240112195818402

4.功能实现——拉取在线用户

1.功能完成
1.qqcommon/MessageType.java更新
    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"; //客户端请求退出
2.qqclient/service/ClientConnectServerThread.java更新
package qqclient.service;import qqcommon.Message;import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.Socket;/*** @author 孙显圣* @version 1.0* 这个线程持有socket*/
public class ClientConnectServerThread extends Thread {private Message message; //存放信息private Socket socket;public static Boolean STATE = false; //子线程任务完成状态,用于线程同步//该构造器可以接受一个Socket对象public ClientConnectServerThread(Socket socket) {this.socket = socket;}//更方便的得到Socketpublic Socket getSocket() {return socket;}//刷新子线程状态public static void flushState() {STATE = false;}//因为线程需要在后台一直保持和服务器的通信,因此使用while循环@Overridepublic void run() {while (true) {System.out.println("客户端线程,等待读取从服务器端发送的信息");try {//获取该线程socket的对象输入流ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());//读取信息message = (Message) objectInputStream.readObject(); //如果没有数据传进来,则这个线程则会阻塞switch (message.getMesType()) {case "3": //普通信息包break;case "5": //返回在线用户列表System.out.println(message.getContent());break;}STATE = true; //更新状态} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}}
}
3.qqclient/service/UserClientService.java更新
    //向服务器端发送请求在线用户的数据包public void onlineFriendList(String userId) throws IOException, ClassNotFoundException, InterruptedException {//获取一个消息包Message message = new Message();//设置参数message.setMesType(MessageType.MESSAGE_GET_ONLINE_FRIEND);//获取当前用户名对应的线程ClientConnectServerThread currentThread = ManageClientConnectServerThread.getClientConnectServerThread(userId);//获取线程中的socketSocket socket = currentThread.getSocket();//获取对象输出流ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//输出对象objectOutputStream.writeObject(message);while (!ClientConnectServerThread.STATE); //等待子线程完成ClientConnectServerThread.flushState(); //刷新状态}
4.view/QQView.java更新

image-20240113101956600

5.qqserver/service/ManageClientThreads.java更新
添加方法
    //获取线程集合public static HashMap<String, ServerConnectClientThread> getHm() {return hm;}
6.qqserver/service/QQServer.java更新
添加方法
    //遍历当前用户列表并发送到前端public static void getCurrentOnlineFriendList(Socket socket) throws IOException {//获取当前用户列表HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();//遍历并保存到数据包中Message message = new Message(); //创建一个数据包//设置数据类型message.setMesType(MessageType.MESSAGE_RET_ONLINE_FRIEND); //类型为返回在线用户列表//记录返回的内容StringBuilder res = new StringBuilder();//获取所有的key,使用迭代器遍历Set<String> strings = hm.keySet();Iterator<String> iterator1 = strings.iterator();int i = 0; //统计用户个数while (iterator1.hasNext()) {String next = iterator1.next();res.append("用户" + (++i) + ": ").append(next).append(" "); //拼接}//将结果放到数据包中message.setContent(res.toString());//根据目前的socket来发送数据ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());objectOutputStream.writeObject(message);}
7.qqserver/service/ServerConnectClientThread.java更新
try语句更新
                //读取数据ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());Message o = (Message) objectInputStream.readObject(); //由于之前已经接受过User对象了,现在就是接受的Message对象//根据读到的信息类型进行处理switch (o.getMesType()) {case "3": //普通信息包break;case "4": //返回当前在线用户列表QQServer.getCurrentOnlineFriendList(socket); //将目前的socket给他break;case "6": //客户端请求退出break;}
2.调试阶段
1.代码冗余
  1. 我最开始自己实现时,获取服务器端的socket是在线程数组中通过客户端传过来的姓名来获取的,后来发现没这么麻烦
  2. 服务器端的一个线程就对应一个通道的socket,并且在不断读取,如果读取到了,则此时的线程实例中的属性socket,就应该是与发送信息的客户端连通的那个socket,直接使用就可以了
2.线程同步问题

image-20240113103809616

  1. 我在拉取在线用户时,在QQ的前端界面调取一个方法,来向服务器端发送Message来请求获取在线用户。然后服务器端发送信息给客户端,此时的客户端是子线程在接收数据,而主线程运行前端页面
  2. 由于主线程只是发送了个消息就直接退出case进行下一次循环,而子线程还要根据信息处理并返回,所以一定比主线程慢,所以我在子线程里面加了一个布尔型的状态常量,并且设置了一个方法可以刷新状态,这样在主线程调用的方法中,可以使用一个while循环持续等待,直到子线程输出数据,然后再刷新状态

5.功能实现——无异常退出系统

image-20240113104920189

1.功能完成
1.qqcommon/MessageType.java更新
package qqcommon;/*** @author 孙显圣* @version 1.0*/
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_SERVICE_EXIT_SUCCESS = "7"; //服务器端退出成功
}
2.qqclient/service/ClientConnectServerThread.java更新
try语句更新
                //获取该线程socket的对象输入流ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());//读取信息message = (Message) objectInputStream.readObject(); //如果没有数据传进来,则这个线程则会阻塞switch (message.getMesType()) {case "3": //普通信息包break;case "5": //返回在线用户列表System.out.println(message.getContent());break;case "7": //服务端退出成功new UserClientService().exitAllThreads(socket, objectInputStream); //关闭资源以及退出主线程loop = false; //退出线程循环break;}STATE = true; //更新状态
3.qqclient/service/UserClientService.java更新
添加三个方法
    //向客户端发送信数据包的方法public void sendMessageToService(String userId, Message message) throws IOException {//获取当前线程ClientConnectServerThread clientConnectServerThread = ManageClientConnectServerThread.getClientConnectServerThread(userId);//获取socketSocket socket = clientConnectServerThread.getSocket();//创建输出流ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());//发送信息objectOutputStream.writeObject(message);}//向客户端发送请求退出的信息public void requestExit(String userId) throws IOException {//创建一个MessageMessage message = new Message();message.setMesType(MessageType.MESSAGE_CLIENT_EXIT);message.setSender(userId); //告诉服务器端发送者是谁,这样可以清除集合中的线程//发送数据包sendMessageToService(userId, message);}//退出子线程以及主线程public void exitAllThreads(Socket socket, ObjectInputStream objectInputStream) throws IOException {objectInputStream.close();socket.close();System.exit(0);}
4.view/QQView.java更新

image-20240113151346682

5.qqserver/service/QQServer.java更新
添加两个方法
    //服务器端发送给客户端数据包的方法public static void sendToClientMessage(Socket socket, Message message) throws IOException {//获取输出流ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());objectOutputStream.writeObject(message);}//服务器端,返回一个退出成功的数据包然后关闭socketpublic static void ServiceExit(Socket socket, ObjectInputStream objectInputStream) throws IOException {//创建一个数据包Message message = new Message();//放入数据message.setMesType(MessageType.MESSAGE_SERVICE_EXIT_SUCCESS); //服务器端退出成功//发送sendToClientMessage(socket, message);objectInputStream.close();socket.close();}
6.qqserver/service/ManageClientThreads.java更新
添加方法
    //根据userId删除public static void deleteByUserId(String userId) {hm.remove(userId);}
7.qqserver/service/ServerConnectClientThread.java更新
    //保持这个socket的运行private boolean loop = true;@Overridepublic void run() {while (loop) {System.out.println("服务端和客户端" + userId + "线程保持通信,读取数据。。。");try {//读取数据ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());Message o = (Message) objectInputStream.readObject(); //由于之前已经接受过User对象了,现在就是接受的Message对象//根据读到的信息类型进行处理switch (o.getMesType()) {case "3": //普通信息包break;case "4": //返回当前在线用户列表QQServer.getCurrentOnlineFriendList(socket); //将目前的socket给他break;case "6": //客户端请求退出ManageClientThreads.deleteByUserId(o.getSender()); //清除列表元素QQServer.ServiceExit(socket, objectInputStream);//关闭流和套接字loop = false;break;}} catch (IOException e) {throw new RuntimeException(e);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}}
2.调试阶段
1.出现空指针异常
  1. 我第一次在关闭客户端的时候,在服务器端出现了空指针异常
  2. 原因:我在处理关闭服务器端的时候只是关闭了流和套接字,并没有关闭run方法的循环,导致子线程继续在读,但是由于套接字已经关闭,读的时候还要使用它获取流,所以出现异常
2.数据未同步
  1. 修改完异常之后,可以正常退出,但是我在测试拉取在线用户时出现了异常
  2. 原因:客户端已经退出,但是服务器端的线程集合中的元素并没有清除,所以导致了异常
3.安全性提升
  1. 原来的的退出系统逻辑就是客户端向服务器端发送退出的请求,然后服务器端收到请求就直接退出
  2. 这样是不安全的,因为客户端的主线程向服务器端发送完请求之后就直接退出,但是有个问题,如果服务器端接受到信息的速度慢了一点,导致客 户端先关闭了socket,那么服务器端在使用socket的时候就会报异常
  3. 我的解决方案:让客户端通知服务器端请求关闭连接的时候,在服务器的socket关闭之前向客户端发送一条消息,就是服务器端关闭成功,当客户端接收到这个消息的时候再退出

6.功能实现——私聊功能

1.功能完成
1.qqclient/service/ClientConnectServerThread.java更新

image-20240113165228777

2.qqclient/service/UserClientService.java更新
添加方法
    //私聊消息public void privateMessages(String sender) throws IOException {//展示所有用户之后//获取用户名称System.out.print("请输入你要聊天的用户名称:");String getter = new Scanner(System.in).next();//获取聊天的内容System.out.print("请输入聊天的内容");String content = new Scanner(System.in).nextLine();//创建一个数据包Message message = new Message();message.setMesType(MessageType.MESSAGE_COMM_MES); //普通消息message.setContent(content);message.setSender(sender);message.setGetter(getter);//发送到服务器端sendMessageToService(sender, message);}//读取私聊消息public void readPrivateMessage(Message message) {String sender = message.getSender();String content = message.getContent();System.out.println("\n========== " + sender + "对你说" + " ==========");System.out.println(content);}
3.view/QQView.java更新

image-20240113165447314

4.qqserver/service/QQServer.java更新
添加方法
    //转发消息public static void forwordMessage(Message message) throws IOException {//获取信息String content = message.getContent();String sender = message.getSender();String getter = message.getGetter();//根据姓名获取线程ServerConnectClientThread sendThread = ManageClientThreads.getServerConnectClientThread(getter);//发送包sendToClientMessage(sendThread.getSocket(), message);}
5.qqserver/service/ServerConnectClientThread.java更新

image-20240113165742602

2.调试阶段
并未发现错误

7.功能实现——群发功能

1.功能完成
1.qqcommon/MessageType.java更新
package qqcommon;/*** @author 孙显圣* @version 1.0*/
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_SERVICE_EXIT_SUCCESS = "7"; //服务器端退出成功String MESSAGE_SEND_ALL_USER = "8"; //群发消息
}
2.qqclient/service/ClientConnectServerThread.java更新

image-20240113184829218

3.qqclient/service/UserClientServer.java更新
添加方法
    //群发消息public void sendToAllUser(String userId) throws IOException {System.out.println("==========请输入你要发送的内容==========");Scanner scanner = new Scanner(System.in);String content = scanner.nextLine();//创建一个数据包Message message = new Message();message.setMesType(MessageType.MESSAGE_SEND_ALL_USER);message.setContent(content);message.setSender(userId);//发送数据包sendMessageToService(userId, message);}//读取群发消息public void readAllSendMessage(Message message) {//获取信息String sender = message.getSender();String content = message.getContent();System.out.println("\n========== " + sender +" 的群发消息==========");System.out.println(content);}
4.qqserver/service/QQServer.java更新

image-20240113185243937

5.qqserver/service/QQServer.java更新
添加方法
    //群发消息public static void sendToAllUser(Message message, String userId) throws IOException {//遍历在线用户集合,发送消息HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();Collection<ServerConnectClientThread> threads = hm.values();for (ServerConnectClientThread thread : threads) {if (hm.get(userId) == thread) { //不用发送给本用户continue;}//发送包sendToClientMessage(thread.getSocket(), message);}}
6.qqserver/service/ServerConnectClientThread.java更新

image-20240113185445558

2.调试阶段
未发现错误

8.功能实现——发文件

1.功能完成
1.qqcommon/MessageType.java更新
package qqcommon;/*** @author 孙显圣* @version 1.0*/
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_SERVICE_EXIT_SUCCESS = "7"; //服务器端退出成功String MESSAGE_SEND_ALL_USER = "8"; //群发消息String MESSAGE_SEND_FILE = "9"; //发送文件
}
2.qqcommon/Message.java更新
package qqcommon;import java.io.Serializable;/*** @author 孙显圣* @version 1.0* 表示客户端和服务器端通讯时的消息对象*/
public class Message implements Serializable { //也需要进行序列化private String sender; //发送者private String getter; //接受者private String content; //消息内容private String sendTime; //发送时间private String mesType; //消息类型,在接口中定义已知的消息类型private String path; //记录路径private byte[] bytes; //存储文件private int length; //记录长度public int getLength() {return length;}public void setLength(int length) {this.length = length;}private static final long serialVersionUID = 1L;public byte[] getBytes() {return bytes;}public void setBytes(byte[] bytes) {this.bytes = bytes;}public String getPath() {return path;}public void setPath(String path) {this.path = path;}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;}
}
3.qqclient/service/ClientConnectServerThread.java更新

image-20240113210046856

4.qqclient/service/UserClientServer.java更新
添加方法
    //发送文件public void sendFile(String setter) throws IOException {//获取用户名称System.out.print("请输入要发送文件的用户名称:");Scanner scanner = new Scanner(System.in);String getter = scanner.next();//获取本地文件路径System.out.print("请输入本地文件路径:");String path1 = scanner.next();//获取对方文件路径System.out.print("请输入对方文件路径:");String path2 = scanner.next();//读取本地文件BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(path1));//设置缓冲byte[] bytes = new byte[1024 * 10];//记录长度int len = 0;while ((len = inputStream.read(bytes)) != -1) {Message message = new Message();//创建一个数据包message.setSender(setter);message.setGetter(getter);message.setMesType(MessageType.MESSAGE_SEND_FILE);message.setPath(path2);message.setBytes(bytes);message.setLength(len);//发送sendMessageToService(setter, message);}//关闭inputStream.close();//最后发送一个普通信息包,通知用户Message message = new Message();message.setMesType(MessageType.MESSAGE_COMM_MES);message.setContent("用户" + setter + "向你发送了一个文件,路径为" + path2);message.setGetter(getter);message.setSender(setter);sendMessageToService(setter, message);}//读取文件public void readFile(Message message) throws IOException {String sender = message.getSender();String path = message.getPath();byte[] bytes = message.getBytes();int length = message.getLength();//写入到本地路径BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(new FileOutputStream(path, true));bufferedOutputStream.write(bytes,0, length);//关闭bufferedOutputStream.close();}
5.view/QQView.java更新

image-20240113210634198

6.qqserver/service/ServerConnectClientThread.java更新

image-20240113210743722

2.调试阶段
1.传输文件大小膨胀
  1. 一开始由于Message要传输的内容是String类型的,所以我就将文件分成很多byte[1024*10]的部分进行传输并且转换成了String
  2. 但是这个导致了文件变大了很多
  3. 解决方法:在Message中添加属性,来保存byte类型的数组和读取到的长度,然后再将其放到包中传输,在读取的时候以byte数组的形式读取就行

9.功能实现——服务器端推送新闻

1.功能完成
1.qqserver/service/SendAllThread.java
package qqserver.service;import qqcommon.Message;
import qqcommon.MessageType;import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.Scanner;/*** @author 孙显圣* @version 1.0* 用来向客户端推送新闻*/
public class SendAllThread extends Thread{@Overridepublic void run() {while (true) { //循环获取要推送的信息System.out.println("请输入要推送的消息");Scanner scanner = new Scanner(System.in);String content = scanner.next();//获取MessageMessage message = new Message();message.setMesType(MessageType.MESSAGE_COMM_MES);message.setSender("系统");message.setContent(content);//遍历所有用户并群发HashMap<String, ServerConnectClientThread> hm = ManageClientThreads.getHm();Collection<ServerConnectClientThread> values = hm.values(); //所有的socketfor (ServerConnectClientThread Thread : values) {//获取线程的socket,从而获取对象输出流try {ObjectOutputStream objectOutputStream = new ObjectOutputStream(Thread.getSocket().getOutputStream());//输出普通信息包objectOutputStream.writeObject(message);System.out.println("服务器端推送消息:" + content);} catch (IOException e) {throw new RuntimeException(e);}}}}
}
2.qqserver/service/ServerConnectClientThread.java更新

image-20240113225413422

2.调试阶段
1.子线程群发问题
  1. 我最初是把Message的内容写好,然后调用群发方法发送给各个用户
  2. 但是我只开了一个用户,然后一直测试发现群发不了,但是后来想起来,我的那个群发方法,设置的是不发送给当前的用户,真是醉了
  3. 解决方案:自己遍历所有用户,群发消息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_1027121.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据库管理开发工具Navicat for MySQL Mac版下载

Navicat for MySQL&#xff08;Mac版&#xff09;是一款强大的数据库管理开发工具&#xff0c;专为MySQL设计。它提供直观的用户界面&#xff0c;支持数据建模、查询构建、数据传输等功能&#xff0c;帮助用户轻松管理数据库。其特点包括高效的数据处理能力、安全的数据传输机制…

mysql80-DBA数据库学习1-数据库安装

掌握能力 核心技能 核心技能 mysql部署 官网地址www.mysql.com 或者www.oracle.com https://dev.mysql.com/downloads/repo/yum/ Install the RPM you downloaded for your system, for example: yum install mysql80-community-release-{platform}-{version-number}.noarch…

Flutter Provider 使用指南详解

介绍 在Flutter应用程序开发中&#xff0c;状态管理是一个至关重要的方面。随着应用程序的复杂性增加&#xff0c;有效地管理和共享状态变得至关重要。Flutter Provider是一个流行的状态管理解决方案&#xff0c;它提供了一种简单而强大的方式来管理Flutter应用程序中的状态。…

Web Components初探

组件化&#xff0c;标签语义化&#xff0c;是前端发展的趋势。现在流行的组件化框架有React、Vue等&#xff0c;标签语义化在H5中添加的article、dialog等。 Web Components 就是类似的一套技术&#xff0c;允许您创建可重用的定制元素&#xff0c;并且在您的web应用中使用它们…

2024河北石家庄矿业矿山展览会|河北智慧矿山展会|河北矿博会

2024中国&#xff08;石家庄&#xff09;国际矿业博览会      时间&#xff1a;2024年7月4-6日 地点&#xff1a;石家庄国际会展中心.正定      随着全球经济的持续增长和矿产资源需求的不断攀升&#xff0c;矿业行业正迎来前所未有的发展机遇。作为矿业领域的盛会&…

代码随想录算法训练营第day60|84.柱状图中最大的矩形

84.柱状图中最大的矩形 力扣题目链接(opens new window) 给定 n 个非负整数&#xff0c;用来表示柱状图中各个柱子的高度。每个柱子彼此相邻&#xff0c;且宽度为 1 。 求在该柱状图中&#xff0c;能够勾勒出来的矩形的最大面积。 思路&#xff1a; 为什么这么说呢&#xff…

浏览器工作原理与实践--作用域链和闭包 :代码中出现相同的变量,JavaScript引擎是如何选择的

在上一篇文章中我们讲到了什么是作用域&#xff0c;以及ES6是如何通过变量环境和词法环境来同时支持变量提升和块级作用域&#xff0c;在最后我们也提到了如何通过词法环境和变量环境来查找变量&#xff0c;这其中就涉及到作用域链的概念。 理解作用域链是理解闭包的基础&#…

如何在Linux系统使用Docker本地部署Halo网站并实现无公网IP远程访问

最近&#xff0c;我发现了一个超级强大的人工智能学习网站。它以通俗易懂的方式呈现复杂的概念&#xff0c;而且内容风趣幽默。我觉得它对大家可能会有所帮助&#xff0c;所以我在此分享。点击这里跳转到网站。 文章目录 1. Docker部署Halo1.1 检查Docker版本如果未安装Docker可…

GEC6818开机自动加载驱动与更改开发板的RTC时钟

GEC6818开机自动加载驱动与更改开发板的RTC时钟 本文主要涉及&#xff1a; 1.GEC6818开机自动加载驱动 2.更改开发板的RTC时钟 文章目录 GEC6818开机自动加载驱动与更改开发板的RTC时钟一、开机自动加载驱动或运行程序**STEP1&#xff1a;** 使用vi打开文件profile.命令如下**S…

Gitlab的流水线任务【实现每小时自动测试 dev分支的更新】

背景 在现代软件开发实践中&#xff0c;持续集成&#xff08;Continuous Integration, CI&#xff09;是确保代码质量和快速响应软件缺陷的关键策略。GitLab 提供了强大的 CI/CD 功能&#xff0c;允许开发者自动化测试和部署流程。本文将介绍如何设置 GitLab 流水线计划任务&a…

GPT5都要来了,现在登录就送!!!

据《商业内幕》报道&#xff0c;OpenAI计划在未来几个月内推出ChatGPT的更强大版本。 据两位知情人士透露&#xff0c;这款名为GPT-5的新型人工智能模型预计将在今年夏天发布。在发布之前&#xff0c;一些企业据称已经尝试了该工具的演示版本&#xff0c;以测试其升级后的能力。…

Windows系统安装Elasticsearch结合内网穿透实现远程团队数据共享

文章目录 系统环境1. Windows 安装Elasticsearch2. 本地访问Elasticsearch3. Windows 安装 Cpolar4. 创建Elasticsearch公网访问地址5. 远程访问Elasticsearch6. 设置固定二级子域名 Elasticsearch是一个基于Lucene库的分布式搜索和分析引擎&#xff0c;它提供了一个分布式、多…

php 快速入门(七)

一、操作数据库 1.1 操作MySQL的步骤 第一步&#xff1a;登录MySQL服务器 第二步&#xff1a;选择当前数据库 第三步&#xff1a;设置请求数据的字符集 第四步&#xff1a;执行SQL语句 1.2 连接MySQL 函数1&#xff1a;mysql_connect() 功能&#xff1a;连接&#xff08;登录…

权限提升-Win系统权限提升篇AD内网域控NetLogonADCSPACKDCCVE漏洞

知识点 1、WIN-域内用户到AD域控-CVE-2014-6324 2、WIN-域内用户到AD域控-CVE-2020-1472 3、WIN-域内用户到AD域控-CVE-2021-42287 4、WIN-域内用户到AD域控-CVE-2022-26923 章节点&#xff1a; 1、Web权限提升及转移 2、系统权限提升及转移 3、宿主权限提升及转移 4、域控权…

常见技术难点及方案

1. 分布式锁 1.1 难点 1.1.1 锁延期 同一时间内不允许多个客户端同时获得锁&#xff1b; 1.1.2 防止死锁 需要确保在任何故障场景下&#xff0c;都不会出现死锁&#xff1b; 1.2.3 可重入 特殊的锁机制&#xff0c;它允许同一个线程多次获取同一个锁而不会被阻塞。 1.2…

新火种AI|大厂围剿,“长文本”成不了Kimi的护城河

作者&#xff1a;一号 编辑&#xff1a;美美 长文本之后&#xff0c;Kimi能找到新的“护城河”吗&#xff1f; 过去的一周&#xff0c;由AI技术天才杨植麟的大模型初创企业月之暗面及其产品Kimi所带来的连锁反应&#xff0c;从社交媒体一路冲向了A股&#xff0c;带动了一批“…

【Java程序设计】【C00392】基于(JavaWeb)Springboot的校园生活服务平台(有论文)

基于&#xff08;JavaWeb&#xff09;Springboot的校园生活服务平台&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过…

LeetCode_1.两数之和

一、题目描述 二、方法 1.方法1&#xff08;暴力枚举法&#xff09; 利用两个for循环&#xff0c;对数组进行逐一的遍历&#xff0c;直到找到两个数的和为目标值时返回这两个数的下标。以下为c实现的完整代码。 # include<iostream> using namespace std; #include<…

大数据开发扩展shell--尚硅谷shell笔记

大数据开发扩展shell 学习目标 1 熟悉shell脚本的原理和使用 2 熟悉shell的编程语法 第一节 Shell概述 1&#xff09;Linux提供的Shell解析器有&#xff1a; 查看系统中可用的 shell [atguiguhadoop101 ~]$ cat /etc/shells /bin/sh/bin/bash/sbin/nologin/bin/dash/bin/t…

javaWeb项目-火车票订票信息系统功能介绍

项目关键技术 开发工具&#xff1a;IDEA 、Eclipse 编程语言: Java 数据库: MySQL5.7 框架&#xff1a;ssm、Springboot 前端&#xff1a;Vue、ElementUI 关键技术&#xff1a;springboot、SSM、vue、MYSQL、MAVEN 数据库工具&#xff1a;Navicat、SQLyog 1、Spring Boot框架 …