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的子類,默認是阻塞式的