通过Sockets对网络请求拦截并转发的思路和简单实例(Java)

2024-02-20 00:38

本文主要是介绍通过Sockets对网络请求拦截并转发的思路和简单实例(Java),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前一阵子突然对网络安全和数据传输这方面有了点兴趣,加上朋友介绍了新的CrossWall的工具,便也想自己做个小工具试试看。

因为我觉得如果只是简单的使用工具,而不去深入理解原理,作为一个程序员就不会有进步。

这里只是分享一下我的思路和简单的例子,并没有使用复杂的数据加密和协议(像SSR)。

但仍然需要购买海外服务器,具体哪个我就不介绍了,只要海外的都行。

 

一. 思路:

原理其实很简单,

①通过代理服务器拦截所有的本地Http和Https请求

②通过Sockets接收到请求后,截取请求头并加密(为了不让防火墙拦截),将加密后的请求头再拼回原请求,并发送到海外的服务器

③海外服务器接收到加密后的请求后,对header解密,并将请求发送到对应的目标服务器(如Google)

④将目标服务器返回的数据流加密后返回给本机

⑤将海外服务器返回的数据解密后返回给浏览器

第④⑤步加密解密我并没有做,因为我只是通过将header加密解密就通过了防火墙。

由此可见,防火墙似乎并没有对数据本身做Check,只是校验了Header。

这也可以理解,因为返回的数据本身是很难验证的,而且现在网络那么发达,如果对每一条请求做太多的验证会影响整个互联网的访问速度,防火墙服务器的处理压力也会变得很大。

防火墙能封国外IP还是因为它不仅能拦截,还能主动探测端口,这个就不在这里讨论了。

另外,我对Header的加密,也只是简单的将字节加了1位而已,毕竟加密不是这里讨论的重点。

 

二. 本机Client端:

2.1. 目录结构:

2.2. Client.java:

定义了客户端的启动界面,可以设置海外服务器的IP和Port。

/*** */
/*** @author Administrator**/
package ssr;import com.ice.jni.registry.RegistryException;
import ssr.com.*;import javax.swing.*;
import java.io.*;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;import java.awt.*;
import java.awt.event.*;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;/*
1.创建一个ServerSocket对象;
2.调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
3.从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
4.处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
5.处理响应:根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;
6.关闭Socket对象;
7.转到步骤2,继续等待连接请求;
*/public class Client implements ActionListener{//Specify the look and feel to use.  Valid values:  s//null (use the default), "Metal", "System", "Motif", "GTK+"final static String LOOKANDFEEL = "System";JButton jbutton;JLabel lblServer;JLabel lblPort;JLabel lblPassword;JTextField txtServer;JTextField txtPort;JTextField txtPassword;Boolean blnStart = false;static final int workerNumber = 4;//线程池保留数量,服务器为8核cpu,合适的数量应该小于8static final int maxPoolSize=256;//最大线程数量,即最大并发量static final int maxWorkerInQueue = 2500;// 最大工作队列数量static final int waitTime = 10;// 超时等待时间static final int listenPort=8788;static final String listenIP="127.0.0.1";static final String foreignIP="*.*.*.*";static final ThreadPoolExecutor tpe = new ThreadPoolExecutor(workerNumber,maxPoolSize, waitTime, TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(maxWorkerInQueue));public Client(){doShutDownWork();}public static void main(String[] args) {//Schedule a job for the event-dispatching thread://creating and showing this application's GUI.javax.swing.SwingUtilities.invokeLater(new Runnable() {public void run() {Client client = new Client();client.createAndShowGUI();}});}private void doShutDownWork() {Runtime.getRuntime().addShutdownHook(new Thread() {public void run() {Proxy proxy = new Proxy();try {// 设置代理服务器proxy.disableProxy();} catch (RegistryException ex) {ex.printStackTrace();}}});}public void actionPerformed(ActionEvent e) {if (!blnStart){jbutton.setText("Stop Proxy");blnStart = true;ThreadClient threadClient = new ThreadClient();threadClient.start();}else{// 退出System.exit(1);}}class ThreadClient extends Thread{@Overridepublic void run() {try {// 设置代理服务器Proxy proxy = new Proxy();// IE代理服务器proxy.changeProxy(listenIP, listenPort);} catch (Exception ex) {System.out.println("PC Proxy Server Setting Error:" + ex.getMessage());}try {// 开启本地代理服务器Client client = new Client();// 等待连接请求client.await(txtServer.getText(), txtPort.getText());} catch (Exception ex) {System.out.println("Proxy Client Error:" + ex.getMessage());}}}private static void initLookAndFeel() {String lookAndFeel = null;if (LOOKANDFEEL != null) {if (LOOKANDFEEL.equals("Metal")) {lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();} else if (LOOKANDFEEL.equals("System")) {lookAndFeel = UIManager.getSystemLookAndFeelClassName();} else if (LOOKANDFEEL.equals("Motif")) {lookAndFeel = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";} else if (LOOKANDFEEL.equals("GTK+")) { //new in 1.4.2lookAndFeel = "com.sun.java.swing.plaf.gtk.GTKLookAndFeel";} else {System.err.println("Unexpected value of LOOKANDFEEL specified: "+ LOOKANDFEEL);lookAndFeel = UIManager.getCrossPlatformLookAndFeelClassName();}try {UIManager.setLookAndFeel(lookAndFeel);} catch (ClassNotFoundException e) {System.err.println("Couldn't find class for specified look and feel:"+ lookAndFeel);System.err.println("Did you include the L&F library in the class path?");System.err.println("Using the default look and feel.");} catch (UnsupportedLookAndFeelException e) {System.err.println("Can't use the specified look and feel ("+ lookAndFeel+ ") on this platform.");System.err.println("Using the default look and feel.");} catch (Exception e) {System.err.println("Couldn't get specified look and feel ("+ lookAndFeel+ "), for some reason.");System.err.println("Using the default look and feel.");e.printStackTrace();}}}/*** Create the GUI and show it.  For thread safety,* this method should be invoked from the* event-dispatching thread.*/private void createAndShowGUI() {//Set the look and feel.---设置外观,可以忽略initLookAndFeel();//Make sure we have nice window decorations.//设置为false的话,即为不改变外观JFrame.setDefaultLookAndFeelDecorated(true);//Create and set up the window.JFrame frame = new JFrame("Client");frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//----------------------Pannel Components---------------------------Panel pn = new Panel(null);pn.setSize(800, 600);lblServer = new JLabel("Server");lblServer.setBounds(50, 30, 100, 30);pn.add(lblServer);txtServer = new JTextField(listenIP);txtServer.setBounds(150, 30, 100, 30);pn.add(txtServer);JLabel lblForeignIP = new JLabel(foreignIP);lblForeignIP.setBounds(300, 30, 100, 30);pn.add(lblForeignIP);lblPort = new JLabel("Port");lblPort.setBounds(50, 80, 100, 30);pn.add(lblPort);txtPort = new JTextField("8888");txtPort.setBounds(150, 80, 100, 30);pn.add(txtPort);lblPassword = new JLabel("Password");lblPassword.setBounds(50, 130, 100, 30);pn.add(lblPassword);txtPassword = new JTextField("sun");txtPassword.setBounds(150, 130, 100, 30);pn.add(txtPassword);jbutton = new JButton("Start Proxy");jbutton.setMnemonic(KeyEvent.VK_I);jbutton.addActionListener(this);jbutton.setBounds(100, 180, 200, 30);pn.add(jbutton);//----------------------Pannel Components---------------------------//Display the window.frame.add(pn);frame.pack();frame.setVisible(true);frame.setSize(400, 280);}public void await(String serverIP, String serverPort) throws IOException {// 创建一个ServerSocket对象ServerSocket serverSocket = null;try {//服务器套接字对象serverSocket = new ServerSocket(listenPort, 1, InetAddress.getByName(listenIP));} catch (IOException e) {e.printStackTrace();System.exit(1);}// 循环等待一个请求while (true) {Socket socket = null;try {socket = serverSocket.accept();socket.setKeepAlive(true);//加入任务列表,等待处理ClinetProxy cp = new ClinetProxy(socket, serverIP, serverPort);Thread t = new Thread(cp);t.start();}catch (Exception e) {e.printStackTrace();}}}
}

2.3. Register.java:

通过register.jar和ICE_JNIRegistry.dll修改注册表,设置代理服务器拦截Http和Https请求。

package ssr.com;import com.ice.jni.registry.RegDWordValue;
import com.ice.jni.registry.RegStringValue;
import com.ice.jni.registry.Registry;
import com.ice.jni.registry.RegistryException;
import com.ice.jni.registry.RegistryKey;
import ssr.Client;import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;public class Register {// 把ICE_JNIRegistry.dll在的路径加载到java.library.path中,这里是放在classpath下面了static {// ①编译成Jar包后的DLL路径设置String basePath = new Register().getClass().getProtectionDomain().getCodeSource().getLocation().getPath();try {basePath = URLDecoder.decode(basePath,"utf-8");} catch (UnsupportedEncodingException e) {e.printStackTrace();}if(basePath.endsWith(".jar")){basePath = basePath.substring(0,basePath.lastIndexOf("/")+1);}File f = new File(basePath);basePath = f.getAbsolutePath();  //得到windows下的正确路径System.setProperty("java.library.path", basePath+"/");// ②本机调试用//System.setProperty("java.library.path", Register.class.getResource("/").getPath());}public String getValue(String folder, String subKeyNode, String subKeyName)throws SecurityException,IllegalArgumentException,RegistryException {RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);RegistryKey subKey = software.openSubKey(subKeyNode);String value = subKey.getStringValue(subKeyName);subKey.closeKey();return value;}public int getIntValue(String folder, String subKeyNode, String subKeyName)throws SecurityException,IllegalArgumentException,RegistryException {RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);RegistryKey subKey = software.openSubKey(subKeyNode);int value = ((RegDWordValue) subKey.getValue(subKeyName)).getData();subKey.closeKey();return value;}public boolean setIntValue(String folder, String subKeyNode,String subKeyName, int subKeyValue) throws RegistryException {RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);RegistryKey subKey = software.createSubKey(subKeyNode, "");RegDWordValue value = new RegDWordValue(subKey, subKeyName);value.setData(subKeyValue);subKey.setValue(value);subKey.flushKey();subKey.closeKey();return true;}public boolean setValue(String folder, String subKeyNode,String subKeyName, String subKeyValue) throws RegistryException {RegistryKey software = Registry.HKEY_CURRENT_USER.openSubKey(folder);RegistryKey subKey = software.createSubKey(subKeyNode, "");subKey.setValue(new RegStringValue(subKey, subKeyName, subKeyValue));subKey.flushKey();subKey.closeKey();return true;}
}

注意测试的时候和打成Jar包以后,static方法不太一样。

2.4. Proxy.java:

代理服务器的修改方法类

package ssr.com;import com.ice.jni.registry.NoSuchKeyException;
import com.ice.jni.registry.NoSuchValueException;
import com.ice.jni.registry.RegistryException;public class Proxy {private static String folder = "SOFTWARE";private static String subKeyNode = "Microsoft\\Windows\\CurrentVersion\\Internet Settings";private static String subKeyNameServer = "ProxyServer";private static String subKeyNameEnable = "ProxyEnable";private static String subKeyNameOverride = "ProxyOverride";private static String subKeyOverrideValue = "<local>";private int originProxyEnable;private String originProxyServer;private String originProxyOverride;private Register register = new Register();public boolean backToOriginValue() {try {register.setIntValue(folder, subKeyNode, subKeyNameEnable,originProxyEnable);register.setValue(folder, subKeyNode, subKeyNameServer,originProxyServer);try {System.out.println("backed key: "+ register.getValue(folder, subKeyNode,subKeyNameServer)+ " "+ register.getIntValue(folder, subKeyNode,subKeyNameEnable));} catch (SecurityException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();}register.setValue(folder, subKeyNode, subKeyNameOverride,originProxyOverride);} catch (NoSuchKeyException e) {e.printStackTrace();return false;} catch (RegistryException e) {e.printStackTrace();return false;}return true;}public boolean changeProxy(String proxyIp, int proxyPort) {try {enableProxy();setProxy(proxyIp, proxyPort);try {System.out.println("after change key: "+ register.getValue(folder, subKeyNode,subKeyNameServer)+ " "+ register.getIntValue(folder, subKeyNode,subKeyNameEnable));} catch (SecurityException e) {e.printStackTrace();} catch (IllegalArgumentException e) {e.printStackTrace();}setOverride();} catch (NoSuchKeyException e) {e.printStackTrace();return false;} catch (RegistryException e) {e.printStackTrace();return false;}return true;}public boolean saveOriginValue() {try {originProxyServer = register.getValue(folder, subKeyNode,subKeyNameServer);originProxyEnable = register.getIntValue(folder, subKeyNode,subKeyNameEnable);System.out.println("save origin value: " + originProxyServer + " "+ originProxyEnable);} catch (SecurityException e) {e.printStackTrace();return false;} catch (IllegalArgumentException e) {e.printStackTrace();return false;} catch (NoSuchKeyException e) {e.printStackTrace();return false;} catch (RegistryException e) {e.printStackTrace();return false;}// 没有勾选跳过本地代理服务器时,没有proxyoverride,此时保存为“”,并且返回truetry {originProxyOverride = register.getValue(folder, subKeyNode,subKeyNameOverride);} catch (SecurityException e) {e.printStackTrace();return false;} catch (IllegalArgumentException e) {e.printStackTrace();return false;} catch (NoSuchKeyException e) {e.printStackTrace();return false;} catch (NoSuchValueException e) {originProxyOverride = "";return true;} catch (RegistryException e) {e.printStackTrace();return false;}return true;}public void enableProxy() throws NoSuchKeyException, RegistryException {register.setIntValue(folder, subKeyNode, subKeyNameEnable, 1);}public void disableProxy() throws NoSuchKeyException, RegistryException {register.setIntValue(folder, subKeyNode, subKeyNameEnable, 0);}private void setProxy(String ip, int port) throws NoSuchKeyException,RegistryException {register.setValue(folder, subKeyNode, subKeyNameServer, ip + ":" + port);}private void setOverride() throws NoSuchKeyException, RegistryException {register.setValue(folder, subKeyNode, subKeyNameOverride,subKeyOverrideValue);}public void setRegister(Register register) {this.register = register;}}  

2.5. ClientProxy.java:

将客户端发送过来的数据转发海外服务器,并将海外服务器返回的数据转发给客户端。

package ssr.com;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 将客户端发送过来的数据转发给海外服务器,并将海外服务器返回的数据转发给客户端**/
public class ClinetProxy implements Runnable {private Socket socketIn;private Socket socketOut;private long totalUpload=0l;//总计上行比特数private long totalDownload=0l;//总计下行比特数private String serverIP;private String serverPort;public ClinetProxy(Socket socket, String serverIP, String serverPort) {this.socketIn = socket;this.serverIP = serverIP;this.serverPort = serverPort;}private static final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");/** 已连接到请求的服务器 */private static final String AUTHORED = "HTTP/1.1 200 Connection established\r\n\r\n";/** 内部错误 */private static final String SERVERERROR = "HTTP/1.1 500 Connection FAILED\r\n\r\n";@Overridepublic void run() {StringBuilder builder=new StringBuilder();try {builder.append("\r\n").append("Request Time  :" + sdf.format(new Date()));InputStream isIn = socketIn.getInputStream();OutputStream osIn = socketIn.getOutputStream();// 设置外部服务器主机和端口socketOut = new Socket(serverIP, Integer.parseInt(serverPort));socketOut.setKeepAlive(true);InputStream isOut = socketOut.getInputStream();OutputStream osOut = socketOut.getOutputStream();//新开一个线程将返回的数据转发给客户端//由于涉及到TCP协议,必须采用异步的方式,同时收发数据,并不是等所有Input结束后再OutputThread stc = new ServerToClientDataSendThread(isOut, osIn);stc.start();// 对header加密HttpHeaderClient header = HttpHeaderClient.readHeader(isIn);byte[] headerData=header.toString().getBytes();osOut.write(codeUtils.enCode(headerData));osOut.flush();//读取客户端请求过来的数据转发给服务器Thread cts = new ClientToServerDataSendThread(isIn, osOut);cts.start();cts.join();//等待向客户端转发的线程结束stc.join();  // 将服务器返回的数据写回客户端,主线程等待ot结束再关闭。} catch (Exception e) {e.printStackTrace();if(!socketIn.isOutputShutdown()){//如果还可以返回错误状态的话,返回内部错误try {socketIn.getOutputStream().write(SERVERERROR.getBytes());} catch (IOException e1) {}}} finally {try {if (socketIn != null) {socketIn.close();}} catch (IOException e) {}if (socketOut != null) {try {socketOut.close();} catch (IOException e) {}}//纪录上下行数据量和最后结束时间并打印builder.append("\r\n").append("Up    Bytes  :" + totalUpload);builder.append("\r\n").append("Down  Bytes  :" + totalDownload);builder.append("\r\n").append("Closed Time  :" + sdf.format(new Date()));builder.append("\r\n");logRequestMsg(builder.toString());}}/*** 避免多线程竞争把日志打串行了* @param msg*/private synchronized void logRequestMsg(String msg){System.out.println(msg);}/*** 将客户端返回的数据转发给服务器端**/class ClientToServerDataSendThread extends Thread {private InputStream isIn;private OutputStream osOut;ClientToServerDataSendThread(InputStream isIn, OutputStream osOut) {this.isIn = isIn;this.osOut = osOut;}@Overridepublic void run() {byte[] buffer = new byte[4096];try {int len;while ((len = isIn.read(buffer)) != -1) {if (len > 0) {osOut.write(buffer, 0, len);}totalUpload+=len;if (socketIn.isClosed() || socketOut.isClosed()) {break;}}osOut.flush();osOut.close();} catch (Exception e) {try {socketOut.close();// 尝试关闭远程服务器连接,中断转发线程的读阻塞状态} catch (IOException e1) {System.out.println(e.getMessage());}System.out.println(e.getMessage());}finally{}}}/*** 将服务器端返回的数据转发给客户端**/class ServerToClientDataSendThread extends Thread {private InputStream isOut;private OutputStream osIn;ServerToClientDataSendThread(InputStream isOut, OutputStream osIn) {this.isOut = isOut;this.osIn = osIn;}@Overridepublic void run() {byte[] buffer = new byte[4096];try {int len;while ((len = isOut.read(buffer)) != -1) {if (len > 0) {// logData(buffer, 0, len);osIn.write(buffer, 0, len);totalDownload+=len;}if (socketIn.isOutputShutdown() || socketOut.isClosed()) {break;}}osIn.flush();osIn.close();} catch (Exception e) {System.out.println(e.getMessage());}}}}

2.6. HttpHeaderClient.java

客户端解析头部

package ssr.com;import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;/*** 解析头部信息**/
public final class HttpHeaderClient {private List<String> header=new ArrayList<String>();private String method;private String host;private String port;public static final int MAXLINESIZE = 4096;public static final String METHOD_GET="GET";public static final String METHOD_POST="POST";public static final String METHOD_CONNECT="CONNECT";private HttpHeaderClient(){}/*** 从数据流中读取请求头部信息,必须在放在流开启之后,任何数据读取之前* @param in* @return* @throws IOException*/public static final HttpHeaderClient readHeader(InputStream in) throws IOException {HttpHeaderClient header = new HttpHeaderClient();StringBuilder sb = new StringBuilder();//先读出交互协议来,char c = 0;while ((c = (char) in.read()) != '\n') {sb.append(c);if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段break;}}//如能识别出请求方式则则继续,不能则退出if(header.addHeaderMethod(sb.toString())!=null){do {sb = new StringBuilder();while ((c = (char) in.read()) != '\n') {sb.append(c);if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段break;}}if (sb.length() > 1 && header.notTooLong()) {//如果头部包含信息过多,抛弃剩下的部分header.addHeaderString(sb.substring(0, sb.length() - 1));} else {break;}} while (true);}return header;}/**** @param str*/private void addHeaderString(String str){str=str.replaceAll("\r", "");header.add(str);if(str.startsWith("Host")){//解析主机和端口String[] hosts= str.split(":");host=hosts[1].trim();if(method.endsWith(METHOD_CONNECT)){port=hosts.length==3?hosts[2]:"443";//https默认端口为443}else if(method.endsWith(METHOD_GET)||method.endsWith(METHOD_POST)){port=hosts.length==3?hosts[2]:"80";//http默认端口为80}System.out.println("Header:" + str);}}/*** 判定请求方式* @param str* @return*/private String addHeaderMethod(String str){str=str.replaceAll("\r", "");header.add(str);if(str.startsWith(METHOD_CONNECT)){//https链接请求代理method=METHOD_CONNECT;}else if(str.startsWith(METHOD_GET)){//http GET请求method=METHOD_GET;}else if(str.startsWith(METHOD_POST)){//http POST请求method=METHOD_POST;}return method;}@Overridepublic String toString() {StringBuilder sb=new StringBuilder();for(String str : header){sb.append(str).append("\r\n");}sb.append("\r\n");return sb.toString();}public boolean notTooLong(){return header.size()<=16;}
}

2.7. codeUtils.java

加密解密类

package ssr.com;public class codeUtils {public static byte[] enCode(byte[] bytesIn){byte[] bytesOut = new byte[bytesIn.length];int i = 0;for(byte bt:bytesIn){bytesOut[i] = (byte)(bt+1);i++;}return bytesOut;}public static char deCodeChar(char charIn){char charOut = (char)((int)charIn-1);return charOut;}
}

 

三. 海外服务器Server端:

3.1. Server.java

海外服务器的Socket启动程序

/*** */
/*** @author Administrator**/
package ssr;import ssr.com.*;import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;/*
1.创建一个ServerSocket对象;
2.调用ServerSocket对象的accept方法,等待连接,连接成功会返回一个Socket对象,否则一直阻塞等待;
3.从Socket对象中获取InputStream和OutputStream字节流,这两个流分别对应request请求和response响应;
4.处理请求:读取InputStream字节流信息,转成字符串形式,并解析,这里的解析比较简单,仅仅获取uri(统一资源标识符)信息;
5.处理响应:根据解析出来的uri信息,从WEB_ROOT目录中寻找请求的资源资源文件, 读取资源文件,并将其写入到OutputStream字节流中;
6.关闭Socket对象;
7.转到步骤2,继续等待连接请求;
*/public class Server {static final int listenPort=8888;static final String listenIP="0.0.0.0";public static void main(String[] args) {// 开启客户端代理服务器Server server = new Server();// 等待连接请求server.await();}public void await() {// 创建一个ServerSocket对象ServerSocket serverSocket = null;try {//服务器套接字对象serverSocket = new ServerSocket(listenPort, 1, InetAddress.getByName(listenIP));} catch (IOException e) {e.printStackTrace();System.exit(1);}// 循环等待一个请求while (true) {Socket socket = null;try {socket = serverSocket.accept();socket.setKeepAlive(true);//加入任务列表,等待处理ServerProxy sp = new ServerProxy(socket);Thread t = new Thread(sp);t.start();}catch (Exception e) {e.printStackTrace();}}}
}

3.2. ServerProxy.java

海外服务器的数据处理

package ssr.com;import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;/*** 将客户端发送过来的数据转发给请求的服务器端,并将服务器返回的数据转发给客户端**/
public class ServerProxy implements Runnable {private Socket socketIn;private Socket socketOut;private long totalUpload=0l;//总计上行比特数private long totalDownload=0l;//总计下行比特数public ServerProxy(Socket socket) {this.socketIn = socket;}private static final SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");/** 已连接到请求的服务器 */private static final String AUTHORED = "HTTP/1.1 200 Connection established\r\n\r\n";/** 本代理登陆失败(此应用暂时不涉及登陆操作) *///private static final String UNAUTHORED="HTTP/1.1 407 Unauthorized\r\n\r\n";/** 内部错误 */private static final String SERVERERROR = "HTTP/1.1 500 Connection FAILED\r\n\r\n";@Overridepublic void run() {StringBuilder builder=new StringBuilder();try {builder.append("\r\n").append("Request Time  :" + sdf.format(new Date()));InputStream isIn = socketIn.getInputStream();OutputStream osIn = socketIn.getOutputStream();//从客户端流数据中读取头部,获得请求主机和端口HttpHeaderServer header = HttpHeaderServer.readHeader(isIn);//添加请求日志信息builder.append("\r\n").append("From    Host  :" + socketIn.getInetAddress());builder.append("\r\n").append("From    Port  :" + socketIn.getPort());builder.append("\r\n").append("Proxy   Method:" + header.getMethod());builder.append("\r\n").append("Request Host  :" + header.getHost());builder.append("\r\n").append("Request Port  :" + header.getPort());//如果没解析出请求请求地址和端口,则返回错误信息if (header.getHost() == null || header.getPort() == null) {osIn.write(SERVERERROR.getBytes());osIn.flush();return ;}// 查找主机和端口socketOut = new Socket(header.getHost(), Integer.parseInt(header.getPort()));socketOut.setKeepAlive(true);InputStream isOut = socketOut.getInputStream();OutputStream osOut = socketOut.getOutputStream();//新开一个线程将返回的数据转发给客户端//由于涉及到TCP协议,必须采用异步的方式,同时收发数据,并不是等所有Input结束后再OutputThread stc = new ServerToClientDataSendThread(isOut, osIn);stc.start();if (header.getMethod().equals(HttpHeaderClient.METHOD_CONNECT)) {// 将已联通信号返回给请求页面osIn.write(AUTHORED.getBytes());osIn.flush();}else{//http请求需要将请求头部也转发出去byte[] headerData=header.toString().getBytes();totalUpload+=headerData.length;osOut.write(headerData);osOut.flush();}//读取客户端请求过来的数据转发给服务器Thread cts = new ClientToServerDataSendThread(isIn, osOut);cts.start();cts.join();//等待向客户端转发的线程结束stc.join();  // 将服务器返回的数据写回客户端,主线程等待ot结束再关闭。} catch (Exception e) {e.printStackTrace();if(!socketIn.isOutputShutdown()){//如果还可以返回错误状态的话,返回内部错误try {socketIn.getOutputStream().write(SERVERERROR.getBytes());} catch (IOException e1) {}}} finally {try {if (socketIn != null) {socketIn.close();}} catch (IOException e) {}if (socketOut != null) {try {socketOut.close();} catch (IOException e) {}}//纪录上下行数据量和最后结束时间并打印builder.append("\r\n").append("Up    Bytes  :" + totalUpload);builder.append("\r\n").append("Down  Bytes  :" + totalDownload);builder.append("\r\n").append("Closed Time  :" + sdf.format(new Date()));builder.append("\r\n");logRequestMsg(builder.toString());}}/*** 避免多线程竞争把日志打串行了* @param msg*/private synchronized void logRequestMsg(String msg){System.out.println(msg);}/*** 将客户端返回的数据转发给服务器端**/class ClientToServerDataSendThread extends Thread {private InputStream isIn;private OutputStream osOut;ClientToServerDataSendThread(InputStream isIn, OutputStream osOut) {this.isIn = isIn;this.osOut = osOut;}@Overridepublic void run() {byte[] buffer = new byte[4096];try {int len;while ((len = isIn.read(buffer)) != -1) {if (len > 0) {osOut.write(buffer, 0, len);}totalUpload+=len;if (socketIn.isClosed() || socketOut.isClosed()) {break;}}osOut.flush();osOut.close();} catch (Exception e) {try {socketOut.close();// 尝试关闭远程服务器连接,中断转发线程的读阻塞状态} catch (IOException e1) {System.out.println(e.getMessage());}System.out.println(e.getMessage());}finally{}}}/*** 将服务器端返回的数据转发给客户端**/class ServerToClientDataSendThread extends Thread {private InputStream isOut;private OutputStream osIn;ServerToClientDataSendThread(InputStream isOut, OutputStream osIn) {this.isOut = isOut;this.osIn = osIn;}@Overridepublic void run() {byte[] buffer = new byte[4096];try {int len;while ((len = isOut.read(buffer)) != -1) {if (len > 0) {// logData(buffer, 0, len);osIn.write(buffer, 0, len);totalDownload+=len;}if (socketIn.isOutputShutdown() || socketOut.isClosed()) {break;}}osIn.flush();osIn.close();} catch (Exception e) {System.out.println(e.getMessage());}}}}

3.3. HttpHeaderServer.java

海外服务器的头部处理

package ssr.com;import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;/*** 解析头部信息**/
public final class HttpHeaderServer {private List<String> header=new ArrayList<String>();private String method;private String host;private String port;public static final int MAXLINESIZE = 4096;public static final String METHOD_GET="GET";public static final String METHOD_POST="POST";public static final String METHOD_CONNECT="CONNECT";private HttpHeaderServer(){}/*** 从数据流中读取请求头部信息,必须在放在流开启之后,任何数据读取之前* @param in* @return* @throws IOException*/public static final HttpHeaderServer readHeader(InputStream in) throws IOException {HttpHeaderServer header = new HttpHeaderServer();StringBuilder sb = new StringBuilder();//先读出交互协议来,char c = 0;while ((c = codeUtils.deCodeChar((char) in.read())) != '\n') {sb.append(c);if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段break;}}//如能识别出请求方式则则继续,不能则退出if(header.addHeaderMethod(sb.toString())!=null){do {sb = new StringBuilder();while ((c = codeUtils.deCodeChar((char) in.read())) != '\n') {sb.append(c);if (sb.length() == MAXLINESIZE) {//不接受过长的头部字段break;}}if (sb.length() > 1 && header.notTooLong()) {//如果头部包含信息过多,抛弃剩下的部分header.addHeaderString(sb.substring(0, sb.length() - 1));} else {break;}} while (true);}return header;}/**** @param str*/private void addHeaderString(String str){str=str.replaceAll("\r", "");header.add(str);if(str.startsWith("Host")){//解析主机和端口String[] hosts= str.split(":");host=hosts[1].trim();if(method.endsWith(METHOD_CONNECT)){port=hosts.length==3?hosts[2]:"443";//https默认端口为443}else if(method.endsWith(METHOD_GET)||method.endsWith(METHOD_POST)){port=hosts.length==3?hosts[2]:"80";//http默认端口为80}System.out.println("Header:" + str);}}/*** 判定请求方式* @param str* @return*/private String addHeaderMethod(String str){str=str.replaceAll("\r", "");header.add(str);if(str.startsWith(METHOD_CONNECT)){//https链接请求代理method=METHOD_CONNECT;}else if(str.startsWith(METHOD_GET)){//http GET请求method=METHOD_GET;}else if(str.startsWith(METHOD_POST)){//http POST请求method=METHOD_POST;}return method;}@Overridepublic String toString() {StringBuilder sb=new StringBuilder();for(String str : header){sb.append(str).append("\r\n");}sb.append("\r\n");return sb.toString();}public boolean notTooLong(){return header.size()<=16;}public List<String> getHeader() {return header;}public void setHeader(List<String> header) {this.header = header;}public String getMethod() {return method;}public void setMethod(String method) {this.method = method;}public String getHost() {return host;}public void setHost(String host) {this.host = host;}public String getPort() {return port;}public void setPort(String port) {this.port = port;}}

3.4. pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>sun</groupId><artifactId>project</artifactId><version>1.0-SNAPSHOT</version><properties><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.source>1.8</maven.compiler.source><netty.version>4.1.25.Final</netty.version></properties><dependencies><!-- 添加servlet3.0核心包 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId><version>3.0.1</version></dependency><dependency><groupId>javax.servlet.jsp</groupId><artifactId>javax.servlet.jsp-api</artifactId><version>2.3.2-b01</version></dependency><!-- jstl --><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId><version>1.2</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-buffer</artifactId><version>${netty.version}</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-codec</artifactId><version>${netty.version}</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-codec-http</artifactId><version>${netty.version}</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-handler</artifactId><version>${netty.version}</version></dependency><dependency><groupId>io.netty</groupId><artifactId>netty-handler-proxy</artifactId><version>${netty.version}</version></dependency><dependency><groupId>org.bouncycastle</groupId><artifactId>bcpkix-jdk15on</artifactId><version>1.58</version></dependency><dependency><groupId>registry</groupId><artifactId>registry</artifactId><version>1.0</version><scope>system</scope><systemPath>${basedir}/lib/registry.jar</systemPath></dependency></dependencies><build><plugins><plugin><artifactId>maven-compiler-plugin</artifactId><version>3.1</version><configuration><source>1.7</source><target>1.7</target><encoding>utf8</encoding></configuration></plugin></plugins><resources><!--指定资源的位置 --><resource><directory>lib</directory></resource></resources></build>
</project>

3.5. 其他:

将register.jar和ICE_JNIRegistry.dll放在lib目录下(64位的Register注册表修改包网上自己搜吧)

 

四. 运行方法:

4.1.由于需要修改注册表,用到了lib下面的ICE_JNIRegistry.dll文件。
在Register.java里面的static方法中,有设置dll的路径。
要执行编译后的jar包就用上面那种方法,调试的时候可以用下面的。

4.2.我是通过IDEA中的Build Artifacts编译了两个Jar包
ssrClient.jar是在本机运行。
ssrServer.jar是在国外的服务器运行。

4.3.运行本机的ssrClient.jar:
(1)把ssrClient.jar和ICE_JNIRegistry.dll放在C:\ssr目录下
(2)cmd中执行

>cd c:\ssr
>java -jar ssrClient.jar

(另外说一句,我这里的Password是一开始准备使用AES加密用的,现在没用到)
(3)界面中修改海外服务器的IP地址,点击StartProxy
(4)修改注册表中的代理服务器好像有延迟,如果不行,就手动在IE里面改代理服务器算了。

4.4.运行海外服务器的ssrServer.jar:
(1)环境CentOS+jdk1.8.0_172,配置文件/etc/profile修改好
(2)复制ssrServer.jar到/usr/local/ssr目录中(没有就自己建)
(3)执行

>java -jar ssrServer.jar

4.5.测试:

输入打开www.google.com:

本机的ssrClient显示如下:

海外服务器的ssrServer显示如下:

 

五. 资料:

Github代码:https://github.com/sunroyi/sunSocks

 

这篇关于通过Sockets对网络请求拦截并转发的思路和简单实例(Java)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/726509

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

csu 1446 Problem J Modified LCS (扩展欧几里得算法的简单应用)

这是一道扩展欧几里得算法的简单应用题,这题是在湖南多校训练赛中队友ac的一道题,在比赛之后请教了队友,然后自己把它a掉 这也是自己独自做扩展欧几里得算法的题目 题意:把题意转变下就变成了:求d1*x - d2*y = f2 - f1的解,很明显用exgcd来解 下面介绍一下exgcd的一些知识点:求ax + by = c的解 一、首先求ax + by = gcd(a,b)的解 这个

hdu2289(简单二分)

虽说是简单二分,但是我还是wa死了  题意:已知圆台的体积,求高度 首先要知道圆台体积怎么求:设上下底的半径分别为r1,r2,高为h,V = PI*(r1*r1+r1*r2+r2*r2)*h/3 然后以h进行二分 代码如下: #include<iostream>#include<algorithm>#include<cstring>#include<stack>#includ