netty系列之:小白福利!手把手教你做一個簡單的代理服務器

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"簡介","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"愛因斯坦說過:所有的偉大,都產生於簡單的細節中。netty爲我們提供瞭如此強大的eventloop、channel通過對這些簡單東西的有效利用,可以得到非常強大的應用程序,比如今天要講的代理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"代理和反向代理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相信只要是程序員應該都聽過nginx服務器了,這個超級優秀nginx一個很重要的功能就是做反向代理。那麼有小夥伴要問了,有反向代理肯定就有正向代理,那麼他們兩個有什麼區別呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先講一下正向代理,舉個例子,最近流量明星備受打擊,雖然被打壓,但是明星就是明星,一般人是見不到的,如果有人需要跟明星對話的話,需要首先經過明星的經紀人,有經紀人將話轉達給明星。這個經紀人就是正向代理。我們通過正向代理來訪問要訪問的對象。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼什麼是反向代理呢?比如現在出現了很多人工智能,假如我們跟智能機器人A對話,然後A把我們之間的對話轉給了後面的藏着的人,這個人用他的智慧,回答了我們的對話,交由智能機器人A輸出,最終實現了人工智能。這個過程就叫做反向代理。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"netty實現代理的原理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼在netty中怎麼實現這個代理服務器呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首選我們首先代理服務器是一個服務器,所以我們需要在netty中使用ServerBootstrap創建一個服務器:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"EventLoopGroup bossGroup = new NioEventLoopGroup(1);\n EventLoopGroup workerGroup = new NioEventLoopGroup();\n try {\n ServerBootstrap b = new ServerBootstrap();\n b.group(bossGroup, workerGroup)\n .channel(NioServerSocketChannel.class)\n .handler(new LoggingHandler(LogLevel.INFO))\n .childHandler(new SimpleDumpProxyInitializer(REMOTE_HOST, REMOTE_PORT))\n .childOption(ChannelOption.AUTO_READ, false)\n .bind(LOCAL_PORT).sync().channel().closeFuture().sync();\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在這個local服務器中,我們傳入ProxyInitializer。在這個handler初始化器中,我們傳入自定義的handler:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" public void initChannel(SocketChannel ch) {\n ch.pipeline().addLast(\n new LoggingHandler(LogLevel.INFO),\n new SimpleDumpProxyInboundHandler(remoteHost, remotePort));\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在自定義的handler中,我們使用Bootstrap創建一個client,用來連接遠程要代理的服務器,我們將這個client端的創建放在channelActive方法中:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"// 開啓outbound連接\n Bootstrap b = new Bootstrap();\n b.group(inboundChannel.eventLoop())\n .channel(ctx.channel().getClass())\n .handler(new SimpleDumpProxyOutboundHandler(inboundChannel))\n .option(ChannelOption.AUTO_READ, false);\n ChannelFuture f = b.connect(remoteHost, remotePort);\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後在client建立好連接之後,就可以從inboundChannel中讀取數據了:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":"outboundChannel = f.channel();\n f.addListener(future -> {\n if (future.isSuccess()) {\n // 連接建立完畢,讀取inbound數據\n inboundChannel.read();\n } else {\n // 關閉inbound channel\n inboundChannel.close();\n }\n });\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"因爲是代理服務,所以需要將inboundChannel讀取的數據,轉發給outboundChannel,所以在channelRead中我們需要這樣寫:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" public void channelRead(final ChannelHandlerContext ctx, Object msg) {\n // 將inboundChannel中的消息讀取,並寫入到outboundChannel\n if (outboundChannel.isActive()) {\n outboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {\n if (future.isSuccess()) {\n // flush成功,讀取下一個消息\n ctx.channel().read();\n } else {\n future.channel().close();\n }\n });\n }\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當outboundChannel寫成功之後,再繼續inboundChannel的讀取工作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"同樣對於client的outboundChannel來說,也有一個handler,在這個handler中,我們需要將outboundChannel讀取到的數據反寫會inboundChannel中:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"codeblock","attrs":{"lang":"text"},"content":[{"type":"text","text":" public void channelRead(final ChannelHandlerContext ctx, Object msg) {\n // 將outboundChannel中的消息讀取,並寫入到inboundChannel中\n inboundChannel.writeAndFlush(msg).addListener((ChannelFutureListener) future -> {\n if (future.isSuccess()) {\n ctx.channel().read();\n } else {\n future.channel().close();\n }\n });\n }\n","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當inboundChannel寫成功之後,再繼續outboundChannel的讀取工作。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如此一個簡單的代理服務器就完成了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"實戰","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們將本地的8000端口,代理到www.163.com的80端口,會發生什麼情況呢?運行我們的程序,訪問http://localhost:8000, 我們會看到下面的頁面:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a9/a9a954e6c723151b6c0bc931a1fd27d6.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼沒有如我們想象的那樣展示正常的頁面呢?那是因爲我們代理過去之後的域名是localhost,而不是正常的www.163.com, 所以服務器端不認識我們的請求,從而報錯。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"總結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的代理服務器之間簡單的轉發請求,並不能夠處理上述的場景,那麼該怎麼解決上面的問題呢? 敬請期待我的後續文章!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文的例子可以參考:","attrs":{}},{"type":"link","attrs":{"href":"https://github.com/ddean2009/learn-netty4","title":"","type":null},"content":[{"type":"text","text":"learn-netty4","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文已收錄於 ","attrs":{}},{"type":"link","attrs":{"href":"http://www.flydean.com/35-netty-simple-proxy/","title":"","type":null},"content":[{"type":"text","text":"http://www.flydean.com/35-netty-simple-proxy/","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最通俗的解讀,最深刻的乾貨,最簡潔的教程,衆多你不知道的小技巧等你來發現!","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"歡迎關注我的公衆號:「程序那些事」,懂技術,更懂你!","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章