一个基于java NIO的Http服务器

      学习了Java Nio,分析一个基于Java NIo 的Http服务器。

   主要的三个类是NioHttpServer,RequestHander,ResponceHeaderBuilder;

Http服务器为了并发,常采用线程技术来处理高并发。但是Java NIO用基于事件的处理机制来避免创建过多的线程来占用服务器资源;
NioHttpServer单独一个线程,用来开启服务器,等待请求。处理方法:该类包含一个RequestHander引用列表,当NioHttpServer接到
请求后,会调用引用列表中的请求处理器处理请求。
RequestHander为一个线程,用来处理请求。这里,程序根据cpu的个数来创建相等个数的RequestHander线程,来了请求,就取一个处理线程来处理数据;

ResponceHeaderBuilder  :回应(Response)数据构造器,用来构造http会发的头数据。



NioHttpServer:

public class NioHttpServer implements Runnable {

	private static Logger logger = Logger.getLogger(NioHttpServer.class);

	public static void main(String[] args) throws IOException {
        //得到当前路径的相对路径
		String root = new File(".").getAbsolutePath();
		//端口8080
		int port = 8080;
		//如果有输入参数,第1个参数是端口,第二个参数为路径
		if (args.length > 0)
			port = Integer.parseInt(args[0]);

		if (args.length > 1)
			root = args[1];
		//打出日志
		logger.info("listenning at *." + port + "; root: " + root);
		//创建NioHttpServer
		NioHttpServer server = new NioHttpServer(null, port);
		//得到java虚拟机可用cpu的数量
		int cpu = Runtime.getRuntime().availableProcessors();
		//缓存,有待考究
		ButterflySoftCache cache = new ButterflySoftCache();
		// int i = 0;
		for (int i = 0; i < cpu; ++i) {
			//遍历cpu,一个cpu一个线程请求处理器
			RequestHandler handler = new RequestHandler(server, root, cache);
			server.addRequestHanlder(handler);
			new Thread(handler, "worker" + i).start();
		}

		new Thread(server, "selector").start();
	}

	
	
	private ServerSocketChannel serverChannel;
	private Selector selector;
	private ByteBuffer readBuffer = ByteBuffer.allocate(8912);
	private List<ChangeRequest> changeRequests = new LinkedList<ChangeRequest>();
	private Map<SocketChannel, List<ByteBuffer>> pendingSent = new HashMap<SocketChannel, List<ByteBuffer>>();
	private List<RequestHandler> requestHandlers = new ArrayList<RequestHandler>();

	public NioHttpServer(InetAddress address, int port) throws IOException {
		selector = Selector.open();
		serverChannel = ServerSocketChannel.open();
		serverChannel.configureBlocking(false);
		serverChannel.socket().bind(new InetSocketAddress(address, port));
		serverChannel.register(selector, SelectionKey.OP_ACCEPT);

	}

	private void accept(SelectionKey key) throws IOException {
		SocketChannel socketChannel = serverChannel.accept();
		// logger.info("new connection:\t" + socketChannel);
		socketChannel.configureBlocking(false);
		socketChannel.register(selector, SelectionKey.OP_READ);
	}

	public void addRequestHanlder(RequestHandler handler) {
		requestHandlers.add(handler);
	}

	private void read(SelectionKey key) throws IOException {
		//读操作可以读取客户端发来的数据。此时通过传入的key获取可以执行读操作的客户端socket通道句柄
		SocketChannel socketChannel = (SocketChannel) key.channel();
		//清空缓冲区
		readBuffer.clear();
		int numRead;
		try {
			//执行度操作,尝试吧客户端发来的数据读到缓冲区中
			numRead = socketChannel.read(readBuffer);

		} catch (IOException e) {
			// the remote forcibly closed the connection
			//发生异常的话,key取消,客户端socket关闭
			key.cancel();
			socketChannel.close();
//			logger.info("closed by exception" + socketChannel);
			return;
		}
        //如果读完了
		if (numRead == -1) {
			// remote entity shut the socket down cleanly.
			socketChannel.close();
			key.cancel();
//			logger.info("closed by shutdown" + socketChannel);
			return;
		}

		int worker = socketChannel.hashCode() % requestHandlers.size();
		if (logger.isDebugEnabled()) {
			logger.debug(selector.keys().size() + "\t" + worker + "\t"
					+ socketChannel);
		}
		requestHandlers.get(worker).processData(socketChannel,
				readBuffer.array(), numRead);
	}

	public void run() {
		SelectionKey key = null;
		while (true) {
			try {
				synchronized (changeRequests) {
					for (ChangeRequest request : changeRequests) {
						switch (request.type) {
						case ChangeRequest.CHANGEOPS:
							key = request.socket.keyFor(selector);
							if (key != null && key.isValid()) {
								key.interestOps(request.ops);
							}
							break;
						}
					}
					changeRequests.clear();
				}

				selector.select();
				Iterator<SelectionKey> selectedKeys = selector.selectedKeys()
						.iterator();
				while (selectedKeys.hasNext()) {
					key = selectedKeys.next();
					selectedKeys.remove();
					if (!key.isValid()) {
						continue;
					}
					if (key.isAcceptable()) {
						accept(key);
					} else if (key.isReadable()) {
						read(key);
					} else if (key.isWritable()) {
						write(key);
					}
				}
			} catch (Exception e) {
				if (key != null) {
					key.cancel();
					Util.closeQuietly(key.channel());
				}
				logger.error("closed" + key.channel(), e);
			}
		}

	}

	public void send(SocketChannel socket, byte[] data) {
		synchronized (changeRequests) {
			changeRequests.add(new ChangeRequest(socket,
					ChangeRequest.CHANGEOPS, SelectionKey.OP_WRITE));
			synchronized (pendingSent) {
				List<ByteBuffer> queue = pendingSent.get(socket);
				if (queue == null) {
					queue = new ArrayList<ByteBuffer>();
					pendingSent.put(socket, queue);
				}
				queue.add(ByteBuffer.wrap(data));
			}
		}

		selector.wakeup();
	}

	private void write(SelectionKey key) throws IOException {
		SocketChannel socketChannel = (SocketChannel) key.channel();
		synchronized (pendingSent) {
			List<ByteBuffer> queue = pendingSent.get(socketChannel);
			while (!queue.isEmpty()) {
				ByteBuffer buf = queue.get(0);
				socketChannel.write(buf);
				// have more to send
				if (buf.remaining() > 0) {
					break;
				}
				queue.remove(0);
			}
			if (queue.isEmpty()) {
				key.interestOps(SelectionKey.OP_READ);
			}
		}
	}
}



Requesthander:

public class RequestHandler implements Runnable {

	private static final DateFormat formater = new SimpleDateFormat(
			"EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
	static {
		formater.setTimeZone(TimeZone.getTimeZone("GMT"));
	}
	private static final Logger logger = Logger.getLogger(RequestHandler.class);
	private ButterflySoftCache cache;
	private File currentFile;
	private Date lastModified;
	private List<RequestSegmentHeader> pendingRequestSegment = new ArrayList<RequestSegmentHeader>();
	private Map<SocketChannel, RequestHeaderDecoder> requestMap = new WeakHashMap<SocketChannel, RequestHeaderDecoder>();
	private NioHttpServer server;
	private String serverRoot;
	private String acceptEncoding;

	/**
	 * 
	 * @param server
	 *            {@link NioHttpServer} the server
	 * @param wwwroot
	 *            wwwroot
	 * @param cache
	 *            cache implementation
	 */
	public RequestHandler(NioHttpServer server, String wwwroot,
			ButterflySoftCache cache) {
		this.cache = cache;
		this.serverRoot = wwwroot;
		this.server = server;
	}

	//处理客户端发来的请求数据,把数据放到list列表中,因为一直有RequestHander线程开着,所以会处理这个数据
	public void processData(SocketChannel client, byte[] data, int count) {

		byte[] dataCopy = new byte[count];
		System.arraycopy(data, 0, dataCopy, 0, count);
      /* //打出请求数据
		String a=new String(dataCopy);
		System.out.println(a);*/
		synchronized (pendingRequestSegment) {
			// add data
			pendingRequestSegment
					.add(new RequestSegmentHeader(client, dataCopy));
			pendingRequestSegment.notify();
		}
	}

	
	public void run() {

		RequestSegmentHeader requestData = null;
		RequestHeaderDecoder header = null;
		ResponceHeaderBuilder builder = new ResponceHeaderBuilder();
		byte[] head = null;
		byte[] body = null;
		String file = null;
		String mime = null;
		boolean zip = false;

		// wait for data
		while (true) {
//不停循环从pendingRequestSegment列表中取数据,如果有数据,就处理,没有就等待
			synchronized (pendingRequestSegment) {
				while (pendingRequestSegment.isEmpty()) {
					try {
						pendingRequestSegment.wait();
					} catch (InterruptedException e) {
					}
				}
				requestData = pendingRequestSegment.remove(0);
			}
           //取出请求数据(http服务器 为请求头)
			header = requestMap.get(requestData.client);
			if (header == null) {
				header = new RequestHeaderDecoder();
				requestMap.put(requestData.client, header);
			}
			try {
				if (header.appendSegment(requestData.data)) {
					file = serverRoot + header.getResouce();
					currentFile = new File(file);
					mime = Util.getContentType(currentFile);
					// logger.info(currentFile + "\t" + mime);
					acceptEncoding = header.getHeader(ACCEPT_ENCODING);
					// gzip text  判断是否gzip压缩
					zip = mime.contains("text")
							&& acceptEncoding != null
							&& (acceptEncoding.contains("gzip") || acceptEncoding
									.contains("gzip"));
					builder.clear(); // get ready for next request;
                     //构造回应数据
					// always keep alive
					builder.addHeader(CONNECTION, KEEP_ALIVE);
					builder.addHeader(CONTENT_TYPE, mime);

					// response body byte, exception throws here
					body = Util.file2ByteArray(currentFile, zip);
					builder.addHeader(CONTENT_LENGTH, body.length);
					if (zip) {
						// add zip header
						builder.addHeader(CONTENT_ENCODING, GZIP);
					}

					// last modified header
					lastModified = new Date(currentFile.lastModified());
					builder.addHeader(LAST_MODIFIED,
							formater.format(lastModified));

					// response header byte
					head = builder.getHeader();

					// data is prepared, send out to the client
					server.send(requestData.client, head);
					if (body != null && header.getVerb() == Verb.GET)
						server.send(requestData.client, body);

					logger.info(header.getResouce() + " 200");

				}
			} catch (IOException e) {
				builder.addHeader(CONTENT_LENGTH, 0);
				builder.setStatus(NOT_FOUND_404);
				head = builder.getHeader();
				server.send(requestData.client, head);
				// cache 404 if case client make a mistake again
				cache.put(file, head, body);
				logger.error(header.getResouce() + " 404");

			} catch (Exception e) {
				// any other, it's a 505 error
				builder.addHeader(CONTENT_LENGTH, 0);
				builder.setStatus(SERVER_ERROR_500);
				head = builder.getHeader();
				server.send(requestData.client, head);
				logger.error("505 error", e);
			}
		}
	}
}


//封装了一个socketChannel通道,一个字节数组
class RequestSegmentHeader {
	SocketChannel client;
	byte[] data;

	public RequestSegmentHeader(SocketChannel client, byte[] data) {
		this.client = client;
		this.data = data;
	}
}



ResponceHeaderBuilder:


public class ResponceHeaderBuilder {
	public static final String OK_200 = "HTTP/1.1 200 OK";
	public static final String NEWLINE = "\r\n";
	public static final String NOT_FOUND_404 = "HTTP/1.1 404 Not Find";
	public static final String SERVER_ERROR_500 = "HTTP/1.1 500 Internal Server Error";
	public static final String CONTENT_TYPE = "Content-Type";
	public static final String CONNECTION = "Connection";
	public static final String CONTENT_LENGTH = "Content-Length";
	public static final String KEEP_ALIVE = "keep-alive";
	public static final String CONTENT_ENCODING = "Content-Encoding";
	public static final String ACCEPT_ENCODING = "Accept-Encoding";
	public static final String LAST_MODIFIED = "Last-Modified";
	public static final String GZIP = "gzip";

	private String status;
	private Map<String, Object> header = new TreeMap<String, Object>();

	/**
	 * status default to 200
	 */
	public ResponceHeaderBuilder() {
		status = OK_200;
	}

	public ResponceHeaderBuilder addHeader(String key, Object value) {
		header.put(key, value);
		return this;
	}

	public void clear() {
		status = OK_200;
		header.clear();
	}

	public byte[] getHeader() {
		return toString().getBytes();
	}

	public ResponceHeaderBuilder setStatus(String status) {
		this.status = status;
		return this;
	}

	@Override
	public String toString() {

		StringBuilder sb = new StringBuilder(120);
		sb.append(status).append(NEWLINE);
		Set<String> keySet = header.keySet();
		for (String key : keySet) {
			sb.append(key).append(": ").append(header.get(key)).append(NEWLINE);
		}
		sb.append(NEWLINE); // empty line;
		return sb.toString();
	}

}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值