2.簡單的代理模型介紹2.1 使用" />

国产成人精品无码青草_亚洲国产美女精品久久久久∴_欧美人与鲁交大毛片免费_国产果冻豆传媒麻婆精东

18143453325 在線咨詢 在線咨詢
18143453325 在線咨詢
所在位置: 首頁 > 營銷資訊 > 建站知識 > TCP 代理服務(wù)器淺析

TCP 代理服務(wù)器淺析

時間:2023-02-10 20:03:01 | 來源:建站知識

時間:2023-02-10 20:03:01 來源:建站知識

1.前言

代理服務(wù)器可謂是無處不在。許多爬蟲需要大量的 TCP 代理來偽造自己的 IP 地址,從而防止因為訪問過快而被網(wǎng)站拉入黑名單。這里主要介紹代理服務(wù)器原理以及服務(wù)器端的基礎(chǔ)。

2.簡單的代理模型介紹

2.1 使用 Socks5 協(xié)議的架構(gòu)

Chrome 可以安裝一個 SwitchyOmega 插件來更好地支持 Socks5 協(xié)議的代理。他的架構(gòu)如下:

瀏覽器<--->代理服務(wù)器<--->餓了么網(wǎng)站
瀏覽器在開啟代理模式下,當打開一個網(wǎng)站時,會先連上代理服務(wù)器,并進行必要的握手步驟。 Socks5 的握手分兩步。

1. 第一步握手是無聊且?guī)缀豕潭ǖ膯柎?,俗稱「對暗號」。

2. 第二步握手, 瀏覽器發(fā)給代理服務(wù)器的報文中會包含需要代訪問的 IP 地址(或者域名)。代理服務(wù)器連接(connect)上餓了么網(wǎng)站服務(wù)器后,會發(fā)送響應報文給瀏覽器,告訴他成功了 。

到現(xiàn)在,握手完成。瀏覽器會發(fā)送數(shù)據(jù)給代理服務(wù)器,代理服務(wù)器將 TCP 報文原封不動發(fā)送給餓了么網(wǎng)站, 再將餓了么網(wǎng)站返回的數(shù)據(jù)也原封不動返回給瀏覽器。瀏覽器最終將頁面渲染并顯示在屏幕上。代理服務(wù)器甚至感知不到 HTTP 協(xié)議的存在,因為它只做 TCP 字節(jié)流的轉(zhuǎn)發(fā)。

2.2 使用 Shadowsocks 協(xié)議的架構(gòu)

Shadowsocks 將 2.2 的代理服務(wù)器拆分成兩部分,一部分依然在本機,我們稱之為 SSlocal ,一部分設(shè)在遠程,我們稱它為 SSServer 。由于 2.2 的協(xié)議傳輸?shù)亩际敲魑模琒hadowsocks 對數(shù)據(jù)的轉(zhuǎn)發(fā)進行了加密混淆處理。他雖然不能保證絕對安全,但至少能盡量混淆數(shù)據(jù)。

瀏覽器<--->SSlocal<--->SSServer<--->網(wǎng)站
Shadowsocks 也有握手過程,和 Socks5 的協(xié)議比較像,這節(jié)不做重點介紹。在握手成功后,代理會執(zhí)行轉(zhuǎn)發(fā)任務(wù)。SSlocal 會將瀏覽器發(fā)來的數(shù)據(jù)加密,發(fā)送給 SSServer , SSServer 把數(shù)據(jù)解密后發(fā)給網(wǎng)站。返回來的數(shù)據(jù)也是同理,都會有個加密解密的過程。一般來說比較建議用 AES-256-CFB 這種比較安全的加密方式。

3. TCP 服務(wù)端程序并發(fā)模型

代理服務(wù)器如果用的人多,那么并發(fā)壓力就會很大。不同的語言有不同的最佳模型。

3.1 多線程同步阻塞式

多線程同步式通常用一個線程處理一個 TCP 連接?,F(xiàn)如今的多線程同步,很少再用 pthread 里的線程去寫并發(fā),因為一個線程??赡?8 Mib,線程一多內(nèi)存就撐不住了。再加上線程切換的開銷,使得這種傳統(tǒng)的并發(fā)方式連 10k 都夠嗆。一般來說,線程數(shù)等于 CPU 核心數(shù)的時候,線程切換的開銷最小。

好在 Golang 里有輕量級的 Goroutine 替代傳統(tǒng)線程,使得這種同步模型實現(xiàn)高并發(fā)成為可能。通常這種同步阻塞式寫法如下:

// go 偽代碼func main() { while (true) { conn = acceptor.accept() go handleConn(conn) }}func handleConn(conn) { // 第一次握手 err = handshake(conn) checkError(err) addr, port = getAddr(conn) // 嘗試連接客戶端發(fā)來的 IP 地址 server, err = connect(addr, port) checkError(err) // 成功連上要通知客戶端,這里省略代碼 ... // 將客戶端發(fā)來的消息發(fā)送至遠程服務(wù)器 go io.Copy(server, conn) // 將服務(wù)端發(fā)來的消息轉(zhuǎn)發(fā)至客戶端 io.Copy(conn, server)} 說明:通常主函數(shù)就是一個大循環(huán),有新連接就開個 Goroutine 處理這個客戶端連接??蛻舳诉B接先進行握手后會發(fā)送想要訪問的目的服務(wù)器地址,代理服務(wù)器先嘗試 connect ,成功連接上則通知客戶端,客戶端開始發(fā)送真正的數(shù)據(jù)。這時候做一下數(shù)據(jù)的轉(zhuǎn)發(fā)就可以了。由于 TCP 是全雙工的協(xié)議,收發(fā)獨立,再加上 Goroutine 已經(jīng)相當廉價了,所以可以開啟兩個 Goroutine, 一個負責收,一個負責發(fā),互相不影響。不可以開啟多個線程(Goroutine)去對同一個 TCP 連接并行地發(fā)送數(shù)據(jù),因為這樣發(fā)送的數(shù)據(jù)是交錯在一起的,是錯誤的。

3.2 Reactor 同步非阻塞

一般來說,在 Linux 下,C/C++,Python 這種比較多使用 Reactor 模式。 Reactor 通常是一個主線程大循環(huán),由于使用了 I/O 多路復用技術(shù),使得單線程也能有很好的 I/O 性能。

//c++偽代碼int main() { while (true) { err, events = poller.wait(interval) processTimerTask() //處理定時器任務(wù) if (err) { //處理錯誤 continue } for (event : events) { if (event.isReadable()) { //處理讀事件 handleRead(event) } if (event.isWriteable()) { //處理寫事件 } if (event.isClosed()) { //處理關(guān)閉套接字事件 } if (event.hasError()) { //處理錯誤事件 } } }} poller 底層一般有 select,epoll 等。通常情況下使用 epoll 性能最好。單線程可以很容易支撐好幾萬并發(fā)。

3.2.1 「異步」代碼的「保存上下文」

無論是 Proactor 還是 Reactor ,凡是接近 Node.js 那種異步的寫法,都需要主動保存「上下文」在自己的內(nèi)存中?!干舷挛摹雇ǔ0瑳]收完整的數(shù)據(jù) buffer ,目前的狀態(tài) state。

//c++偽代碼struct ConnContext { Buffer buffer; // 接收的消息(可能還不是一個完整的消息) State state; // 狀態(tài)} read 函數(shù)一次性讀的字節(jié)數(shù)也是不確定的,有時需要多次調(diào)用 read 才能接受完完整的數(shù)據(jù)。由于數(shù)據(jù)不完整,并不能執(zhí)行接下來的流程,所以要先把數(shù)據(jù)緩存在一個地方,然后無奈返回。等數(shù)據(jù)接收完整了,才能進入下一個處理程序。一般每個連接都有一個上下文,由 map 保存對應關(guān)系。有些協(xié)議實現(xiàn)起來狀態(tài)比較多,比如有好幾次握手,必要時還需要使用狀態(tài)機保存狀態(tài)。每次有讀事件的時候,都會調(diào)用 handleRead 函數(shù),這時候根據(jù)之前保存的狀態(tài),很容易恢復到之前執(zhí)行的函數(shù)的位置(通過switch case分發(fā))。

// c++ 偽代碼void handleRead(conn) { context = contexts[conn] switch (context.state) { case eSTATE_HANDSHAKE1: handshake1(conn, context) break case eSTATE_HANDSHAKE2: handshake2(conn, context) break // 省略 // ... }}void handshake1(conn, context) { data = read_some(conn) append(context.buffer, data) if (context.buffer 不是一個完整的數(shù)據(jù)) { return } //發(fā)送一些東西 send_some(...) // 將 context.buffer 處理過的數(shù)據(jù)清理掉 // 現(xiàn)在handleshake1狀態(tài)結(jié)束了,更改為下一個狀態(tài) context.state = eSTATE_HANDSHAKE2 // 下一次讀事件將會調(diào)用 handshake2 函數(shù)}3.2.2 gethostbyname 是阻塞的

由于用到了域名,所以需要 DNS 服務(wù)將域名轉(zhuǎn)換成為 IP 地址。

Linux 下 gethostbyname 似乎是阻塞的。這與同步非阻塞式的代碼可謂格格不入??梢該Q一個非阻塞的實現(xiàn)。除此之外,Golang的 DNS 函數(shù)庫并沒有做緩存處理,這就意味著可能會頻繁訪問 DNS 服務(wù)器,速度沒有優(yōu)勢,可能對 DNS 服務(wù)器也是個不小的負擔。所以可以自行設(shè)計一個 DNS 緩存,是個不錯的主意。

4. TCP協(xié)議

假設(shè)讀者對 TCP 協(xié)議有了基本了解,知道滑動窗口,知道擁塞控制。

4.1 滑動窗口與數(shù)據(jù)轉(zhuǎn)發(fā)

我們再看一下這個結(jié)構(gòu):

瀏覽器<--->代理服務(wù)器<--->餓了么網(wǎng)站
考慮一下這個情況,瀏覽器和代理服務(wù)器連通速度很好,收發(fā)很快;而代理服務(wù)器和餓了么站點收發(fā)很慢。這樣一個收發(fā)速度不相等的情況,會出現(xiàn)怎樣的問題?

1. 對于阻塞同步模型,基本上不用考慮這個問題。因為他的收和發(fā)是串行的,這意味著它會自動調(diào)整滑動窗口大小。當代理服務(wù)器收到了瀏覽器的10Kib數(shù)據(jù),代理服務(wù)器就會慢慢發(fā)送這10Kib數(shù)據(jù)給餓了么網(wǎng)站,這時候如果瀏覽器還想發(fā)數(shù)據(jù)給代理服務(wù)器,只會保存在代理服務(wù)器的內(nèi)核緩沖區(qū)里,由于代理服務(wù)器程序在執(zhí)行發(fā)送的任務(wù)(顧不上收數(shù)據(jù)),并沒有從緩沖區(qū)取數(shù)據(jù),緩沖區(qū)的數(shù)據(jù)會越來越多,剩余空間越來越小,在TCP層面,就會通知調(diào)整滑動窗口大小。當讀緩沖區(qū)滿了以后,通知滑動窗口為0,客戶端就會停止發(fā)送數(shù)據(jù)。等代理服務(wù)器發(fā)送完數(shù)據(jù),開始從緩沖區(qū)取瀏覽器的數(shù)據(jù),瀏覽器到代理服務(wù)器的發(fā)送窗口又會從0變大,瀏覽器又可以發(fā)送數(shù)據(jù)了。

2. 對于非阻塞模型,這是一個大問題。由于沒有阻塞功能,代理服務(wù)器會一個勁兒的收下瀏覽器的所有數(shù)據(jù),讀取內(nèi)核緩沖區(qū)的數(shù)據(jù),再轉(zhuǎn)發(fā)數(shù)據(jù),并保存在自己的某個緩沖區(qū)中(sendBuffer)。有點類似于生產(chǎn)者消費者模型,生產(chǎn)得快,消費得慢,內(nèi)存會一直膨脹下去。其實我們很容易做1情況的模擬,只要發(fā)現(xiàn) sendBuffer 過大就停止讀緩沖區(qū)的數(shù)據(jù),等 sendBuffer 消下去了再開始讀就行了。

4.2 TCP 可靠性

TCP 可靠性體現(xiàn)在不亂,不重,不漏。他能很好地防止報文意外丟失,但不能100%防止人為篡改報文,當報文+校驗和一起被替換,還是很難被察覺的。TCP 可靠傳輸并不等于他有數(shù)據(jù)安全,這是兩個概念。但事實上,TCP 在不斷發(fā)展。 它的 29 號選項 TCP Authentication Option 使用了 SHA 哈希大大提高了篡改數(shù)據(jù)的難度。對于一個代理服務(wù)器來說,只需要單純轉(zhuǎn)發(fā)數(shù)據(jù)就可以,可以不用過于關(guān)心數(shù)據(jù)的篡改問題。

5.協(xié)議報文基礎(chǔ)

5.1 常用的兩種方式

5.1.1 長度+數(shù)據(jù)

這種實現(xiàn)是開頭幾個字節(jié)指明報文的長度,之后變長發(fā)送消息。簡單實現(xiàn)大致可以這樣:

std::vector<char> msg {5, 'h', 'e', 'l', 'l', 'o'} 第一字節(jié)表示長度(這里是 5 ),后面跟上這個長度的字節(jié)流。接收者先收一字節(jié),然后動態(tài)開辟這個長度的緩沖區(qū),把剩下的收完。

字節(jié)序

只用一個字節(jié)表示長度(0 ~ 255)似乎不太夠,倘若設(shè)計報文要兩個字節(jié)代表長度,很自然會選擇 uint16_t 。但是超過一個字節(jié)就會存在一個大小端的問題。很有可能自己電腦的本地序和網(wǎng)絡(luò)序不是一種。比方說:

uint16_t a = 0x01; 那么在有些機器上,a里存的是0000000000000001, 有些機器是0000000100000000。如果直接就把字節(jié)流傳給對方,說不定對方不是和自己一種字節(jié)序,就會把數(shù)據(jù)認錯。所以需要統(tǒng)一規(guī)定大小端順序,傳到網(wǎng)絡(luò)上統(tǒng)一用一種端序,從網(wǎng)絡(luò)到本機再轉(zhuǎn)換到本機的端序。

這里用C++聯(lián)合體能很好的展示字節(jié)序問題。

union Uint16 { uint16_t u; char c[2];}Uint16 foo;foo.u = 0x01;std::cout<<static_cast<unsigned int>(foo.c[0]);std::cout<<static_cast<unsigned int>(foo.c[1]); 根據(jù)機器不同,有可能輸出01,有可能輸出10。

這里有一系列c語言的主機序轉(zhuǎn)網(wǎng)絡(luò)序,以及做相反事情的函數(shù)接口

u_long htonl(u_long hostlongvalue);u_short htons(u_short hostshortvalue);u_long ntohl(u_long netlongvalue);u_short hotns(u_short netshortvalue);發(fā)送方代碼如下:

// go 偽代碼// 協(xié)議格式 兩個字節(jié)的長度 + 不定長數(shù)據(jù)sendmsg = "hello sunfish gao!";// 將本地序轉(zhuǎn)換成網(wǎng)絡(luò)序 len = htons(sendmsg.size());conn.write([]byte(len))conn.write([]byte(sendmsg))接收方偽代碼如下:

// go 偽代碼// 讀兩個字節(jié),得到接下來的數(shù)據(jù)總長度len = conn.read(2)// 網(wǎng)絡(luò)序轉(zhuǎn)主機序len = ntons(len) // 再讀剩下的字節(jié)數(shù)data = conn.read(len)5.1.2 以特殊符號分割

假設(shè)要設(shè)計一個鍵值對緩存服務(wù)器,命令以特殊符號分割,「存」與「取」命令可以設(shè)計成如下格式:

put key value/r/n

get key/r/n

當客戶端向服務(wù)端分開發(fā)送如下兩條命令:

“put key value/r/n”
“get key/r/n”
極有可能在服務(wù)端收到一條粘起來的數(shù)據(jù):

“put key value/r/nget key/r/n”
甚至是分兩次收到奇怪的分割的數(shù)據(jù):

“put key value/r/nget k”
“ey/r/n”
其實各種可能都有,因為 TCP 是字節(jié)流協(xié)議,所以 read 函數(shù)每次讀的長度不是確定的,他不像 WebSocket 能不用擔心「粘包」問題。如果數(shù)據(jù)中存在「空格」、「換行符」這樣的字符,則需要進行字符串替換,也就是「轉(zhuǎn)義」。

轉(zhuǎn)義其實生活比較常見,JSON 格式當出現(xiàn) utf8 數(shù)據(jù)時,會將數(shù)據(jù)轉(zhuǎn)成 /u006F 這種形式;在 HTTP 協(xié)議中,URL 含有非 ASCII 或者空格之類的特殊字符,也會轉(zhuǎn)義成為百分號編碼 %2a 這種形式。也就是說,可以將具有特殊意義的字符串替換成普通的字符串,就可以安全地被解析了。

6. 小結(jié)

本次小短文通過借著「TCP 代理服務(wù)器」的線索,串起來了一些 TCP 以及網(wǎng)絡(luò)編程的一些小知識。若有錯誤還望海涵,祝各位讀得開心。

7. 參考

陳碩《Linux 多線程服務(wù)端編程:使用 muduo C++ 網(wǎng)絡(luò)庫》

關(guān)鍵詞:服務(wù),代理

74
73
25
news

版權(quán)所有? 億企邦 1997-2025 保留一切法律許可權(quán)利。

為了最佳展示效果,本站不支持IE9及以下版本的瀏覽器,建議您使用谷歌Chrome瀏覽器。 點擊下載Chrome瀏覽器
關(guān)閉