基于JAVA17的仿向日葵远程控制软件源码+最新自研完整版

hello大家好,今天给大家带来一款用JDK17写的防向日葵的远程控制软件 源码。 此源码为完整版,适合大作业和毕设。对java感兴趣的还可以学习网络编程思路。本文也带来了系统实现的基本原理。

源码为自己开发的源码, 商用必究!!!

从源码种你可以学习到:

  1. tcp网络编程基础,以及一些优化:"禁用Nagle算法,减少延时" , "启用TCP保活机制" 等等。

  2. 如何使用java进行屏幕录制,然后压缩。

  3. 如何实现一款中转服务器(为了使两个不同局域网的客户端进行远程连接)。

  4. 如何进行鼠标的坐标映射,以及屏幕dpi的处理。

  5. 如何进行键盘的映射,以及tab键无效的处理。

视频演示

www.bilibili.com/video/BV13A...

图片演示

技术栈描述

项目框架

JDK17 + Java Swing界面 + TCP Socket网络

完整游戏源码,我已经整理清楚,移步:

gitcode.com/hadluo2/jav...

系统实现

中转服务器的实现

中转服务器就是为了将客户端A的指令透传到客户端B , 达到两个不同局域网的客户端进行网络交互。首先需要有客户端code对应客户端socket的映射类:

typescript 复制代码
public class TcpClientSessions {

    private static final TcpClientSessions instance = new TcpClientSessions();
    // 存储客户端会话信息  <客户端code设备码  ,  session>
    private final Map<String, ClientSession> clientSessions = new ConcurrentHashMap<>();


    private TcpClientSessions() {
    }

    public static TcpClientSessions getInstance() {
        return instance;
    }

    public void addSession(String code, ClientSession session) {
        clientSessions.put(code, session);
    }

    public ClientSession getSession(String code) {
        return clientSessions.get(code);
    }

    public ClientSession removeSession(TcpClientConnection connection) {
        ClientSession session = null;
        for (Map.Entry<String, ClientSession> entry : clientSessions.entrySet()) {
            if (entry.getValue().getConnection() == connection) {
                session = entry.getValue();
                break;
            }
        }
        if (session != null) {
            clientSessions.remove(session.getCode());
        }
        return session;
    }
}

代码用到的单例, 后面方便使用。ClientSession 就相当于一个客户端socket,后续转发消息都是先通过code找到这个ClientSession ,然后再发送消息。

客户端启动时需要注册,代表自己已经在线,下面是处理客户端注册的逻辑:

ini 复制代码
private void handleRemoteCodeRequest(TcpClientConnection connection, BaseMessage message) {
        try {
            // 生成远程码和密码
            String remoteCode = NetworkUtil.generateRemoteCode();
            String password = NetworkUtil.generatePassword();

            // 创建客户端会话
            ClientSession session = new ClientSession();
            session.setCode(remoteCode);
            session.setPassword(password);
            session.setConnection(connection);
            // 存储会话信息
            TcpClientSessions.getInstance().addSession(remoteCode, session);


            RemoteCodeResponse response = RemoteCodeResponse.success(remoteCode, password);
            BaseMessage responseMessage = new BaseMessage(MessageType.REMOTE_CODE_RESPONSE, response);
            connection.sendMessage(responseMessage);

        } catch (Exception e) {
            e.printStackTrace();
            RemoteCodeResponse response = RemoteCodeResponse.failure("服务器内部错误");
            BaseMessage responseMessage = new BaseMessage(MessageType.REMOTE_CODE_RESPONSE, response);
            connection.sendMessage(responseMessage);
        }
    }

有了这个全局的客户端code对应的socket映射,后续的发送自己的屏幕流数据,等指令都只需要相同的转发处理就可以了。

到此中转服务器核心就是如此。

被控客户端发送自己屏幕实现

客户端当收到发起屏幕的指令后, 需要通过 JavaRobotScreenService 来实现实时循环上传当前屏幕。 所以应该是放到一个线程里面实现:

scss 复制代码
// 一直循环 上传屏幕流数据
        new Thread() {
            @Override
            public void run() {
                while (running) {
                    start0(receiverCode , senderCode);
                    try {
                        Thread.sleep(settingsService.getSettings().getRobotCaptureTime());
                    } catch (InterruptedException e) {
                    }
                }
            }
        }.start();

为了防止cpu飙升,我们通过sleep 来控制上传的间隔,也相当于fps帧。 start0 为核心方法:

ini 复制代码
private void start0(String receiverCode  , String sendCode) {
        try {
            System.out.println("正在共享屏幕");

            // 捕获屏幕截图
            long s = System.currentTimeMillis();
            BufferedImage screenshot = robot.createScreenCapture(screenRect);
            s = System.currentTimeMillis() - s;
            // 将图像转换为JPEG字节数组(包含质量压缩)
            long s1 = System.currentTimeMillis();
            byte[] imageData = imageToJpegBytesWithQuality(screenshot);
            s1 = System.currentTimeMillis() - s1;

            // 构建包含接收者信息的完整数据包
            if (imageData != null && imageData.length > 0) {
                ScreenData screenData = new ScreenData();
                screenData.setReceoverCode(receiverCode);
                screenData.setImageData(imageData);
                screenData.setSendCode(sendCode);
                screenData.setCaptureCost(s);
                screenData.setEncodeCost(s1);
                screenData.setDpiScale(dpiScale);
                screenData.setNetworkStartTime(System.currentTimeMillis());
                RemoteControlClient.getInstance().sendTCP(new BaseMessage(MessageType.SCREEN_DATA, screenData));
            }

        } catch (Exception e) {
            log.error("捕获屏幕数据时发生错误", e);
        }
    }

首先通过robot工具捕获屏幕,然后进行高效压缩,转换成支持socket传输的字节流,进行发送。屏幕数据流首先发送到中转服务器,然后解析到receiverCode,转发到对应的客户端。

客户端接受屏幕流进行展示

展示数据很简单,就是将字节数组反转成 BufferedImage ,然后显示到组件上,一个重要的问题就是需要处理对方屏幕的dpi。 不然会出现显示不完整的问题。

ini 复制代码
 public void showScreenData(ScreenData data) {
        // 在后台线程中处理图像解码,避免阻塞UI线程
        TaskExcuteService.getInstance().getScreenUpdater().execute(() -> {
            BufferedImage decode = null ;
            remoteDpiScale = data.getDpiScale() ;
            try {
                if(data.getImageData() != null ) {
                    // java robot - 优化:直接使用ByteArrayInputStream,避免额外的包装流
                    decode = ImageIO.read(new ByteArrayInputStream(data.getImageData()));
                    // 更新帧大小显示
                    SwingUtilities.invokeLater(() -> {
                        frameSizeLabel.setText("帧大小: " + formatBytes(data.getImageData().length)+",捕获屏幕耗时:"+data.getCaptureCost()+"ms"
                        +",编码耗时:"+data.getEncodeCost()+"ms");
                    });
                }if (decode != null) {
                    // 直接设置远程图像
                    remoteImage = decode;
                    connected = true;
                    // 计算缩放比例
                    Dimension panelSize = desktopPanel.getSize();
                    if (panelSize.width > 0 && panelSize.height > 0) {
                        scaleX = (double) panelSize.width / remoteImage.getWidth();
                        scaleY = (double) panelSize.height / remoteImage.getHeight();

                        // 保持图像比例
                        double scale = Math.min(scaleX, scaleY);
                        scaleX = scaleY = scale;
                    }
                    desktopPanel.repaint();
                }
            } catch (Exception e) {
                log.error("显示屏幕数据失败: {}", e.getMessage());
            }
        });
    }

客户端指令处理的统一处理器

为了代码的简洁性,将所有的指令都统一放到了 TcpMessageHandler 进行处理,这样后续迭代和开发都有保障。

typescript 复制代码
@Override
    public void accept(BaseMessage response) {
        try {
            // 处理不同类型的消息
            switch (response.getMessageType()) {
                case REMOTE_CODE_RESPONSE: // 获取的 设备码回应
                    applyCode((RemoteCodeResponse) response.getData());
                    break;
                case REMOTE_CONNECT_RESPONSE: // 远程身份校验 回应
                    remoteCheck((RemoteConnectResponse) response.getData());
                    break;
                case SHARE_SCREEN: // 需要分享自己的屏幕
                    startScreen(response);
                    break;
                case SCREEN_DATA:  // 收到了对方的屏幕数据
                    showScreenData(response);
                    break;
                case MOUSE_EVENT: // 处理 鼠标事件
                    mouse(response);
                    break;
                case KEYBOARD_EVENT:  // 键盘事件
                    keyboard(response);
                    break;
                case Close_Screen: // 关闭屏幕
                    closeScreen(response);
                    break;
                case SETTINGS: // 设置
                    settings(response);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

系统部署

将源码导入到idea中,这个项目就是一个普通的maven管理的项目, 导入前,请设置好maven的仓库配置。

设置好JDK的环境为17

启动中转服务器

启动客户端

启动客户端需要先设置中转服务器的IP, 否则界面会提示连接失败。

启动main函数

相关推荐
涡能增压发动积38 分钟前
Browser-Use Agent使用初体验
人工智能·后端·python
探索java1 小时前
Spring lookup-method实现原理深度解析
java·后端·spring
lxsy1 小时前
spring-ai-alibaba 之 graph 槽点
java·后端·spring·吐槽·ai-alibaba
码事漫谈1 小时前
深入解析线程同步中WaitForSingleObject的超时问题
后端
码事漫谈1 小时前
C++多线程同步:深入理解互斥量与事件机制
后端
少女孤岛鹿2 小时前
微服务注册中心详解:Eureka vs Nacos,原理与实践 | 一站式掌握服务注册、发现与负载均衡
后端
CodeSaku2 小时前
是设计模式,我们有救了!!!(四、原型模式)
后端
Ray662 小时前
「阅读笔记」零拷贝
后端
二闹2 小时前
什么?你的 SQL 索引可能白加了!?
后端·mysql·性能优化
lichenyang4532 小时前
基于Express+Ejs实现带登录认证的多模块增删改查后台管理系统
后端