java socket实现代理Android App

news2025/1/21 1:04:23

实现逻辑就是转发请求和响应。 

核心代码


    // 启动代理服务器
    private void startProxyServer() {
        new Thread(new ProxyServer()).start();
    }

    // 代理服务器
    static class ProxyServer implements Runnable {
        @Override
        public void run() {
            try {
                // 监听指定的端口
                int port = 8098; //一般使用49152到65535之间的端口
                ServerSocket server = new ServerSocket(port);
                // 当一个ServerSocket关闭并释放其绑定的端口后,操作系统通常会在几分钟内不允许其他Socket再次绑定到该端口。
                // true:操作系统将允许其他Socket立即绑定到刚刚被释放的端口。
                server.setReuseAddress(true);
                // 使用线程池,防止过多线程耗尽资源
                ExecutorService threadPool = Executors.newFixedThreadPool(50);
                while (true) {
                    Socket socket = server.accept(); //会一直阻塞,直到有客户端连接进来
                    // new Thread 只是创建一个类的对象实例而已。而真正创建线程的是start()方法。
                    // 这里并没有直接调用start()方法,所以并没创建新线程,而是交给线程池去执行。
                    threadPool.submit(new ProxyClient(socket));
                }
            } catch (Exception e) {
                Log.e("ProxyServer", e.getMessage(), e);
            }
        }
    }

    // 代理客户端
    static class ProxyClient implements Runnable {
        private final Socket proxySocket;//代理Socket
        private Socket targetSocket = null;//目标Socket

        public ProxyClient(Socket socket) {
            this.proxySocket = socket;
        }

        @Override
        public void run() {
            try {
                //客户端请求的报文
                InputStream req = proxySocket.getInputStream();
                int read;
                int contentLength = 0;//body长度
                String method = null;//请求方法
                String url = null;//请求地址
                String protocol = null;//请求协议
                ByteArrayOutputStream os = new ByteArrayOutputStream();
                ByteArrayOutputStream reqBack = new ByteArrayOutputStream();
                //解析,提取请求报文
                while ((read = req.read()) != -1) {
                    os.write(read);
                    reqBack.write(read);
                    if (read == '\n') {
                        //CONNECT www.xx.com:443/xx/yy HTTP/1.1
                        String line = os.toString("UTF-8");
                        os.reset();//重置,以便再次使用
                        if ("\r\n".equals(line)) {
                            //空行,请求头结束标志
                            break;
                        }
                        StringTokenizer stringTokenizer = new StringTokenizer(line, " ");
                        if (method == null) {
                            //八种请求方法:GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE、CONNECT 方法
                            method = stringTokenizer.nextToken().toLowerCase();//connect
                            url = stringTokenizer.nextToken();//www.xx.com:443/xx/yy
                            protocol = stringTokenizer.nextToken().trim();//HTTP/1.1
                        } else {
                            String key = stringTokenizer.nextToken().toLowerCase();
                            if ("content-length:".equals(key)) {
                                String value = stringTokenizer.nextToken().trim();
                                contentLength = Integer.parseInt(value);
                            }
                        }
                    }
                }
                if (contentLength > 0) {
                    for (int i = 0; i < contentLength; i++) {
                        reqBack.write(req.read());
                    }
                }
                //完整请求报文
                // String request = reqBack.toString("UTF-8");
                // System.out.println("请求报文开始");
                // System.out.print(request);
                // System.out.println("\r\n请求报文结束");


                //拼接完整url
                if (url != null && !url.startsWith("http")) {
                    url = method.equals("connect") ? "https://" + url : "http://" + url;
                }
                URL u = new URL(url);
                //目标ip
                String targetHost = u.getHost();
                //目标端口
                int targetPort = u.getPort();
                if (targetPort == -1) {
                    targetPort = 80;
                }

                //目标Socket
                targetSocket = new Socket(targetHost, targetPort);

                if ("connect".equals(method)) {//https
                    //HTTP/1.1 200 Connection established
                    //报文直接发送给代理Socket
                    OutputStream outputStream = proxySocket.getOutputStream();
                    outputStream.write((protocol + " 200 Connection established\r\n").getBytes(StandardCharsets.UTF_8));
                    outputStream.write("Proxy-agent: ProxyServer/1.0\r\n".getBytes(StandardCharsets.UTF_8));
                    outputStream.write("\r\n".getBytes(StandardCharsets.UTF_8));
                    outputStream.flush();

                    //前者转发给后者,代理Socket转发给目标Socket
                    Thread proxy2target = new Thread(new ForwardData(proxySocket, targetSocket));
                    proxy2target.start();

                    //前者转发给后者,目标Socket转发给代理Socket
                    Thread target2proxy = new Thread(new ForwardData(targetSocket, proxySocket));
                    target2proxy.start();

                    proxy2target.join();

                } else {//http
                    //请求报文转发给目标Socket
                    OutputStream outputStream = targetSocket.getOutputStream();
                    outputStream.write(reqBack.toByteArray());
                    outputStream.flush();

                    //前者转发给后者,目标Socket转发给代理Socket
                    Thread thread = new Thread(new ForwardData(targetSocket, proxySocket));
                    thread.start();
                    thread.join();
                }
            } catch (Exception e) {
                Log.e("ProxyClient", e.getMessage(), e);
            } finally {
                try {
                    if (targetSocket != null) {
                        targetSocket.close();
                    }
                } catch (IOException e) {
                    Log.e("ProxyClient", e.getMessage(), e);
                }
                try {
                    if (proxySocket != null) {
                        proxySocket.close();
                    }
                } catch (IOException e) {
                    Log.e("ProxyClient", e.getMessage(), e);
                }
            }
            // Log.e("ProxyClient", "结束");
        }

        // 转发数据
        static class ForwardData implements Runnable {
            private final Socket inputSocket;
            private final Socket outputSocket;

            public ForwardData(Socket inputSocket, Socket outputSocket) {
                this.inputSocket = inputSocket;
                this.outputSocket = outputSocket;
            }

            @Override
            public void run() {
                try {
                    InputStream inputStream = inputSocket.getInputStream();
                    OutputStream outputStream = outputSocket.getOutputStream();
                    int read;
                    while ((read = inputStream.read()) != -1) {
                        outputStream.write(read);
                    }
                } catch (Exception e) {
                    // Log.e("ForwardData", inputSocket + e.getMessage());
                }
            }
        }

    }

app源码

proxyserver: 代理服务器app

我已打包,打包地址:https://gitee.com/gloweds/proxyserver/raw/master/app/release/app-release.apk

有时会报错,但是这个错误不影响功能。

报错时间线如下:

2023-10-06 11:29:16.478 客户端请求结束(发起http请求)
2023-10-06 11:29:16.555 代理proxySocket的read()报错 Connection reset
2023-10-06 11:29:16.571 关闭两个Socket连接
2023-10-06 11:29:16.571 目标targetSocket的read()报错 Socket closed

上面报错的原因,是因为客户端请求发送报文没有完整发送结束标识-1,
如果客户端完整发送结束标识,上面的两个错误不会发生(Connection reset、Socket closed),但是这个错误不影响功能,可以不用处理。

代理proxySocket的read()报错 Connection reset,是因为客户端未完整发送结束标识-1,而客户端请求都结束了,也成功拿到了响应数据,这时关闭连接就导致代理proxySocket的read()报错 Connection reset。

目标targetSocket的read()报错 Socket closed,因为前面代理proxySocket的read()阻塞未正常发送结束标识,所以targetSocket的read()也阻塞了,关闭两个proxySocket和targetSocket连接后,targetSocket的read()自然报错Socket closed连接被强制关闭了。

https请求流程图:

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

AutoGen - 多个Agent开发LLM应用的框架

文章目录 关于安装使用关于 Enable Next-Gen Large Language Model Applications 用多个Agent开发LLM应用的框架,这些agent可相互交流以解决任务。 官网:https://microsoft.github.io/autogen/github : http://github.com/microsoft/autogendiscord : https://discord.com/i…

LVGL_基础控件滚轮roller

LVGL_基础控件滚轮roller 1、创建滚轮roller控件 /* 创建一个 lv_roller 部件(对象) */ lv_obj_t * roller lv_roller_create(lv_scr_act()); // 创建一个 lv_roller 部件(对象),他的父对象是活动屏幕对象// 将部件(对象)添加到组&#xff0c;如果设置了默认组&#xff0c…

buuctf-crypto 1

rsarsa 题目描述 Math is cool! Use the RSA algorithm to decode the secret message, c, p, q, and e are parameters for the RSA algorithm.p 96484230290105156765905517400104265349457376392357398006439893520398525072984913995610350091634270503701075707336333…

Java循环队列

目录 一、循环队列 二、设计循环队列 一、循环队列 队列&#xff1a;只能在一端进行插入数据操作&#xff0c;另一端进行删除数据操作的特殊线性表&#xff0c;是一种先进先出的存储结构 插入操作的一端为队尾&#xff0c;删除操作的一端为队头 在线性队列中&#xff0c;一…

leetcode 62. 不同路径、63.不同路径||

62. 不同路径 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#xff09;。 问总共有多少条不同的路径&…

课题学习(三)----倾角和方位角的动态测量方法(基于陀螺仪的测量系统)

一、内容介绍 该测量系统基于三轴加速度和三轴陀螺仪&#xff0c;安装在钻柱内部&#xff0c;随钻柱一起旋转&#xff0c;形成捷联惯性导航系统&#xff0c;安装如下图所示&#xff1a;   假设三轴加速度和陀螺仪的输出为: f b [ f x f y f z ] T f^b\begin{bmatrix}f_{x} …

C++ 学习系列 -- std::list

一 std::list 介绍 list 是 c 中的序列式容器&#xff0c;其实现是双向链表&#xff0c;每个元素都有两个指针&#xff0c;分别指向前一个节点与后一个节点 链表与数组都是计算机常用的内存数据结构&#xff0c;与数组连续内存空间不一样的地方在于&#xff0c;链表的空间是不…

Springboot实现登录功能(token、redis、登录拦截器、全局异常处理)

登录流程&#xff1a; 1、前端调用登录接口&#xff0c;往接口里传入账号&#xff0c;密码 2、根据账号判断是否有这个用户&#xff0c;如果有则继续判断密码是否正确 3、验证成功后&#xff0c;则是根据账号&#xff0c;登录时间生成token&#xff08;用JWT&#xff09; 4、将…

64位Office API声明语句第111讲

跟我学VBA&#xff0c;我这里专注VBA, 授人以渔。我98年开始&#xff0c;从源码接触VBA已经20余年了&#xff0c;随着年龄的增长&#xff0c;越来越觉得有必要把这项技能传递给需要这项技术的职场人员。希望职场和数据打交道的朋友&#xff0c;都来学习VBA,利用VBA,起码可以提高…

企业使用SSL证书对于SEO有多重要

在当今竞争激烈的在线市场中&#xff0c;搜索引擎优化&#xff08;SEO&#xff09;是企业获得更高排名和增加网站流量的关键。在SEO策略中&#xff0c;企业使用SSL证书已经成为多重不可忽视的重要因素。让我们一起探究企业使用SSL证书对于SEO的重要性。 首先&#xff0c;搜索引…

socket.error: [Errno 10049]错误

今天在pycharm运行rl_server_no_training.py欲启动服务器时&#xff0c;却出现如下错误 Traceback (most recent call last):File "xxx/rl_server_no_training.py", line 333, in <module>main()File "xxx/rl_server_no_training.py", line 326, in…

【C++】运算符重载 ⑧ ( 左移运算符重载 | 友元函数 / 成员函数 实现运算符重载 | 类对象 使用 左移运算符 )

文章目录 一、左移运算符重载1、友元函数 / 成员函数 实现运算符重载2、类对象 使用 左移运算符3、左移运算符 << 重载 二、完整代码示例 一、左移运算符重载 1、友元函数 / 成员函数 实现运算符重载 运算符重载 的正规写法一般都是 使用 成员函数 的形式 实现的 ; 加法…

DLRover - 小记

文章目录 关于 DLRover 关于 DLRover github : https://github.com/intelligent-machine-learning/dlrover DLOver使大型人工智能模型的分布式训练变得简单、稳定、快速和绿色。 它可以在分布式集群上自动训练深度学习模型。 它帮助模型开发人员专注于模型结构&#xff0c;而…

手把手教你从零开始腾讯云服务器部署(连接建站教程)

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com分享使用腾讯云服务器建站教程&#xff0c;…

双周回顾#001 - 火烧云

工作以来&#xff0c;许久未曾见过如此壮观的火烧云景观了。 文章 1、穿透的 DIV &#xff08;pointer-events&#xff09;1 pointer-events 是一个蛮有趣的 CSS3 属性&#xff0c;虽然主要是针对 SVG &#xff0c;但其中几个属性应用在 div 上也是颇有意思。 顾名思义&…

学会自我投资:美团四大名著之<高效能人士的七个习惯>

你有没有这些问题&#xff1f;如果有&#xff0c;那么本文也许对你有用。 受害者心理&#xff0c;如果有人对你说粗话&#xff0c;就怼回去。消极被动。 没有计划。没有目标。不会担心自己的行为带来的后果。随波逐流。及时行乐&#xff0c;做个玩世不恭者。 拖延。总是先做紧…

vue +element 批量删除

1.拿到当前勾选状态 在el-table里边去写 单选用第一个 多选用第二个 select"selectHandle" :当用户手动勾选数据行的 Checkbox 时触发的事件 select-all"selectHandle":当用户手动勾选全选 Checkbox 时触发的事件// 点击勾选选择器selectHandle(selection…

机器学习:决策树

决策树 决策树是一种基于树形结构的模型&#xff0c;决策树从根节点开始&#xff0c;一步步走到叶子节点&#xff08;决策&#xff09;&#xff0c;所有的数据最终都会落到叶子节点&#xff0c;既可以做分类也可以做回归。 特征选择 根节点的选择该用哪一个特征呢&#xff…

【python海洋专题十二】年平均的南海海表面温度图

【python海洋专题十二】年平均的南海海表面温度图 上期内容 南海水深图 本期内容 年平均的南海平面温度图 数据来源 NCEP/DOE Reanalysis II: NOAA Physical Sciences Laboratory NCEP/DOE Reanalysis II skt.skt.sfc.mon.ltm.nc Part01. 本文重点内容 前几期地形图&a…

SSM - Springboot - MyBatis-Plus 全栈体系(二十一)

第四章 SpringMVC 四、RESTFUL 风格设计和实战 1. RESTFul 风格概述 1.1 RESTFul 风格简介 RESTful&#xff08;Representational State Transfer&#xff09;是一种软件架构风格&#xff0c;用于设计网络应用程序和服务之间的通信。它是一种基于标准 HTTP 方法的简单和轻量…