[程序] C++实现 http和https的反向代理程序

前言

  • 前段时间写了一个爬虫,用来收集资源,但是遇到了一个问题,也即是目标网站会通过每个IP每秒请求次数来禁止ip,这样的话,就限制了速度。

  • 那么,我的解决方案就是传统的方法,使用代理,但是我哪里有那么多代理呢?此时通过百度就能找到一大堆的免费代理。经过测试,这些代理1000个里面能用的估计就30多个,但是似乎已经很好了是吗?

  • 问题还是有的,他们不稳定而且速度太慢了。

  • 所以,我就想到了以前我反向代理学校内网免费使用知网的套路。那时候我是这么做的:

  • 在内网的电脑上安装CProxy软件,设置代理端口是808

  • 然后通过frp软件将808端口转发到我服务器的7000 端口,那么我只需要将我的笔记本代理设置成服务器的7000端口就可以了。

  • 现在,如果我能写一个软件,能够实现上面两个软件的作用,那就太棒了。我只要把一个程序发给我的朋友,让他打开,然后我就可以借用他的IP作为代理了!

考虑到如果我让人帮忙的话,肯定麻烦别人越少越好,所以客户端使用了纯C/C++开发,体积最小。

而服务端因为我有连个服务器,一个windows,一个linux,似乎有跨平台需求,所以选用了Python来做(Python代码是真的爽呀)。

代理原理

代理简单的来说就是流量转发,从大的角度来说是没有问题的,但是,细究的话还是会有协议的问题的,下面将会简短的说明一下两个代理方式 http和https代理。

http代理

​ 这种代理方式比较简单,首先一个普通的http请求是下面这样的来源

  • 注意看,POST后面的路径是不带主机名的

  • 但是,看下面对代理请求的请求头,就能看到是带了主机名的

image-20200703150705738

所以,对于http流量代理,初步来看,只是需要将浏览器或者程序发出的http头重写一下,将主机部分去掉,再无脑转发即可。

https代理

对于https代理,就稍微麻烦一些(其实更简单了),虽然https流量是加密的的,但是我们不需要解密,只是做无脑转发,所以不涉及解密过程。https的安全性依旧是能保障的。

  • 首先,请求程序会先发出一个 CONNECT 方法的请求,表示目标服务器是哪个

  • 然后,代理服务器就应该回复,表示已经建立了一个tcp连接

    • HTTP/1.1 200 Connection established\r\n\r\n
  • 当代理程序得到可行的回复之后,就开始将https的加密数据发送给代理服务器,然后代理服务器将这些流量无脑提交给目标http服务器即可。

  • 目标https服务器的响应交给代理服务器,代理服务器再把流量给请求程序即可。之后就一直循环,直到https服务器关闭连接。

此时就会有一个问题,也就是http长连接的问题

众所周知,http是不会保持tcp连接的,但是这个说法其实是http协议1.0的情况了

对于http 1.1版本,默认请求头是 connection选项的值是keep-alive,会保持一段时间的tcp连接,具体时常由max-age选项决定。

问题就在于一个请求结束之后,并不会立马断开,此时依旧占用连接。所以,要么修改请求头中的connecion的值为close,要么主动断开与服务器的连接。

实现

实现的功能是这样的

整体分为客户端和服务端,我将会在服务器上运行服务端,它会监听7201端口

然后将客户端分发给闲置的电脑,注意,根据设想,这里会有多个客户端,都连接上7201端口,并且将自己认证为客户端

服务端接收到多个连接之后,对每个客户端都开放监听一个端口,依次是7202、7203

这样,我要使用他们的时候,先请求7201端口,获取一下可用主机,只要请求服务器的7202端口就好了

客户端

这次开发的客户端支持两种模式,也即是 正向代理反向代理

  • 正向代理

对于正向代理,只需要打开一个监听端口,然后等待浏览器或者请求程序连接即可,然后根据两个代理协议,分别处理就好了

  • 反向代理

反向代理是这次的核心

首先,需要指定服务器的ip和工作端口,然后连接上服务器

之后,对服务器发送过来的请求依次转发给代理服务器即可

服务端

我一直觉得服务端的开发应该不难的才对,毕竟只是一个流量转发,结果后来才发现,没有我想的那么简单。

整个过程其实不太顺利,重写了一遍,现在这个版本依旧有bug,不满意,准备重构。

遇到的所有问题记录

Python对于回复不响应

image-20200624152348900

  • 刚遇到这个问题的时候,着实吓了一跳,然后通过抓包,才注意到最后还有 \r\n\r\n(两个,我只有一个)

image-20200624152215856

  • 此时就正常了

接受的数据只有4字节

  • 通过C++接收到了数据,输出的时候只输出一点,通过strlen函数得到的长度只有4,诧异了一会儿。

image-20200624172939791

  • 通过抓包发现数据是发了过来的

image-20200624174558475

  • 确实是接收到了,但是因为00的关系,输出被截断了

image-20200624175036031

因为在C/C++里面 一个字符串的结束用\0表示,所以会出现这种情况,由上图能看出 bytes变量值是正常的。

最终数据已经发给Python了 但是Python还是阻塞状态

image-20200624190442683

可能是因为,先传输了证书,然后还有一段信息 参考https://imququ.com/post/web-proxy.html

image-20200624191620277

  • 由上面跟踪的数据流能看出,除了最开始数据,还有后面几个不同的数据段。猜测最上面的是证书部分,然后是hello响应,最终从https://imququ.com/post/web-proxy.html这里证实了我的想法

子线程中recv阻塞,卡死

对于每个请求都会开一个线程来转发数据,分别有两个转发,从客户端转发到代理请求,从代理请求转发到客户端。那么,其中就会遇到一个问题:当连接请求端的请求断开之后,中转服务器与客户端的连接没有断开,那么新的请求来了之后,旧的线程会先接收到客户端发来的信息,但是这个线程与代理请求已经断开了,就没办法发送,此时会异常。与此同时新的线程并没有接收到数据,也就无从转发

几种不完美的解决方案

  • 与代理请求通讯的套接字设为全局

    • 这样就不能同时实现多个请求同时处理了
  • 每次重连

    • 这样不能保证每次都能是同一个端口
  • 杀死线程

    • 太暴力了,会导致数据不安全,并且杀死线程不是简单的事情

还有几种尝试过但失败的方法

  • 为套接字设置超时时间
    • 虽然可以终止线程,但是太慢了
  • 使用结束标志
    • 当进程中recv结束阻塞的时候,正是他已经接受了数据之后。。。

因为中间只有一个通道,所以,想要完成多线程同时转发还需要额外的设计

可以预见,将会对每个请求标记一个id,以防止流量混乱的问题,目前版本仅能实现正向代理,以及请求速度不高的反向代理。。。

重构!!!!!

额外的想法

  • 我想研究一下frp这个软件的通信协议,这样我就能直接使用他的流量转发功能,而不用自己再运行一个服务端了!

源码: https://gitee.com/EasyWord/rProxy

参考: https://www.cnblogs.com/airoot/p/7851227.html
这个小项目对我帮助巨大,虽然这个只能在linux上跑,但是一些代码设计方法真的是学到了,不过其中用到了大量的全局变量,我感觉不利于代码复用。

posted @ 2020-07-03 16:22  Startu  阅读(4297)  评论(0编辑  收藏  举报