java socket实现代理Android App

文章详细描述了一个基于Java的代理服务器应用,它监听特定端口,转发客户端请求至目标服务器,并处理可能出现的如连接中断等异常情况。作者还提到,由于客户端请求报文未完整发送结束标识,可能导致代理和目标Socket的读操作错误,但不影响实际功能。
摘要由CSDN通过智能技术生成

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

核心代码


    // 启动代理服务器
    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请求流程图:

 

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值