前言
-
前段時間寫了一個爬蟲,用來收集資源,但是遇到了一個問題,也即是目標網站會通過每個IP每秒請求次數來禁止ip,這樣的話,就限制了速度。
-
那么,我的解決方案就是傳統的方法,使用代理,但是我哪里有那么多代理呢?此時通過百度就能找到一大堆的免費代理。經過測試,這些代理1000個里面能用的估計就30多個,但是似乎已經很好了是嗎?
-
問題還是有的,他們不穩定而且速度太慢了。
-
所以,我就想到了以前我反向代理學校內網免費使用知網的套路。那時候我是這么做的:
-
在內網的電腦上安裝CProxy軟件,設置代理端口是808
-
然后通過frp軟件將808端口轉發到我服務器的7000 端口,那么我只需要將我的筆記本代理設置成服務器的7000端口就可以了。
-
現在,如果我能寫一個軟件,能夠實現上面兩個軟件的作用,那就太棒了。我只要把一個程序發給我的朋友,讓他打開,然后我就可以借用他的IP作為代理了!
考慮到如果我讓人幫忙的話,肯定麻煩別人越少越好,所以客戶端使用了純C/C++開發,體積最小。
而服務端因為我有連個服務器,一個windows,一個linux,似乎有跨平台需求,所以選用了Python來做(Python代碼是真的爽呀)。
代理原理
代理簡單的來說就是流量轉發,從大的角度來說是沒有問題的,但是,細究的話還是會有協議的問題的,下面將會簡短的說明一下兩個代理方式 http和https代理。
http代理
這種代理方式比較簡單,首先一個普通的http請求是下面這樣的來源

-
注意看,POST后面的路徑是不帶主機名的
-
但是,看下面對代理請求的請求頭,就能看到是帶了主機名的

所以,對於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對於回復不響應

- 剛遇到這個問題的時候,着實嚇了一跳,然后通過抓包,才注意到最后還有 \r\n\r\n(兩個,我只有一個)

- 此時就正常了
接受的數據只有4字節
- 通過C++接收到了數據,輸出的時候只輸出一點,通過strlen函數得到的長度只有4,詫異了一會兒。

- 通過抓包發現數據是發了過來的

- 確實是接收到了,但是因為00的關系,輸出被截斷了

因為在C/C++里面 一個字符串的結束用\0表示,所以會出現這種情況,由上圖能看出 bytes變量值是正常的。
最終數據已經發給Python了 但是Python還是阻塞狀態

可能是因為,先傳輸了證書,然后還有一段信息 參考https://imququ.com/post/web-proxy.html

- 由上面跟蹤的數據流能看出,除了最開始數據,還有后面幾個不同的數據段。猜測最上面的是證書部分,然后是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上跑,但是一些代碼設計方法真的是學到了,不過其中用到了大量的全局變量,我感覺不利於代碼復用。



