真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

聊聊第一個開源項目(內(nèi)網(wǎng)穿透)

文章首發(fā):聊聊第一個開源項目 - CProxy 作者:會玩code

為開平等地區(qū)用戶提供了全套網(wǎng)頁設(shè)計制作服務(wù),及開平網(wǎng)站建設(shè)行業(yè)解決方案。主營業(yè)務(wù)為成都做網(wǎng)站、成都網(wǎng)站建設(shè)、開平網(wǎng)站設(shè)計,以傳統(tǒng)方式定制建設(shè)網(wǎng)站,并提供域名空間備案等一條龍服務(wù),秉承以專業(yè)、用心的態(tài)度為用戶提供真誠的服務(wù)。我們深信只要達到每一位用戶的要求,就會得到認可,從而選擇與我們長期合作。這樣,我們也可以走得更遠!

初衷

最近在學C++,想寫個項目練練手。對網(wǎng)絡(luò)比較感興趣,之前使用過ngrok(GO版本的內(nèi)網(wǎng)穿透項目),看了部分源碼,想把自己的一些優(yōu)化想法用C++實現(xiàn)一下,便有了這個項目。

項目介紹

CProxy是一個反向代理,用戶可在自己內(nèi)網(wǎng)環(huán)境中啟動一個業(yè)務(wù)服務(wù),并在同一網(wǎng)絡(luò)下啟動CProxyClient,用于向CProxyServer注冊服務(wù)。CProxyClient和CProxyServer之間會創(chuàng)建一個隧道,外網(wǎng)可以通過訪問CProxyServer,數(shù)據(jù)轉(zhuǎn)發(fā)到CProxyClient,從而被業(yè)務(wù)服務(wù)接收到。實現(xiàn)內(nèi)網(wǎng)服務(wù)被外網(wǎng)訪問。

項目地址

https://github.com/lzs123/CProxy.git

使用方法

bash build.sh
// 啟動服務(wù)端
{ProjectDir}/build/server/Server --proxy_port=8090 --work_thread_nums=4
(另一個終端) 
// 啟動客戶端
{ProjectDir}/build/client/Client --local_server=127.0.0.1:7777 --cproxy_server=127.0.0.1:8080

項目亮點

  • 使用epoll作為IO多路復用的實現(xiàn)
  • 數(shù)據(jù)轉(zhuǎn)發(fā)時,使用splice零拷貝,減少IO性能瓶頸
  • 數(shù)據(jù)連接和控制連接接耦,避免互相影響
  • 采用Reactor多線程模型,充分利用多核CPU性能

流程架構(gòu)

角色

  1. LocalServer: 內(nèi)網(wǎng)業(yè)務(wù)服務(wù)
  2. CProxyClient: CProxy客戶端,一般與LocalServer部署在一起,對接CProxyServer和InnerServer
  3. CProxyServer: CProxy服務(wù)端
  4. PublicClient: 業(yè)務(wù)客戶端

數(shù)據(jù)流

PublicClient先將請求打到CProxyServer,CProxyServer識別請求是屬于哪個CProxyClient,然后將數(shù)據(jù)轉(zhuǎn)發(fā)到CProxyClient,CProxyClient再識別請求是屬于哪個LocalServer的,將請求再轉(zhuǎn)發(fā)到LocalServer,完成數(shù)據(jù)的轉(zhuǎn)發(fā)。

工作流程

先介紹CProxyServer端的兩個概念:

  • Control:在CProxyServer中會維護一個ControlMap,一個Control對應(yīng)一個CProxyClient,存儲CProxyClient的一些元信息和控制信息
  • Tunnel:每個Control中會維護一個TunnelMap,一個Tunnel對應(yīng)一個LocalServer服務(wù)

在CProxyClient端,也會維護一個TunnelMap,每個Tunnel對應(yīng)一個LocalServer服務(wù),只不過Client端的Tunnel與Server端的Tunnel存儲的內(nèi)容略有差異

啟動流程

CProxyServer
  1. 完成幾種工作線程的初始化。
  2. 監(jiān)聽一個CtlPort,等待CProxyClient連接。
CProxyClient
  1. 完成對應(yīng)線程的初始化。
  2. 然后連接Server的CtlPort,此連接稱為ctl_conn, 用于client和server之前控制信息的傳遞。
  3. 請求注冊Control,獲取ctl_id。
  4. 最后再根據(jù)Tunnel配置文件完成多個Tunnel的注冊。需要注意的是,每注冊一個Tunnel,Server端就會多監(jiān)聽一個PublicPort,作為外部訪問LocalServer的入口。

數(shù)據(jù)轉(zhuǎn)發(fā)流程

  1. Web上的PublicClient請求CProxyServer上的PublicPort建立連接;CProxyServer接收連接請求,將public_accept_fd封裝成PublicConn。
  2. CProxyServer通過ctl_conn向client發(fā)送NotifyClientNeedProxyMsg通知Client需要創(chuàng)建一個proxy。
  3. Client收到后,會分別連接LocalServer和CProxyServer:
    3.1. 連接LocalServer,將local_conn_fd封裝成LocalConn。
    3.2. 連接ProxyServer的ProxyPort,將proxy_conn_fd封裝成ProxyConn,并將LocalConn和ProxyConn綁定。
  4. CProxyServer的ProxyPort收到請求后,將proxy_accept_fd封裝成ProxyConn,將ProxyConn與PublicConn綁定。
  5. 此后的數(shù)據(jù)在PublicConn、ProxyConn和LocalConn上完成轉(zhuǎn)發(fā)傳輸。

連接管理

復用proxy連接

為了避免頻繁創(chuàng)建銷毀proxy連接,在完成數(shù)據(jù)轉(zhuǎn)發(fā)后,會將proxyConn放到空閑隊列中,等待下次使用。
proxy_conn有兩種模式 - 數(shù)據(jù)傳輸模式和空閑模式。在數(shù)據(jù)傳輸模式中,proxy_conn不會去讀取解析緩沖區(qū)中的數(shù)據(jù),只會把數(shù)據(jù)通過pipe管道轉(zhuǎn)發(fā)到local_conn; 空閑模式時,會讀取并解析緩沖區(qū)中的數(shù)據(jù),此時的數(shù)據(jù)是一些控制信息,用于調(diào)整proxy_conn本身。

當有新publicClient連接時,會先從空閑列表中獲取可用的proxy_conn,此時proxy_conn處于空閑模式,CProxyServer端會通過proxy_conn向CProxyClient端發(fā)送StartProxyConnReqMsg,
CLient端收到后,會為這個proxy_conn綁定一個local_conn, 并將工作模式置為數(shù)據(jù)傳輸模式。之后數(shù)據(jù)在這對proxy_conn上進行轉(zhuǎn)發(fā)。

數(shù)據(jù)連接斷開處理

close和shutdown的區(qū)別

  1. close
int close(int sockfd)

在不考慮so_linger的情況下,close會關(guān)閉兩個方向的數(shù)據(jù)流。

  1. 讀方向上,內(nèi)核會將套接字設(shè)置為不可讀,任何讀操作都會返回異常;
  2. 輸出方向上,內(nèi)核會嘗試將發(fā)送緩沖區(qū)的數(shù)據(jù)發(fā)送給對端,之后發(fā)送fin包結(jié)束連接,這個過程中,往套接字寫入數(shù)據(jù)都會返回異常。
  3. 若對端還發(fā)送數(shù)據(jù)過來,會返回一個rst報文。

注意:套接字會維護一個計數(shù),當有一個進程持有,計數(shù)加一,close調(diào)用時會檢查計數(shù),只有當計數(shù)為0時,才會關(guān)閉連接,否則,只是將套接字的計數(shù)減一。
2. shutdown

int shutdown(int sockfd, int howto)

shutdown顯得更加優(yōu)雅,能控制只關(guān)閉連接的一個方向

  1. howto = 0 關(guān)閉連接的讀方向,對該套接字進行讀操作直接返回EOF;將接收緩沖區(qū)中的數(shù)據(jù)丟棄,之后再有數(shù)據(jù)到達,會對數(shù)據(jù)進行ACK,然后悄悄丟棄。
  2. howto = 1 關(guān)閉連接的寫方向,會將發(fā)送緩沖區(qū)上的數(shù)據(jù)發(fā)送出去,然后發(fā)送fin包;應(yīng)用程序?qū)υ撎捉幼值膶懭氩僮鲿祷禺惓#╯hutdown不會檢查套接字的計數(shù)情況,會直接關(guān)閉連接)
  3. howto = 2 0+1各操作一遍,關(guān)閉連接的兩個方向。

項目使用shutdown去處理數(shù)據(jù)連接的斷開,當CProxyServer收到publicClient的fin包(CProxyClient收到LocalServer的fin包)后,通過ctlConn通知對端,
對端收到后,調(diào)用shutdown(local_conn_fd/public_conn_fd, 2)關(guān)閉寫方向。等收到另一個方向的fin包后,將proxyConn置為空閑模式,并放回空閑隊列中。

在處理鏈接斷開和復用代理鏈接這塊遇到的坑比較多

  1. 控制對端去shutdown連接是通過ctl_conn去通知的,可能這一方向上對端的數(shù)據(jù)還沒有全部轉(zhuǎn)發(fā)完成就收到斷開通知了,需要確保數(shù)據(jù)全部轉(zhuǎn)發(fā)完才能調(diào)用shutdown去關(guān)閉連接。
  2. 從空閑列表中拿到一個proxy_conn后,需要發(fā)送StartProxyConnReq,告知對端開始工作,如果此時對端的這一proxy_conn還處于數(shù)據(jù)傳輸模式,就會報錯了。

數(shù)據(jù)傳輸

數(shù)據(jù)在Server和Client都需進行轉(zhuǎn)發(fā),將數(shù)據(jù)從一個連接的接收緩沖區(qū)轉(zhuǎn)發(fā)到另一個連接的發(fā)送緩沖區(qū)。如果使用write/read系統(tǒng)調(diào)用,整個流程如下圖

數(shù)據(jù)先從內(nèi)核空間復制到用戶空間,之后再調(diào)用write系統(tǒng)調(diào)用將數(shù)據(jù)復制到內(nèi)核空間。每次系統(tǒng)調(diào)用,都需要切換CPU上下文,而且,兩次拷貝都需要CPU去執(zhí)行(CPU copy),所以,大量的拷貝操作,會成為整個服務(wù)的性能瓶頸。

在CProxy中,使用splice的零拷貝方案,數(shù)據(jù)直接從內(nèi)核空間的Source Socket Buffer轉(zhuǎn)移到Dest Socket Buffer,不需要任何CPU copy。

splice通過pipe管道“傳遞”數(shù)據(jù),基本原理是通過pipe管道修改source socket buffer和dest socket buffer的物理內(nèi)存頁

splice并不涉及數(shù)據(jù)的實際復制,只是修改了socket buffer的物理內(nèi)存頁指針。

并發(fā)模型

CProxyClient和CProxyServer均采用多線程reactor模型,利用線程池提高并發(fā)度。并使用epoll作為IO多路復用的實現(xiàn)方式。每個線程都有一個事件循環(huán)(One loop per thread)。線程分多類,各自處理不同的連接讀寫。

CProxyServer端

為了避免業(yè)務(wù)連接處理影響到Client和Server之間控制信息的傳遞。我們將業(yè)務(wù)數(shù)據(jù)處理與控制數(shù)據(jù)處理解耦。在Server端中設(shè)置了三種線程:

  1. mainThread: 用于監(jiān)聽ctl_conn和proxy_conn的連接請求以及ctl_conn上的相關(guān)讀寫
  2. publicListenThread: 監(jiān)聽并接收外來連接
  3. eventLoopThreadPool: 線程池,用于處理public_conn和proxy_conn之間的數(shù)據(jù)交換。

CProxyClient端

client端比較簡單,只有兩種線程:

  1. mainThread: 用于處理ctl_conn的讀寫
  2. eventLoopThreadPool: 線程池,用于處理proxy_conn和local_conn之間的數(shù)據(jù)交換

遺留問題(未完待續(xù)。。)

在使用ab壓測時,在完成了幾百個轉(zhuǎn)發(fā)后,就卡住了,通過tcpdump抓包發(fā)現(xiàn)客戶端使用A端口連接,但服務(wù)端accept后打印的客戶端端口是B。
數(shù)據(jù)流在【publicClient->CProxyServer->CProxyClient->LocalServer】是正常的;
但回包方向【LocalServer->CProxyClient->CProxyServer-?->publicClient】,目前還沒有找到分析方向。。。

寫在最后

喜歡本文的朋友,歡迎關(guān)注公眾號「會玩code」,專注大白話分享實用技術(shù)


本文名稱:聊聊第一個開源項目(內(nèi)網(wǎng)穿透)
轉(zhuǎn)載來于:http://weahome.cn/article/dsogodj.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部