上班摸鱼用:Java 网络编程实战- 用socket 写一个网络中间件(有源码)

目录

前言

正文

1.绑定端口

2.解析请求头

3.连接到目标主机

4.盲转发

5.关于加密(重点)

6.如何加密

7.小结

8.源码


前言

        网络代理在工作和生活中十分常见,最经典的应用就是nginx,当然还有Charles这种抓包软件,等。使用代理主要就是达到不直接访问网络的目的,或用于特殊的应用场景,说到底代理最大的作用就是去自定义网络,嗯... 至于别的用途,自己去发挥...

        想到或许很少有人会用Java去做这样一款软件,而我自己也有这样的需求,并利用业余时间搞了一个雏形,并在此记录之。

正文

    要想用代理的方式接管操作系统的网络,就需去设置系统的代理IP和端口,Win,Mac,Android,IOS,Linux都可以手工去设置,很简单这里不赘述(Win,Mac可以用脚本来做,脚本放在了源码里)。设置代理之后,操作系统的网络都流向你的设置的IP和端口,此时你就可以用Socket绑定到这个端口上就能收到所有的请求了,收到这些请求之后,你需要判断请求内容,比如这是http还是其他请求,如果是Http的话,它需要解析出目标地址,解析出目标地址之后再开启一个新的socket连接到目标地址,并把你收到的本地数据通过这个新的socket发送到目标服务器,再把目标服务器的响应的数据写入本地的socket中,这样一个来回就完成了代理的整个过程,比较简单,原理图大概如下:

值得注意的是代理程序可以运行在任何PC或者服务器上(把你电脑代理ip和端口设置到这个上就行),只要你的电脑能访问得到它的IP和端口就行,如果要速度快,自然是运行在你本机了。

1.绑定端口

        用一个ServerSocket 绑定端口:当你为你的计算机设置好端口和IP(本机的话自然是设置127.0.0.1)之后,你绑定好ServerSocket 就开始工作了,代码如下:

 ServerSocket serverSocket = new ServerSocket(8888);
 Socket clientSocket=serverSocket .accept();

2.解析请求头

       此时当操作系统发出任何TCP请求都会被这个clientSocket接住,当然其他的tcp请求也是如此,这里只讲基于http的(不同的协议需要自定去解析),比如你的浏览器打开了一个xxxx.com的网站,那么他的数据流就会流到clientSocket的InputStream上,如果你打印出clientSocket 的InputStream的第一条读到的数据可能是如下样子:

 这个就是Http的请求头,如果是Post请求,经过代理后就变成后CONNECT,这是代理的重要标志,如果是Get请求,这里不会改变依然是Get,如下图:

 

         幸运的是即便是Https也不会对常规请求头进行加密,当然这个请求头也并不会暴露具体主机的请求Path,有了这个请求头信息,我们就能够知道当前这个请求要去到哪里了。

3.连接到目标主机

        从上一步的请求头中解析出目标地址IP以及端口,通常情况下Http端口是80,有些浏览器发出的请求不带端口,你可以默认为80,但Https一定会带端口,而且一般是443,如上图,我们就知道这个socket请求代表的请求是想访问xxx.com的80端口,方法是GET,此时我们就应该新建一个socket并连接到xxx.com端口为80,例如:

 Socket requestSocket=new Socket(“xxx.com”,80)

4.盲转发

      到此为止,我们就可以把被代理的电脑的网络请求发出去了,如果是Http的话可以直接把clientSocket的InputStream数据write到requestSocket的OutputStream,再把requestSocket的InputStream数据write到clientSocket的OutputStream,此时就是一个简单的流中数据拷贝,非常简单,唯一需要注意的是如果使用传统的IO,Socket的数据需要放到线程中,否则会因为阻塞,导致效率低下卡死,所以这个数据的拷贝实际上需要开启2个线程,当然NIO的话,可以只需要一个或者多个请求共用一个线程,如果是Https的话,处理略有不同,但很关键,

      不同点1:代理服务器通过解析头发现是443端口的话,证明这是https请求,首先要响应一个特殊的字符数据(发送给被代理端clientSocket的OutputStream):

"HTTP/1.1 200 Connection Established\r\n\r\n"

可以理解为这是一个简单的小协议,只有响应了这个特殊的数据后,浏览器才会和目标主机开始交换数据,Https的数据交互流程才能正常进行,Http则不需要这个步骤。

      不同点2:https不能把这个头写到目标主机,具体什么原因我并没有搞明白,看了好多资料并没有提到这个点,也是我踩的坑,但是除了这个头以外的数据都要原样写到目标主机。

5.关于加密(重点)

        代理本身并不会改变什么,换句话说通用的代理本身并不会加密,比如你上班的时候看B站,可能会被网管点名,根本原因就是因为Http请求具有明显的头信息,非常容易被侦测到,实际上所有的通过用的协议,理论上都可以被侦测到,但是私有协议呢,加密呢?

       比如我发送的GET请求,请求头肯定有GET请求三个字符,但是通过加密后可能变成了YES,这样就不会被轻易发现了,那么此刻问题来了,加密之后别的WEB服务器也不认识啊,这就需要再有一个外部服务器接收到你的加密信息再解密,再转发请求,回传响应。

       此刻代理程序至少是两个,一个本机的代理程序负责截获本机(被代理端)的请求,并加密转发到另一个计算机(代理端),此时代理端已经并不是内网的计算机,不受内网网关监控,可以自由访问其他网站,从而可以去到任何你想去的网站,SO,上班有可以摸鱼了,还不会被老板发现,爽歪歪。

6.如何加密

       加密的方式方法很多,无非就是把A字节变成B字节然后再到A字节(这种情况下),因此只要能对称加解密就行(说白了就是共用一个密码本),甚至可以把密码交换写入到代理过程中,但是比较麻烦。

       如何共用密码本:我的做法是把-128到+127 做成2个字节ArrayList,数组的内容绝对不能重复,这2个数组就是一个简单的一一对应关系,定时调用一下Collections.shuffle,保证代理端和被代理端都是同一个种子参数:

Random rand = new Random(getSeed());
	
Collections.shuffle(bytes1, rand);
Collections.shuffle(bytes2, rand);

getSeed 只要参数一样,rand的结果和shuffle结果也是一样的,这样服务器端和客户端“摇出来”的结果就是一样,有点“量子纠缠”的意思,时间就是一个很好的seed,但是考虑到请求时间存在间隔,建议shuffle的seed 取更大的时间单位,比如年的话,就是一年变意思,月的话就是1月变意思,周的话就是一周变一次,天的话,就是一天变一次,考虑到一个请求可能为几十毫秒到几百毫秒,seed单位最好不要取到秒级别的,可以很好的避开客户端和服务器端密码本不一致的问题,当然方法很多......

7.小结

掌握代理的本质过程,你可以做更多有意义的事,比如你也可以像nginx那样,在服务器做反向代理,端口转发,负载均衡,dns修改,流量监控,行为监控等等.....

8.源码

https://github.com/woshiwzy/OpenProxyJ

猜你喜欢

转载自blog.csdn.net/wang382758656/article/details/123098032