基於阻塞模式的簡易javaHTTP服務器

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 此類實現 IP 套接字地址(IP 地址 + 端口號)。SocketAddress
public class SimpleHttpServer {
	private int port = 80;
	private ServerSocketChannel serverSocketChannel = null;
	private ExecutorService executorService;
	private static final int POOL_MULTIPLE = 4;

	public SimpleHttpServer() throws IOException {

		executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
				.availableProcessors() * POOL_MULTIPLE);
		serverSocketChannel = ServerSocketChannel.open();
		// 確保socket關閉之後,端口可以立即被另一個程序使用
		serverSocketChannel.socket().setReuseAddress(true);
		// 將服務器程序綁定到一個端口
		serverSocketChannel.socket().bind(new InetSocketAddress(port));
		System.out.println("服務器已經啓動!");

	}

	public void service() {
		while (true) {
			SocketChannel socketChannel = null;
			try {
				socketChannel = serverSocketChannel.accept();
				executorService.execute(new Handler(socketChannel));
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String args[]) throws IOException {
		new SimpleHttpServer().service();
	}

	class Handler implements Runnable {
		private SocketChannel socketChannel;

		public Handler(SocketChannel socketChannel) {
			this.socketChannel = socketChannel;
		}

		@Override
		public void run() {
			handle(socketChannel);
		}

		public void handle(SocketChannel socketChannel) {
			try {
				Socket socket = socketChannel.socket();
				System.out.println("接收到用戶連接,來自:" + socket.getInetAddress()
						+ ":" + socket.getPort());
				ByteBuffer buffer = ByteBuffer.allocate(1024);
				socketChannel.read(buffer);// 接收HTTP請求,假定其長度不超過1024個字節
				buffer.flip();// 將極限設置爲位置,再把位置設置爲0
				String request = decode(buffer);
				System.out.println(request);// 打印HTTP請求

				// 生成HTTP響應結果
				StringBuffer sb = new StringBuffer("HTTP/1.1 200 OK\r\n");
				sb.append("Content-Type:text/html\r\n\r\n");
				socketChannel.write(encode(sb.toString()));// 輸出響應頭

				FileInputStream in;
				// 獲得HTTP請求的第一行 解析請求

				String firstLineOfRequest = request.substring(0,
						request.indexOf("\r\n"));
				if (firstLineOfRequest.indexOf("login.htm") != -1) {
					System.out.println("hehe");
					in = new FileInputStream(new File("D://root/login.htm"));
				}
				LinkedList 
				else
					in = new FileInputStream(new File("D://root/hello.htm"));
				// 通道Channel用來連接緩衝區與數據源或者數據目的地
				// FileChannel類是Channel的實現類,代表這一個與文件相連的通道。
				// 該類實現了ByteChannel、ScatteringByteChannel和GatheringByteChannel接口,支持讀寫
				// 、分散讀和集中寫操作,FIleChannel沒有公開的構造方法,因此客戶程序不能用new來創建它的實例
				// 不過在FileInputStream和FileOutStream當中提供了getChannel()返回FileChannel的實例
				FileChannel fileChannel = in.getChannel();
				// 將字節從此通道的文件傳輸到給定的可寫入字節通道。
				/*
				 * public abstract long transferTo(long position, long count,
				 * WritableByteChannel target) throws IOException
				 */

				/*
				 * 將字節從此通道的文件傳輸到給定的可寫入字節通道。
				 * 
				 * 試圖讀取從此通道的文件中給定 position 處開始的 count
				 * 個字節,並將其寫入目標通道。此方法的調用不一定傳輸所有請求的字節;是否傳輸取決於通道的性質和狀態。如果此通道的文件從給定的
				 * position 處開始所包含的字節數小於 count 個字節,或者如果目標通道是非阻塞的並且其輸出緩衝區中的自由空間少於
				 * count 個字節,則所傳輸的字節數要小於請求的字節數。
				 * 
				 * 此方法不修改此通道的位置。如果給定的位置大於該文件的當前大小,則不傳輸任何字節。如果目標通道中有該位置,則從該位置開始寫入各字節
				 * ,然後將該位置增加寫入的字節數。
				 * 
				 * 與從此通道讀取並將內容寫入目標通道的簡單循環語句相比,此方法可能高效得多。很多操作系統可將字節直接從文件系統緩存傳輸到目標通道
				 * ,而無需實際複製各字節。
				 * 
				 * 參數: position - 文件中的位置,從此位置開始傳輸;必須爲非負數 count -
				 * 要傳輸的最大字節數;必須爲非負數 target - 目標通道 返回: 實際已傳輸的字節數,可能爲零
				 */
				long num = fileChannel.transferTo(0, fileChannel.size(),
						socketChannel);
				System.out.println(num);
				// fileChannel.close();
			} catch (Exception e) {
				e.printStackTrace();
			} finally {
				try {
					if (socketChannel != null)
						socketChannel.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}

		/*
		 * java.nio.Charset類的每個實例代表特定的字符編碼類型。 將字節序列轉換爲字符串的過程稱之爲解碼;decode
		 * 將字符串轉換爲字節序列的過程稱之爲編碼 ;encode Charset的靜態forName(String
		 * encode)方法返回一個Charset對象 代表encode指定的編碼類型
		 */
		private Charset charset = Charset.forName("GBK");

		public String decode(ByteBuffer buffer) {
			// decode(ByteBuffer buffer) 將ByteBuffer構成的字節序列進行解碼 生成CharBuffer
			CharBuffer charBuffer = charset.decode(buffer);
			return charBuffer.toString();
		}

		public ByteBuffer encode(String str) { // 編碼
			return charset.encode(str);
		}
	}
}

通過輸入http://localhost:80/login.htm可以訪問本地的這個htm文件,

這個編程是基於阻塞模式的,ServerSocketChannel是繼承與SelecteableChannel的子類,默認是阻塞式的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章