在日常的開發中,我們用過很多開源的web服務器,例如tomcat、apache等等。現在我們自己實現一個簡單的web服務器,基本的功能就是用戶點擊要訪問的資源,服務器將資源發送到客戶端的瀏覽器。為了簡化操作,這里不考慮資源不存在等異常情況。web服務基於的是HTTP協議,用戶在瀏覽器的地址欄輸入要訪問的地址,服務器如何得到該地址是個關鍵。先看下一般的HTTP請求和響應報文的一般格式:
HTTP 請求報文

HTTP 響應報文

web服務器獲取一個用戶的連接時,會初始化一個線程和用戶通信,代碼如下:
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.net.Socket;
//每有一個連接建立時,服務器分出一個通信的線程
public class CommunicateThread extends Thread{
//與客戶端通信的套接字
Socket client;
public CommunicateThread(Socket s) {
client = s;
}
//獲取瀏覽器請求資源的路徑
public String getResourcePath(String s){
// 一般的HTTP請求報文的第一行是“GET /index.html HTTP/1.1”
// 我們要獲取的就是中間的"/indext.apsx"
//獲取資源的位置
String s1 = s.substring(s.indexOf(' ')+1);
s1 = s1.substring(1,s1.indexOf(' '));
//默認資源為index.html
if(s1.equals(""))
s1 = "index.html";
return s1;
}
public void sendFile(PrintStream out,File file){
try{
DataInputStream in = new DataInputStream(new FileInputStream(file));
int len = (int)file.length();
byte buf[] = new byte[len];
in.readFully(buf);//讀取文內容到buf數組中
out.write(buf,0,len);
out.flush();
in.close();
}
catch(Exception e){
System.out.println(e.getMessage());
System.exit(1);
}
}
public void run(){
try{
//獲取用戶的IP地址和端口號
String clientIP = client.getInetAddress().toString();
int clientPort = client.getPort();
//創建輸出流對象
PrintStream out = new PrintStream(client.getOutputStream());
//創建輸入流對象
DataInputStream in = new DataInputStream(client.getInputStream());
//讀取瀏覽器提交的請求
String msg = in.readLine();
//獲取文件路徑
String fileName = getResourcePath(msg);
System.out.println("The user asked for resource: "+fileName);
File file = new File(fileName);
if(file.exists()){
//根據響應報文格式設置
System.out.println(fileName+" start send");
out.println("HTTP/1.0 200 OK");
out.println("MIME_version:1.0");
out.println("Content_Type:text/html");
int len = (int) file.length();
out.println("Content_Length:"+len);
out.println("");//報文頭和信息之間要空一行
//發送文件
sendFile(out,file);
out.flush();
}
client.close();
}
catch(Exception e){
System.out.println(e.getMessage());
}
}
}
服務器主要負責初始化套接字和線程,代碼如下:
import java.net.ServerSocket;
import java.net.Socket;
public class WebServer {
public static void main(String[] args) {
int Port = 12345;//端口號,由於這里是測試,所以不要使用常用端口
//創建兩個套接字
ServerSocket server = null;
Socket client = null;
try{
server = new ServerSocket(Port);
//服務器開始監聽
System.out.println("The WebServer is listening on port "+server.getLocalPort());
while(true){
client = server.accept();
//多線程運行
new CommunicateThread(client).start();
}
}catch(Exception e){
System.out.println(e.getMessage());
}
}
}
運行測試:
編寫一個index.html文件
This is the index of my WebServer
放到項目文件的根目錄,然后在瀏覽器地址欄輸入:“localhost:12345/index.html”,就可以看到位於服務器端的html文件了。注意由於服務器是死循環,重啟服務器會發現指定的端口已被綁定,只需要進入任務管理器,關閉" Java(TM) Platfrom SE binary"進程即可。最后結果如下所示:
這個服務器程序很簡陋,還有很大的改進余地。大家可以自己嘗試改進。這里可以嘗試一下訪問其他的文件,發現時成功的,說明這服務器很不安全呀。

