1. 程式人生 > >JAVA實現UDP反向代理

JAVA實現UDP反向代理

用JAVA實現TCP協議的反向代理非常容易,只用不到100行程式碼就能搞定,每一個連線只需兩個Socket,3條執行緒,進行輸入流與輸出流之間互相讀寫就可以了,可以承載所有TCP協議層以上的流量,比如HTTP(s),FTP,sFTP,郵件,即時通訊等等。其效果和HAProxy或者Nginx的TCP反向代理差不多,而且如果客戶端的併發數比較大的情況,還可以使用JAVA的NIO和AIO框架,降低伺服器資源的開銷。

對於UDP協議來說,它是無連線無狀態的點對點協議,所以與TCP協議比起來會有很大的不同,主要體現在:TCP是有連結協議,所以當有很多個客戶端訪問代理程式時,代理會轉發他們的請求給伺服器端,伺服器響應資料給代理程式後,代理程式清楚的知道這個響應應該發給哪個客戶端,並將資料傳送回去。其邏輯如下

1、客戶端發起一個Socket,繫結埠50000,目標地址為代理伺服器8080。代理程式開啟ServerSocket,繫結埠8080,於是一條客戶端5000到代理端8080的連線就建立起來,客戶端Socket通過OuputStream把資料傳送給代理端的InputStream。

2、服務端監聽到連線建立之後,立即new Socket,繫結51000埠,目標埠為真實伺服器的80埠,於是一條代理伺服器50000到真實伺服器80的連線就建立起來。

3、然後代理伺服器新建兩個執行緒,一條將客戶端的連線的InputStream寫入到真實伺服器連線的OutputStream,另一條執行緒將服務端連線的InputStream寫入到客戶端連線的OutputStream,就完成了一次代理轉發。

在這個過程中,客戶端開放了一個埠50000,代理程式開放兩個埠8080和51000,真實伺服器開放了一個埠80

如果有n個客戶端,那麼每個客戶端也只開放一個埠,代理程式開放1+n個埠,真實伺服器始終開放一個埠。

如果有n個客戶端,每個客戶端建立m條併發連線,那麼每個客戶端開放m個埠,代理程式開放1+m*n個埠,真實伺服器開放一個埠。

從上面可以看出,對於TCP來說,每一條連線使用不同的埠進行區分,當一條連線建立起來時,客戶端會監聽一個49000以上的埠,代理端使用代理埠與其連線,但是代理端建立的與真實伺服器的連線也會在代理端開放一個49000以上的埠,這個埠號和客戶端開放的埠號一般是不同的。這樣一來,在上面的例子中,服務端傳送到代理端51000的資料包很自然的被代理端轉發給客戶端50000埠,一一對映不會出錯。即使再多的客戶端連線也不會混淆。

但是UDP協議是無連線的協議,它也可以仿照TCP協議的轉發方式進行工作,過程如下

1、客戶端發起一個DatagramSocket,繫結埠50000,目標地址為代理伺服器的53埠。代理程式也new 一個 DatagramSocket,監聽53埠。於是客戶端就可以向代理端傳送資料包了。

2、代理端收到資料包之後,有兩個選擇,第一是用接收客戶端資料包的DatagramSocket把這個資料包傳送到真實伺服器,不過這樣就沒法再接收客戶端發來的後續資料包了,所以一般情況下,代理程式會new DatagramSocket,把剛剛收到的DatagramPacket修改一下目標地址傳送給真實的伺服器。

3、現在代理端又有兩個選擇了,由於UDP不像TCP那樣有流的概念,所以我們無法拿到輸入輸出流,只能操作資料包,而且是單向的操作資料包。一個方案是用剛剛向真實伺服器發包的DatagramSocket接收服務端返回的資料包,另一個方案是不接受資料包繼續轉發下一個資料包。

第一個方案我們可以接收到伺服器的返回資料,缺點是沒法再利用這個DatagramSocket傳送第二個資料包了,第二種方案可以流暢的轉發客戶端的資料包到服務端,不過客戶端就收不到服務端的響應了。

所以此時,我們需要根據業務需求制定相關的轉發策略了,這裡我主要分為了兩種型別

1、DNS型轉發:客戶端傳送一個數據包需要服務端馬上返回一個數據包實現業務邏輯,比如DNS。單個數據包最大為65536位元組,某些環境下還會更小。

2、P2P隧道轉發:客戶端向服務端傳送一連串的多個數據包,並且服務端也向客戶端傳送多個數據包,資料包收發並不是一問一答的關係,而是傳送和接收同時進行。

邏輯上來說第一種方式是第二種方式的子集,只不過為了節約資源開銷,第一種方式在服務端返回資料包以後立即關閉Socket,從而節約記憶體和網路資源。有些應用比如OpenVPN的udp模式就沒有應答的概念,而是輸入和輸出像流一樣同時進行。從程式碼實現上來說,第二種方式比較容易實現一些,首先建立兩個DatagramSocket,一個監聽在本地埠,用於接收客戶端發來的資料包並轉發真實伺服器的響應;另一個把目的地址指向真實伺服器,用於向真實伺服器傳送和接收資料包。在代理端的DatagramSocket通過receive()方法收到一個DatagramPacket之後,新建兩個執行緒,一個負責接收客戶端的資料包並向真實伺服器傳送資料包,另一個負責接收伺服器發的資料包並修改目的地址傳送給客戶端。如此一來,通過兩個DatagramSocket兩個執行緒就實現了一個P2P的UDP轉發隧道。

注意,UDP報文是沒有FIN這種識別符號的,所以代理伺服器無法知道連線是否被主動關閉,只能通過超時時間去判斷,所以為了連線保活,需要客戶端定時傳送心跳包。另外,如果埠是固定的話也可以讓代理伺服器建立永久的轉發通道,只不過這樣資源開銷比較大,不適合高併發的場景。

上面的兩種方式都面臨一個問題,就是併發訪問的問題。所謂併發訪問就是代理伺服器反向代理了一個埠,然後有不止一個客戶端連結這個埠,或者一個客戶端呼叫了多個程式併發訪問該埠,那麼如果程式碼上不做處理就會引起UDP資料包傳遞的混亂。

舉個例子,比如客戶端A向DNS反向代理髮送了一個DNS請求,還沒收到返回訊息的時候客戶端B也發出了一個DNS請求,這時代理伺服器收到了真實伺服器發來的客戶端A請求的響應,由於UDP是無連線協議,所以此時代理伺服器並不知道應該把資料包發到哪去。不向TCP那樣獲取到Socket之後直接寫流就可以了。所以需要我們用JAVA程式碼去維護一個路由表。也就是說,當用戶A請求DNS的時候,記錄下A的源IP和埠號,然後new DatagramSocket向真實伺服器發出請求,然後再用這個DatagramSOcket的receive()去接收返回資料包,然後按照剛才記錄的A的ip和埠把資料傳送回去,同理如果多個客戶端來請求,那麼就需要new 多個DatagramSOcket並記錄每個請求的源IP和埠。如果多個客戶端複用同一個DatagramSocket向真實伺服器傳送訊息,雖然可以把資料包傳送出去,但是收到響應以後你就無從獲知是哪個客戶端請求的了,所以這樣做就會導致資料包錯發的問題,會出現把客戶端A請求DNS的響應錯發給B,B本來請求的是www.csdn.com的IP,但是代理卻發給它www.baidu.com的IP,這是和TCP最大的區別。如下圖黃色圖示標出的請求和相應資訊,就發生了這種錯亂:


總結一下過程如下

1、一個客戶端發出一個DNS請求到代理伺服器53埠,開放埠50000監聽返回值。代理端使用兩個DatagramSocket,一個監聽53埠,與客戶端收發資料,另一個開放51000埠用來與真實伺服器收發資料。真實伺服器只開放一個53埠

2、n個客戶端各發出一個DNS請求,每個客戶端都開放50000埠監聽返回資料,代理端使用1+n個DatagramSocket,開放53和另外n個UDP埠,並在記憶體中記錄好每個客戶端的源IP和埠,第一個DatagramSocket用來和所有客戶端收發資料,後n個用來和真實伺服器收發資料。

3、n個客戶端都同時發出m個DNS請求,每個客戶端開放50000,50001,50002...接收返回資料,代理端使用1+m*n個DatagramSocket,開放53和m*n個UDP埠,記錄好每一個請求的源IP和埠,第一個DatagramSocket用來和所有客戶端收發資料,後m*n個用來和真實伺服器收發資料。

總的來說,有一點很重要,那就是同一會話使用同一個DatagramSocket,不同的會話使用不同的DatagramSocket,每一個UDP有兩個會話,其中客戶端和代理伺服器的會話是所有客戶端公用一個DatagramSocket,就像TCP裡面的ServerSocket,代理伺服器和真實伺服器之間每個會話使用不同DatagramSocket,就像TCP裡面為每個連線new的SOcket物件。當真實伺服器響應了資料之後,一定要弄清楚該資料是發給哪個客戶端的,而辨別的要點就是埠號,將代理端開放的埠和客戶端傳送的埠做一一對映的關係表,就不會錯亂了。

打成Jar包並且把Main.java設為主類,然後執行下面語句(JRE 1.7以上版本)

單個數據包收發(一發必有一收)

java -jar tcptunnel-0.1.0.jar 53 8.8.8.8 53 --udp-dns
多個數據包收發(流式傳送)
java -jar tcptunnel-0.1.0.jar 7000 xxx.xxx.xxx.xxx 9999 --udp-tun

相關推薦

JAVA實現UDP反向代理

用JAVA實現TCP協議的反向代理非常容易,只用不到100行程式碼就能搞定,每一個連線只需兩個Socket,3條執行緒,進行輸入流與輸出流之間互相讀寫就可以了,可以承載所有TCP協議層以上的流量,比如HTTP(s),FTP,sFTP,郵件,即時通訊等等。其效果和HAProx

java 實現udp通訊

ket 地址 upd void ESS util dst 服務端 unknown 需求:應用A(通常有多個)和應用B(1個)進行 socket通訊,應用A必須知道應用B的ip地址(在應用A的配置文件中寫死的),這個時候就必須把應用B的ip設成固定ip(但是某些時候如更換路由

linux+apache+nginx實現反向代理動靜分離

在我們開發的過程中,一定會遇到,負載均衡方面的問題。下面我們,做一個小例子:使用nginx+apache實現反向代理,動靜分離。 這裡apache、php、nginx的安裝就不做贅述了,不懂的朋友可以看看我其他的文章,或者去百度搜索瞭解一下。 現在,我們的電腦上有apache、nginx、php,其中ph

Mac與Linux 實現nginx的安裝 與 訪問虛擬機器裡的nginx 實現反向代理網站及檔案目錄

一             現在主要實現的是 nginx   反向代理功能,      首先 , 先在虛擬機器裡的系統裝 nginx  Note:我用的是Cent

java獲取nginx反向代理後瀏覽器的真實ip

若用nginx做反向代理後,直接用String ip = request.getRemoteAddr(); 獲取的將是nginx伺服器所在ip地址,不能獲取瀏覽器真實ip地址! 第一步:在nginx中新增如下配置:     proxy_

centos7.2 原始碼編譯安裝nginx,實現tcp反向代理,不中斷服務新增編譯模組

   我們很多時候,需要將區域網內伺服器叢集中的某臺機器的某個埠對映到外網,可以直接通過代理伺服器連線到區域網內的電腦,進行操作。而nginx除了能實現http的反向代理外和負載均衡外,還能實現tcp的

Nginx實現TCP反向代理

    預設Nginx只支援http的反向代理,要想nginx支援tcp的反向代理,還需要在編譯時增加tcp代理模組支援,即nginx_tcp_proxy_module    下面操作步驟只讓nginx支援tcp_proxy,沒有加入prce、gzip、ssl等功能,如需要,

Java實現UDP服務端與多客戶端連線

學習的課程,程式比較簡單,直接上程式碼。UDPServerpackage com.imooc; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocke

利用IIS7實現網站反向代理功能

最近公司網站備案,遇到一個難題:公司域名註冊地為北京,但是實際上為了運營維護方便,主機已經移到重慶IDC機房,問題來了,年度審查備案要核實站點ip資訊,但是現在網站ip是重慶這邊的ip,如果要備案通過,就要改動ip資訊! 以往的做法是將站點資料全部轉移到北京機房來,帶來的

Nginx實現https反向代理配置

一些對安全性要求比較高的站點,可能會使用 HTTPS(一種使用ssl通訊標準的安全HTTP協議)。   先了解一些http相關的概念:  HTTP:是網際網路上應用最為廣泛的一種網路協議,是一個客戶端和伺服器端請求和應答的標準(TCP),用於從WWW伺服器傳輸超文字到本地

Go | Go 結合 Consul 實現動態反向代理

Go 結合 Consul 實現動態反向代理 代理的核心功能可以用一句話概括:接受客戶端的請求,轉發到後端伺服器,獲得應答之後返回給客戶端。 --- **Table of Contents** - [反向代理](#反向代理) - [實現邏輯](#實現邏輯) - [Go 語言實現](#go-語言實現)

java實現反向代理服務器

response connect enc char exception pos httputil let commons 1.寫的一個簡單的例子 package forword; import java.io.IOException; import javax.ser

Spring Boot + Java爬蟲 + 部署到Linux(八、Nginx實現反向代理、動靜分離和websocket處理)

    Nginx (engine x) 是一個高效能的HTTP和反向代理伺服器,也是一個IMAP/POP3/SMTP伺服器。所以,我們就用Nginx來實現反向代理和動靜分離的功能。    反向代理,通過搜尋、百科也可以大概知道。不過因為同為代理,所以總是和正向的代理區分不了

Keepalived實現高可用Nginx反向代理

keepalived實現高可用nginx反向代理由於好久沒有接觸過負載相關的調試了復習一下實驗系統:(1)CentOS 6.5_x86_64;(2)共有二臺主機,本實驗以ip地址來命名主機,即10主機、11主機。實驗前提:防火墻和selinux都關閉,主機之間時間同步實驗軟件:nginx-1.10.2-1.e

Linux之使用MogileFS分布式文件系統並使用nginx實現反向代理

mogilefs與nginx的點點滴滴MogileFS是一套高效的文件自動備份組件,由Six Apart開發,廣泛應用在包括LiveJournal等web2.0站點上。 MogileFS的特性: 工作在應用層,無單點,自動文件復制(復制的最小單位是class,而不是文件),傳輸中立且使用nfs或者http協議

EG:nginx反向代理兩臺web服務器,實現負載均衡 所有的web服務共享一臺nfs的存儲

分享 代理服 /dev/ 負載均衡 chmod 修改 修改配置 防火墻 usr step1: 三臺web服務器環境配置:iptables -F; setenforce 0 關閉防火墻;關閉setlinux step2:三臺web服務器 裝軟件 step3:主機修改配置文件

Nginx+Tomcat反向代理利用certbot實現https

per share 反向 oot 一段 new gree package cti 一、利用Let‘s Encrypt 免費生成HTTPS證書 1、下載安裝certbot(Let‘s Encrypt ) 2、利用certbot生成證書 3、配置nginx的https證書 安裝

Nginx反向代理實現會話(session)保持的兩種方式 (轉)

upstream 適用於 反向代理 ip_hash 負載 amp 丟失 tail 基於 http://blog.csdn.net/gaoqiao1988/article/details/53390352 一、ip_hash: ip_hash使用源地址哈希算法,將同一客戶

初識nginx反向代理和緩存機制(簡單實現

修改 請求轉發 b- nginx bin text pan types con 實現的需求圖: 環境: nginx緩存和反向代理服務器:192.168.0.224 實際存儲數據機器:192.168.0.37 一、實現反向代理 1、安裝nginx,兩臺服務器

nginx實現反向代理負載均衡

反向代理負載均衡 Nginx實現反向代理 nginx代理基於是ngx_http_proxy_module模塊的功能,該模塊有很多屬性配置選項,如: proxy_pass:指定將請求代理至server的URL路徑; proxy_set_header:將發送至 server的報文的某首部進行重寫