这里发现
一只程序猿O(∩_∩)O
渴望用Hello World改变世界,喜欢电影,喜欢跑步,略带文艺的逗比程序猿一只!

Java实现多线程代理服务器-JavaHttpProxy

最近帮别人做一个代理服务器,只是个小demo,代码发上来,共享一下。

代理服务器的原理就是接受客户端的请求,从HTTP头中提取出目标服务器的地址,然后构建客户端和目标服务器的通信管道,就可以实现代理的功能。当然真正的代理服务器还有很多工作要做,但是原理大抵如此。

本文实现的代理服务器采用多线程的方式,每个请求用单独地线程进行处理,并附带了dns缓存的功能,进一步加速代理服务器的处理效率。

代码目录结构如下:

JavaHttpProxy

 

 

 

 

 

其中,proxyd.java是服务的主程序,为每个request产生一个线程来单独处理。启动服务要设置服务的端口,如:

#java proxyd -p 8888
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import edu.npu.proxy.HttpProxyMainThread;

public class proxyd {

	public static void main(String[] args) {
		
		if (args.length > 0 && args[0].equals("-p")) {
			int port = Integer.parseInt(args[1]);
			ServerSocket serverSocket = null;
			try {
				serverSocket = new ServerSocket(port);
				System.out.println("The proxy have start on port:" + port + "\n");
				while (true) {
					Socket socket = null;
					try {
						socket = serverSocket.accept();
						new HttpProxyMainThread(socket).start();
					} catch (Exception e) {
						System.out.println("Thread start fail");
					}
				}
			} catch (IOException e1) {
				System.out.println("proxyd start fail\n");
			}finally{
				try {
					serverSocket.close();
				} catch (IOException e) {
					//e.printStackTrace();
				}
			}
		}else{
			System.out.println("parameter error");
		}
	}
	
}

HttpProxyMainThread是程序的主线程,这里完成主要工作。首先获取HTTP请求头中的第一行,从中提取出目标url,从url中提取出目标服务器的域名,端口信息等,将域名解析成IP,并缓存下来,然后通过提取出来的IP和端口,向目标主机建立连接,然后构建客户端和目标服务器的通信管道,其中一个线程负责从将客户端请求写到目标服务器,另一个将目标服务器的响应返回给客户端。

package edu.npu.proxy;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

import edu.npu.utils.URL;

public class HttpProxyMainThread extends Thread {
	static public int CONNECT_RETRIES = 5; // 尝试与目标主机连接次数
	static public int CONNECT_PAUSE = 5; // 每次建立连接的间隔时间
	static public int TIMEOUT = 50; // 每次尝试连接的最大时间
	
	protected Socket csocket;// 与客户端连接的Socket

	public HttpProxyMainThread(Socket cs) {
		this.csocket = cs;
	}

	public void run() {
		String firstLine = ""; // http请求头第一行
		String urlStr = ""; // 请求的url
		Socket ssocket = null;//与目标服务器连接的socket
		// cis为客户端输入流,sis为目标主机输入流
		InputStream cis = null, sis = null;
		// cos为客户端输出流,sos为目标主机输出流
		OutputStream cos = null, sos = null;
		try {
			csocket.setSoTimeout(TIMEOUT);
			cis = csocket.getInputStream();
			cos = csocket.getOutputStream();
			while (true) {
				int c = cis.read();
				if (c == -1)
					break; // -1为结尾标志
				if (c == '\r' || c == '\n')
					break;// 读入第一行数据,从中获取目标主机url
				firstLine = firstLine + (char) c;
			}
			urlStr = extractUrl(firstLine);
			System.out.println(urlStr);
			URL url = new URL(urlStr);//将url封装成对象,完成一系列转换工作,并在getIP中实现了dns缓存
			firstLine = firstLine.replace(url.getScheme()+"://"+url.getHost(), "");//这一步很重要,把请求头的绝对路径换成相对路径
			int retry = CONNECT_RETRIES;
			while (retry-- != 0) {
				try {
					ssocket = new Socket(url.getIP(), url.getPort()); // 尝试建立与目标主机的连接
					System.out.println("+++++successfully connect to ("+url.getIP()+":"+url.getPort()+")(host:"+url.getHost()+")+++++,get resource("+url.getResource()+")");
					break;
				} catch (Exception e) {
					System.out.println("-----fail connect to ("+url.getIP()+":"+url.getPort()+")(host:"+url.getHost()+")-----");
				}
				// 等待
				Thread.sleep(CONNECT_PAUSE);
			}
			if (ssocket != null) {
				ssocket.setSoTimeout(TIMEOUT);
				sis = ssocket.getInputStream();
				sos = ssocket.getOutputStream();
				sos.write(firstLine.getBytes()); // 将请求头写入
				pipe(cis, sis, sos, cos); // 建立通信管道
			}
		} catch (Exception e) {
			//e.printStackTrace();
		} finally {
			try {
				csocket.close();
				cis.close();
				cos.close();
			} catch (Exception e1) {
			}
			try {
				ssocket.close();
				sis.close();
				sos.close();
			} catch (Exception e2) {
			}
		}
	}
	/**
	 * 从http请求头的第一行提取请求的url
	 * @param firstLine http请求头第一行
	 * @return url
	 */
	public String extractUrl(String firstLine) {
		String[] tokens = firstLine.split(" ");
		String URL = "";
		for (int index = 0; index < tokens.length; index++) {
			if (tokens[index].startsWith("http://")) {
				URL = tokens[index];
				break;
			}
		}
		return URL;
	}

	/**
	 * 为客户机与目标服务器建立通信管道
	 * @param cis 客户端输入流
	 * @param sis 目标主机输入流
	 * @param sos 目标主机输出流
	 * @param cos 客户端输出流
	 */
	public void pipe(InputStream cis, InputStream sis, OutputStream sos, OutputStream cos) {
		Client2ServerThread c2s = new Client2ServerThread(cis, sos);
		Server2ClientThread s2c = new Server2ClientThread(sis, cos);
		c2s.start();
		s2c.start();
		try {
			c2s.join();
			s2c.join();
		} catch (InterruptedException e1) {
			
		}
	}
}

Client2ServerThread.java和Server2ClientThread.java就是通信管道的两个子线程。

package edu.npu.proxy
import java.io.InputStream;
import java.io.OutputStream;

public class Client2ServerThread extends Thread{
	private InputStream cis;
    private OutputStream sos;

    public Client2ServerThread(InputStream cis, OutputStream sos) {
        this.cis = cis;
        this.sos = sos;
    }

    public void run() {
    	int length;
    	byte bytes[] = new byte[1024];
		while(true){
			try {
				if ((length = cis.read(bytes)) > 0) {
					sos.write(bytes, 0, length);//将http请求头写到目标主机
					sos.flush();
				} else if (length < 0)
					break;
			} catch (Exception e) {
				//System.out.println("\nRequest Exception:");
				//e.printStackTrace();
			}
		}
    }
}
package edu.npu.proxy;

import java.io.InputStream;
import java.io.OutputStream;

public class Server2ClientThread extends Thread{
	private InputStream sis;
    private OutputStream cos;

    public Server2ClientThread(InputStream sis, OutputStream cos) {
        this.sis = sis;
        this.cos = cos;
    }

    public void run() {
    	int length;
    	byte bytes[] = new byte[1024];
		while(true){
			try {
				if ((length = sis.read(bytes)) > 0) {
					cos.write(bytes, 0, length);//将http请求头写到目标主机
					cos.flush();
				} else if (length < 0)
					break;
			} catch (Exception e) {
				//System.out.println("\nRequest Exception:");
			}
		}
    }
}

URL.java是封装的一个工具类,可以将URL分成不同的部分,构造函数传一个url,自动将url转换成包含scheme, host, ip, port, resource等部分。其中getIP中实现了dns解析,并且利用了java提供的类实现了dns的缓存,并每30s更新一次dns缓存。

package edu.npu.utils;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class URL {
	private String scheme;
	private String host;
	private String IP;
	private String resource;
	private int port;

	public String getScheme() {
		return scheme;
	}

	public void setScheme(String schemes) {
		this.scheme = schemes;
	}

	public String getHost() {
		return host;
	}

	public void setHost(String host) {
		this.host = host;
	}

	public int getPort() {
		return port;
	}

	public void setPort(int port) {
		this.port = port;
	}

	public String getIP() {
		java.security.Security.setProperty("networkaddress.cache.ttl", "30");
		try {
			this.IP = InetAddress.getByName(this.host).getHostAddress();
		} catch (UnknownHostException e) {
			return "";
		}
		return this.IP;
	}
	

	public String getResource() {
		return resource;
	}

	public String toString() {
		return "scheme:" + this.getScheme() + "\nhost:" + this.getHost()
				+ "\nport:" + this.getPort() + "\nIP:"+this.getIP()+"\nResource:"+this.getResource();
	}

	public URL(String url) {
		String scheme = "http";
		String host = "";
		String port = "80";
		int index;
		// 抽取host
		index = url.indexOf("//");
		if (index != -1)
			scheme = url.substring(0, index - 1);
		host = url.substring(index + 2);
		index = host.indexOf('/');
		if (index != -1){
			this.resource = host.substring(index);
			host = host.substring(0, index);
		}
		index = host.indexOf(':');
		if (index != -1) {
			port = host.substring(index + 1);
			host = host.substring(0, index);
		}
		this.scheme = scheme;
		this.host = host;
		this.port = Integer.parseInt(port);
	}
}

完整代码详见:https://github.com/sean-npu/JavaHttpProxy

转载请注明出处fullstackdevel.com:SEAN是一只程序猿 » Java实现多线程代理服务器-JavaHttpProxy

分享到:更多 ()

相关推荐

Comment 11

评论前必须登录!

  1. #2

    运行的截图是啥样的

    发沙3年前 (2015-11-30)
  2. #1

    为什么执行proxyd的第八行会挂起,运行结果直接是parameter error

    发沙3年前 (2015-11-30)
    • 你没有配置端口吧?

      • 麻烦问下着端口怎么配置,因为初学比较水,谢谢啊

        发沙3年前 (2015-11-30)
      • #java proxyd -p 8888大神,这个在哪里设置

        发沙3年前 (2015-11-30)
        • 在命令行下执行

          • 命令行下?体会不来,能否具体点啊

            发沙3年前 (2015-12-01)
          • 你编的这个如何可以实现代理访问,也就是如何掩饰?比如说不启动代理的话,客户端访问不了目标服务器,启动代理的话就可以访问。

            发沙3年前 (2015-12-01)
          • 先在命令行下把服务跑起来,然后在浏览器里设置你的代理服务器是本地的地址就行了

            Sean是条程序狗3年前 (2015-12-02)