• java socket实现代理Android App


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

    核心代码

    1. // 启动代理服务器
    2. private void startProxyServer() {
    3. new Thread(new ProxyServer()).start();
    4. }
    5. // 代理服务器
    6. static class ProxyServer implements Runnable {
    7. @Override
    8. public void run() {
    9. try {
    10. // 监听指定的端口
    11. int port = 8098; //一般使用49152到65535之间的端口
    12. ServerSocket server = new ServerSocket(port);
    13. // 当一个ServerSocket关闭并释放其绑定的端口后,操作系统通常会在几分钟内不允许其他Socket再次绑定到该端口。
    14. // true:操作系统将允许其他Socket立即绑定到刚刚被释放的端口。
    15. server.setReuseAddress(true);
    16. // 使用线程池,防止过多线程耗尽资源
    17. ExecutorService threadPool = Executors.newFixedThreadPool(50);
    18. while (true) {
    19. Socket socket = server.accept(); //会一直阻塞,直到有客户端连接进来
    20. // new Thread 只是创建一个类的对象实例而已。而真正创建线程的是start()方法。
    21. // 这里并没有直接调用start()方法,所以并没创建新线程,而是交给线程池去执行。
    22. threadPool.submit(new ProxyClient(socket));
    23. }
    24. } catch (Exception e) {
    25. Log.e("ProxyServer", e.getMessage(), e);
    26. }
    27. }
    28. }
    29. // 代理客户端
    30. static class ProxyClient implements Runnable {
    31. private final Socket proxySocket;//代理Socket
    32. private Socket targetSocket = null;//目标Socket
    33. public ProxyClient(Socket socket) {
    34. this.proxySocket = socket;
    35. }
    36. @Override
    37. public void run() {
    38. try {
    39. //客户端请求的报文
    40. InputStream req = proxySocket.getInputStream();
    41. int read;
    42. int contentLength = 0;//body长度
    43. String method = null;//请求方法
    44. String url = null;//请求地址
    45. String protocol = null;//请求协议
    46. ByteArrayOutputStream os = new ByteArrayOutputStream();
    47. ByteArrayOutputStream reqBack = new ByteArrayOutputStream();
    48. //解析,提取请求报文
    49. while ((read = req.read()) != -1) {
    50. os.write(read);
    51. reqBack.write(read);
    52. if (read == '\n') {
    53. //CONNECT www.xx.com:443/xx/yy HTTP/1.1
    54. String line = os.toString("UTF-8");
    55. os.reset();//重置,以便再次使用
    56. if ("\r\n".equals(line)) {
    57. //空行,请求头结束标志
    58. break;
    59. }
    60. StringTokenizer stringTokenizer = new StringTokenizer(line, " ");
    61. if (method == null) {
    62. //八种请求方法:GET、POST、HEAD、OPTIONS、PUT、PATCH、DELETE、TRACE、CONNECT 方法
    63. method = stringTokenizer.nextToken().toLowerCase();//connect
    64. url = stringTokenizer.nextToken();//www.xx.com:443/xx/yy
    65. protocol = stringTokenizer.nextToken().trim();//HTTP/1.1
    66. } else {
    67. String key = stringTokenizer.nextToken().toLowerCase();
    68. if ("content-length:".equals(key)) {
    69. String value = stringTokenizer.nextToken().trim();
    70. contentLength = Integer.parseInt(value);
    71. }
    72. }
    73. }
    74. }
    75. if (contentLength > 0) {
    76. for (int i = 0; i < contentLength; i++) {
    77. reqBack.write(req.read());
    78. }
    79. }
    80. //完整请求报文
    81. // String request = reqBack.toString("UTF-8");
    82. // System.out.println("请求报文开始");
    83. // System.out.print(request);
    84. // System.out.println("\r\n请求报文结束");
    85. //拼接完整url
    86. if (url != null && !url.startsWith("http")) {
    87. url = method.equals("connect") ? "https://" + url : "http://" + url;
    88. }
    89. URL u = new URL(url);
    90. //目标ip
    91. String targetHost = u.getHost();
    92. //目标端口
    93. int targetPort = u.getPort();
    94. if (targetPort == -1) {
    95. targetPort = 80;
    96. }
    97. //目标Socket
    98. targetSocket = new Socket(targetHost, targetPort);
    99. if ("connect".equals(method)) {//https
    100. //HTTP/1.1 200 Connection established
    101. //报文直接发送给代理Socket
    102. OutputStream outputStream = proxySocket.getOutputStream();
    103. outputStream.write((protocol + " 200 Connection established\r\n").getBytes(StandardCharsets.UTF_8));
    104. outputStream.write("Proxy-agent: ProxyServer/1.0\r\n".getBytes(StandardCharsets.UTF_8));
    105. outputStream.write("\r\n".getBytes(StandardCharsets.UTF_8));
    106. outputStream.flush();
    107. //前者转发给后者,代理Socket转发给目标Socket
    108. Thread proxy2target = new Thread(new ForwardData(proxySocket, targetSocket));
    109. proxy2target.start();
    110. //前者转发给后者,目标Socket转发给代理Socket
    111. Thread target2proxy = new Thread(new ForwardData(targetSocket, proxySocket));
    112. target2proxy.start();
    113. proxy2target.join();
    114. } else {//http
    115. //请求报文转发给目标Socket
    116. OutputStream outputStream = targetSocket.getOutputStream();
    117. outputStream.write(reqBack.toByteArray());
    118. outputStream.flush();
    119. //前者转发给后者,目标Socket转发给代理Socket
    120. Thread thread = new Thread(new ForwardData(targetSocket, proxySocket));
    121. thread.start();
    122. thread.join();
    123. }
    124. } catch (Exception e) {
    125. Log.e("ProxyClient", e.getMessage(), e);
    126. } finally {
    127. try {
    128. if (targetSocket != null) {
    129. targetSocket.close();
    130. }
    131. } catch (IOException e) {
    132. Log.e("ProxyClient", e.getMessage(), e);
    133. }
    134. try {
    135. if (proxySocket != null) {
    136. proxySocket.close();
    137. }
    138. } catch (IOException e) {
    139. Log.e("ProxyClient", e.getMessage(), e);
    140. }
    141. }
    142. // Log.e("ProxyClient", "结束");
    143. }
    144. // 转发数据
    145. static class ForwardData implements Runnable {
    146. private final Socket inputSocket;
    147. private final Socket outputSocket;
    148. public ForwardData(Socket inputSocket, Socket outputSocket) {
    149. this.inputSocket = inputSocket;
    150. this.outputSocket = outputSocket;
    151. }
    152. @Override
    153. public void run() {
    154. try {
    155. InputStream inputStream = inputSocket.getInputStream();
    156. OutputStream outputStream = outputSocket.getOutputStream();
    157. int read;
    158. while ((read = inputStream.read()) != -1) {
    159. outputStream.write(read);
    160. }
    161. } catch (Exception e) {
    162. // Log.e("ForwardData", inputSocket + e.getMessage());
    163. }
    164. }
    165. }
    166. }

    app源码

    proxyserver: 代理服务器app

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

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

    报错时间线如下:

    1. 2023-10-06 11:29:16.478 客户端请求结束(发起http请求)
    2. 2023-10-06 11:29:16.555 代理proxySocket的read()报错 Connection reset
    3. 2023-10-06 11:29:16.571 关闭两个Socket连接
    4. 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请求流程图:

     

  • 相关阅读:
    WebVR — 网络虚拟现实
    用WebGPU实现基于物理的渲染
    3.4 C++高级编程_函数模板_重载
    里程碑!用自己的编程语言实现了一个网站
    【RT-Thread】nxp rt10xx 设备驱动框架之--rtc搭建和使用
    高阶项目管理必须知道——项目组合治理
    瑞吉外卖(优化篇)
    链表应用(C++,递增合并、递增求交集、逆序、删除区间)
    为何面试官总是将你简历上的技术问题问到回答不出来为止?
    经典c程序100例==61--100
  • 原文地址:https://blog.csdn.net/u014644574/article/details/133608940