Java NIO 实现简单代理

通过简单代理的实现,学习Java 非阻塞IO的使用,供大家参考。本文不涉及异步IO处理。

1、首先是SocketChannel的创建,创建方式与ServerSocket相似。在有新的请求来后,启动新的线程对该Socket连接进行处理。

package jim.proxy;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;

/***
 * Start up the proxy.
 * @author Jim-Zhang
 *
 */
public class ProxyBootstrap {

	/**
	 * Entry.
	 * @param args arguments
	 */
	public static void main(String[] args) {
		
		// Check arguments
		
		// If the first argument is '-help',print usage.
		if (args == null || args.length == 0){
			
			// Start the server
			startServer(Consts.LISTEN_PORT);
			return;
		}
		
		// Check '-p'.
		for (int i = 0; i <args.length; i ++){
			String arg = args[i];
			if (!arg.equals(Consts.CMD_ARG_PORT)){
				printHelp();
				return;
			}
			
			if (i == args.length -1){
				printHelp();
				return;
			}
			
			// Check port
			String sport = args[i+1].trim();
			if (sport.length() == 0){ // invalid
				printHelp();
				return;
			}
			
			try{
				int usePort = Integer.valueOf(sport);
				
				// start the server
				startServer(usePort);
			}catch(NumberFormatException e){
				printHelp();
				e.printStackTrace();
				return;
			}
		}
		
	}
	
	/**
	 * Start up the server on specific port.
	 * @param port Listen port
	 */
	private static void startServer(int port){
		
		try {
			ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
			SocketAddress socketAddress = new InetSocketAddress(port);
			
			// Bind serverSocketChannel
			serverSocketChannel.bind(socketAddress);
			
			System.out.println("Listening on port:"+port+".");
			while(true){
				
				// block mode
				SocketChannel socketChannel = serverSocketChannel.accept();
				
				ConnectionHandler.getInstance().add(socketChannel);
				
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Print help info.
	 */
	private static void printHelp(){
		System.out.println("Usage:");
		System.out.println("-p port");
		System.out.println("For example: -p 8200 will listen on port 8200.");
	}
}

2、SocketChannel处理类,主要通过线程池对连接进行处理,涉及的知识较多,主要是HTTP协议、非阻塞IO的使用。

package jim.proxy;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

/**
 * Connection handler.
 * Need to finish.
 * @author Jim-Zhang
 */
public class ConnectionHandler{
	
	/** Singleton */
	private static final ConnectionHandler instance = new ConnectionHandler();
	
	/** Scheduled worker */
	private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(Consts.THREAD_COUNT);
	
	/** 
	 * Add channel.
	 * @param socketChannel SocketChannel to handle 
	 * */
	public void add(SocketChannel socketChannel){
		
		// One thread per request's SocketChannel
		executorService.execute(new HandlerThread(socketChannel));
	}
	
	/** 
	 * Get {@link ConnectionHandler} object.
	 * @return {@link ConnectionHandler} Instance
	 * */
	public static ConnectionHandler getInstance(){
		return instance;
	}
	
	/** Thread for handling socket channel. */
	private class HandlerThread extends Thread{
		
		/** Reqeust SocketChannel */
		private SocketChannel socketChannel;
		
		private final BlockingQueue<String> sourceRequestData = new ArrayBlockingQueue<String>(1000);
		
		/** Target */
		private SocketChannel targetSocketChannel;
		
		/** Constructor */
		public HandlerThread(SocketChannel socketChannel){
			HandlerThread.this.socketChannel = socketChannel;
			setDaemon(true);
		}
		
		@Override
		public void run() {
			
			// Use SocketChannel to read http data
			// final Socket socket = socketChannel.socket();
			String resourceUrl = null;
			try {
				// set non-block
				socketChannel.configureBlocking(false);
				Selector selector = Selector.open();
				SelectionKey sourceKey = socketChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
				
				// for reading line 
				StringBuilder lineBuilder = new StringBuilder();
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				String line = null; // null for no data
				
				// for target
				Selector targetSelector = null;
				SelectionKey targetSelectionKey = null;
				
				// Read request data
				while (true){ 
					
					Thread.sleep(20);
					
					buffer.clear();
					
					// Read request data
					selector.selectNow();
					
					if (sourceKey.isValid() && sourceKey.isReadable()){
						int readLen = socketChannel.read(buffer);
						if (readLen > 0){
							byte[] datas = new byte[readLen];
							buffer.rewind();
							buffer.get(datas);
							lineBuilder.append(new String(datas.clone()));
							byte[] dataEnd = new byte[]{datas[datas.length -2],datas[datas.length -1]};
							if (endof(dataEnd)){
								line = lineBuilder.toString();
								break;
							}
						}
						
						if (readLen == -1){
							line = lineBuilder.toString();
							break;
						}
					}
					
				}
				
				
				if (line == null || line.isEmpty()){
					return;
				}
				
				if (line != null){
					
					// Push request data to Queue to be handled later
					String requestStr = line.replace(HttpHeader.PROXY_CONNECTION, "Connection");
					sourceRequestData.add(requestStr);
				}
				
				// Handle target channel
				while(true && socketChannel.isOpen()){
					
					String lineToHandle = sourceRequestData.poll();
					// GET http://xx/ss/dd HTTP/1.1 
					// Immediately open the SocketChannel
					if (targetSocketChannel == null 
							&& lineToHandle != null 
							&& lineToHandle.indexOf("HTTP/1.1") != -1){
						String targetHostStr = lineToHandle.split(" ")[1];
						targetHostStr = targetHostStr.substring(targetHostStr.indexOf("//")+2);
						targetHostStr = targetHostStr.substring(0,targetHostStr.indexOf("/")); 
								
						// use SocketChannel to request data
						String[] hostParams = targetHostStr.split(HttpHeader.SPLIT_CHAR);
						String host = hostParams[0].trim();
						int port = hostParams.length == 1 ?  80 : Integer.valueOf(hostParams[1].trim());
						
						SocketAddress targetAddress = new InetSocketAddress(host, port);
							
						targetSocketChannel =  SocketChannel.open(targetAddress);
						targetSocketChannel.configureBlocking(false);
						
						targetSelector = Selector.open();
						targetSelectionKey = targetSocketChannel.register(targetSelector,
									SelectionKey.OP_READ | SelectionKey.OP_WRITE);
						
						resourceUrl = lineToHandle;
					}
					
					if(targetSocketChannel != null){
					
						targetSelector.select();
					    if (targetSelectionKey.isWritable()){
							String toTargetData = lineToHandle;
							if (toTargetData != null){
								byte[] datas = toTargetData.getBytes();
								ByteBuffer buffers = ByteBuffer.wrap(datas);
								// buffers.flip();
								targetSocketChannel.write(buffers);
								
								targetSocketChannel.write(ByteBuffer.wrap(Consts.LINE_END));
								targetSocketChannel.write(ByteBuffer.wrap(Consts.LINE_END));
								// targetSocketChannel.write(ByteBuffer.wrap(Consts.LINE_END));
								
								// line end
								// targetSocketChannel.write(ByteBuffer.wrap(Consts.LINE_END));
							}else{
								// targetSocketChannel.write(ByteBuffer.wrap(Consts.STREAM_END));
								// targetSocketChannel.write(ByteBuffer.wrap(Consts.LINE_END));
							}
						}
						
						if (targetSelectionKey.isReadable()){
							
							ByteBuffer buffers = ByteBuffer.allocate(1024);
							
							int len = targetSocketChannel.read(buffers);
							if (len == 0){
								continue;
							}
							
							if (len == -1){
								break;
							}
							
							buffers.flip();
							socketChannel.write(buffers);
							buffers.compact();
						}
					}
					
					Thread.sleep(20);
				}
				
				
			} catch (Throwable e) {
				
				try {
					System.out.println(resourceUrl);
					socketChannel.close();
					targetSocketChannel.close();
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		}
		
		/** 
		 * End of stream or line.
		 * @param data Data
		 * @return true-end of line or stream
		 * */
		private boolean endof(byte[] datas){
			return Arrays.equals(Consts.LINE_END,datas) 
						|| Arrays.equals(Consts.STREAM_END,datas);
		}
	}
}

最后是常量定义类。

package jim.proxy;

/**
 * Constants for proxy.Should not instatiate.
 * @author Jim-Zhang
 *
 */
public class Consts {
	
	// private constructor
	private Consts(){throw new UnsupportedOperationException("Can not instatiate.");}
	
	/** The number of threads handle requests. */
	public static final int THREAD_COUNT = 200;
	
	/** Default listening port. */
	public static final int LISTEN_PORT = 4186;
	
	/** Listening port cmd argument */
	public static String CMD_ARG_PORT = "-p";
	
	/** Line end */
	public static String LINE_END_S = "\r\n";
	
	/** Line end */
	public static byte[] LINE_END = "\r\n".getBytes();
	
	/** Stream end */
	public static byte[] STREAM_END = new byte[]{0,0};
}

以上是代理的简单实现,仅是一个HTTP代理的简单思路,目的是为了使用NIO。HTTP协议的头结束、数据压缩等都有涉及,本文只对头做了处理,而对数据压缩未做处理。

目前我另外一个实现为采用Apache HttpComponents,采用其中NHttpServer\NHttpClient使用比较完整的代理。有兴趣可以参考下HttpComponents对Http的封装。



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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值