通过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,合适的数量应该小于8

    static 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{
        @Override
        public 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.2
                lookAndFeel = "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,此时保存为“”,并且返回true
        try {
            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";

    @Override
    public 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结束后再Output
            Thread 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;
        }

        @Override
        public 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;
        }

        @Override
        public 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;
    }


    @Override
    public 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";

    @Override
    public 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结束后再Output
            Thread 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;
        }

        @Override
        public 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;
        }

        @Override
        public 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;
    }


    @Override
    public 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

 

  • 1
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值