项目总结6

1. 使用JavaFX的Java项目中,继承Application后,必须要有start方法,此时无需main函数也能运行start方法中的内容。

并且会显示stage.show(),但是如果写了main方法,那么如果不写launch将不会运行start(),在如下模板的情况下。

 2. 用集合存储id和socket表示在线状态状态,

这是集合的方法类

package server;

import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

public class ClientSocketCache {
    private ConcurrentHashMap<String, Socket> clientSocketMap = new ConcurrentHashMap<>();

    // 添加客户端Socket到缓存
    public void addClientSocket(String clientId, Socket socket) {
        clientSocketMap.put(clientId, socket);
    }

    // 从缓存中移除客户端Socket
    public void removeClientSocket(String clientId) {
        clientSocketMap.remove(clientId);
    }

    // 获取客户端Socket
    public Socket getClientSocket(String clientId) {
        return clientSocketMap.get(clientId);
    }

    // 检查客户端是否在线
    public boolean isClientOnline(String clientId) {
        return clientSocketMap.containsKey(clientId);
    }

    public void Ergodic() {
        for (String clientId : clientSocketMap.keySet()) {
            Socket socket = clientSocketMap.get(clientId);
            System.out.println("Client ID: " + clientId + ", Socket: " + socket);
        }
    }
}

在主函数中创建,并开辟线程用于遍历,检测是否满足要求

package server;

import QQEmail.VerificationCodeManager;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Server {
    public static List<Socket> socketList = Collections.synchronizedList(new ArrayList<>());
    public static VerificationCodeManager manager = new VerificationCodeManager();  // 缓存暂存验证码(静态)

    //存储在线状态相关的两条代码
    public static ClientSocketCache clientSocketCache = new ClientSocketCache();
    public static void main(String[] args) throws IOException, ClassNotFoundException {

        new Thread(new TestThread()).start();//测试是否能正确存储登录信息

        ServerSocket ss = new ServerSocket(6666);
        while (true) {
            // 此行代码会阻塞,将一直等待别人的连接
            Socket s = ss.accept();
            socketList.add(s);
            // 每当客户端连接成功后启动一个ServerThread线程为该客户端服务
            System.out.println("客户端连接" + s);

            new Thread(new ServerThread(s)).start();
        }

    }
}

用于遍历、监听的线程类

package server;

import java.util.concurrent.TimeUnit;

public class TestThread implements Runnable {

    public void run() {
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("调用了一次");//(Thread.currentThread().getName() + " is running");
            Server.clientSocketCache.Ergodic();
        }
    }

}

在长线程开始时调用

Server.clientSocketCache.addClientSocket(String.valueOf(clientId), socket);

每个可能结束长线程的地方调用

Server.clientSocketCache.removeClientSocket(String.valueOf(clientId));

3. 错误原因记录,当方法调用的变量命名与类的属性相同时(举例都命名为为attribute),默认为方法调用的attribute,而this.attribute才是类中的属性。当某个属性该有的 “.方法” 消失不见时,很可能是出现了重名,可更改命名或加this.前缀

4. 当IDEA处于默认主题时,蓝色字体代表类的内部属性,表示一种调用的状态,而方法内定义的、已及传来的数据显示白色,代表局部的状态

5. 实时更新Server具体操作

1. 首先要将 id 和 oos 存集合,id 表示在线,存oos而不存socket是因为socket只能包装一次,多次包装的ObjectOutputStream和ObjectInputStream反序列化时会报错

这是一个集合类的模板

package server;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.concurrent.ConcurrentHashMap;

public class ClientSocketCache {
    private ConcurrentHashMap<String, ObjectOutputStream> clientOosMap = new ConcurrentHashMap<>();


    // 检查客户端是否在线
    public boolean isClientOnline(String clientId) {
        return clientOosMap.containsKey(clientId);
    }

    // 添加客户端ObjectOutputStream到缓存
    public void addClientOos(String clientId, ObjectOutputStream oos) {
        clientOosMap.put(clientId, oos);
    }

    // 从缓存中移除客户端ObjectOutputStream
    public void removeClientOos(String clientId) {
        clientOosMap.remove(clientId);
    }


    // 获取客户端ObjectOutputStream
    public ObjectOutputStream getClientOos(String clientId) {
        return clientOosMap.get(clientId);
    }

    // 遍历所有客户端信息
    public void ergodic() {
        for (String clientId : clientSocketMap.keySet()) {
            ObjectOutputStream oos = clientOosMap.get(clientId);
            System.out.println("Client ID: " + clientId + ", "ObjectOutputStream: " + oos);
        }
    }

其中写在前面的定义相当于一个索引,后面则是关联存储的内容

2. 在Server定义一个静态的集合类,在进入长连接时将 id 和 oos 存集合(不重要)

public static ClientSocketCache clientSocketCache = new ClientSocketCache();
            Server.clientSocketCache.addClientOos(String.valueOf(clientId), oos);

3. 这是一个实时更新思路的案例

首先我在服务端需要进行实时更新的地方(type)插入一串代码,这串代码的作用就是读取一些信息,明白服务端将要用这些信息干什么,然后进行一些JDBC之类的操作,额外打包一个Message发给相关的客户端,提醒客户端进行更新,接下来是一个示例(更新学生班级信息)。

    1. 发送消息过来的长连接逻辑不变,依旧是处理完后发送给客户端进行UI更新,中间插入的StudentCoursesLoading.RealTime(message),即将信息带到类中进行处理。

if (message.getType() == ThreadLibrary.ModifyClassInformation) {
        message = ModifyClassInformation.Course(message);
        StudentCoursesLoading.RealTime(message);//这里是插入的代码
        oos.writeObject(message);
}

 2. 取走进行了更新的信息,通过“外键”找出所有相关的 id (受影响的用户)

AddCourseMessage addCourseMessage = (AddCourseMessage) message.getObject();

// 1. 加载JDBC驱动
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 建立连接
Connection conn = DriverManager.getConnection(DB_URL, USER, PASS);

// 3. SQL查询,从classindex表中找到所有与course_id匹配的id
String sqlSelectIds = "SELECT id FROM classindex WHERE course_id = ?";
PreparedStatement  pstmt = conn.prepareStatement(sqlSelectIds);
pstmt.setInt(1, addCourseMessage.course_id);
ResultSet rs = pstmt.executeQuery();

 3. 找到 相关的 id 后。每次找到一个 id 都检测是否在线(顺手而已,如果oos不像ois一样会阻塞的话可能没什么关系),之后根据需要打包信息,根据存储的oos不同丢给相应的客户端,客户端接收到信息自动完成实时更新。

// 4. 遍历结果集
while (rs.next()) {
    int id = rs.getInt("id");

    if (Server.clientSocketCache.isClientOnline(String.valueOf(id))){
    Message realTimeMessage = CoursesLoading.Student(new Message(new CourseStudentMessage(id), ThreadLibrary.CoursesLoadingStudent));
    ObjectOutputStream oos = Server.clientSocketCache.getClientOos(String.valueOf(id));
    oos.writeObject(realTimeMessage);
    }
}

6. 该实时更新要做的一些优化处理:

       1. 因为集合中的 clientId 是索引,要具有唯一性,因此要限制登录不能登多个 id 相同的账号,在登陆前要做id是否已经在集合中的操作。

       2. 要统一更新模板,最好都是像案例一样,实时更新只需要做简单的外键操作,找到数据后丢给之前的.....Message 和DataBaswClass处理,然后打包送到相应的客户端。

       3. 在长线程周围搭建集合用于存储id和相应的oos时,需要注意在客户端断连时清除,主要在抛异常和主动退出while的部分清除。

       4. 可设置一个线程类,设置20秒左右的休眠,观察客户端的各种操作后,数据能不能正确的从服务端清除。

7. 如果用 int 作为主键,那么必须用Integer

在集合框架(如 ConcurrentHashMap)中,你不能直接使用原始数据类型作为键或值,因为集合框架的泛型要求对象必须是 Object 类型或者是 Object 的子类。

8. 判断是否需要实时更新课程的两种思路

*(模型基础)学生端每次点击一个课程都要与服务器进行交互,然后再渲染出课程页面。(注:最后的结果都是渲染StaticFXML.CoursePage为新的状态)

1. 在客户端设置静态变量(如Local_Data.course_id)记录登录的页面,有页面则记录页面的course_id,没有页面则记录course_id = 0,是否实时更新只需判断(IO两边的)course_id是否相等。

2. 在服务端再设置一个集合,存储 每次 相应 id 渲染交互的 course_id,通过判别用户上次传过来的course_id来判断是否要对它发送更新数据(担忧:可能数据并行,刚好没接到)

9. 根据测试,集合将会只存一个clientId,在有两个clientId相同的情况下,而它的机制是顶替,不是无法插入,也就是替换掉原有的clientId的数据。

所以只能登录一个账号有两种思路,无法登录或者顶号。顶号可能需要开辟一个新线程监听,以及缓存socket等,还有可能因为监听的速度太慢,导致被顶号前做了一些不好的操作。所以选择在每次长连接前(登录前)查找是否有了相应的clientId,有则无法登录,显示账号已登录。(可以优化忘记密码试探有没有clientId已经登录,有则让他退出)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值