一、原理说明
HTTP协议的作用原理包括四个步骤:
(1)连接:Web浏览器与Web服务器建立连接,打开一个称为socket(套接字)的虚拟文件,此文件的建立标志着连接建立成功。
(2)请求:Web浏览器通过socket向Web服务器提交请求。HTTP的请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命
令的格式为:GET路径/文件名HTTP/1.0文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。
(3)应答:Web浏览器提交请求后,通过HTTP协议传送给Web服务器。Web服务器接到后,进行事务处理,处理结果又通过HTTP传回给Web浏览器,从而在Web浏览器上显示出所请求的页面。
二、Java实现Web服务器功能的程序设计
根据上述HTTP协议的作用原理,实现GET请求的Web服务器程序的方法如下:
(1)创建ServerSocket类对象,监听端口5000。这是为了区别于HTTP的标准TCP/IP端口80而取的;
(2)等待、接受客户机连接到端口5000,得到与客户机连接的socket;
(3)创建与socket字相关联的输入流instream和输出流outstream;
(4)从与socket关联的输入流instream中读取一行客户机提交的请求信息,请求信息的格式为:GET路径/文件名HTTP/1.0
(5)从请求信息中获取请求类型。如果请求类型是GET,则从请求信息中获取所访问的HTML文件名。没有HTML文件名时,则以index.html作为文件名;
(6)如果HTML文件存在,则打开HTML文件,把HTTP头信息和HTML文件内容通过socket传回给Web浏览器,然后关闭文件。否则发送错误信息给Web浏览器;
(7)关闭与相应Web浏浏览器连接的socket字。
三、java代码
主程序类WebServer.java部分-----它是一个启动类,主要用来启动web服务器
package webbook.chapter2; //程序包声明语句。表示该文件中声明的全部类都属于这个包
import java.io.IOException; //导入输入输出流的文件包中的子包--异常处理包
import java.net.ServerSocket; //导入网络包中的子包--服务器端套接字包
import java.net.Socket; //导入网络包中的子包—客户端套接字包
public class Webserver { // 定义Webserver类
//主程序类中要求只能有一个公共类
//JAVA解释器要求公共类必须放在与它同名的文件中
public static final int HTTP_PORT=5030; //声明并初始化一个静态最终类整型常量,作为
服务器端的端口
//静态成员变量可直接引用而不用实例化(其
他必须实例化)
//最终类不能被继承,避免被修改
private ServerSocket serverSocket; //声明一个私有的ServerSocket类的成员变量
public void startServer(int port){ //声明构造一个共有的无返回值带参量输入的成员
方法startServer
try{ //捕捉异常方法体
serverSocket=new ServerSocket(port); //实例化建立服务器端监听套接字
System.out.println("Web Server startup on "+port); //标准打印输出一行并换行
while (true) { //循环体 (布尔型常量)
Socket socket=serverSocket.accept(); //等待客户端连接呼叫,建立连接
套接字
new Processor(socket).start(); //实例化并启动线程,直接调用run
} 方法
} catch (IOException e) { //匹配异常
e.printStackTrace(); //处理异常—在控制台上显示异常信息
}
}
public static void main(String[] argv) throws Exception { //声明构造主函数体(带用户输
入参数),抛出所有异常,程序执行入口
Webserver server=new Webserver(); //声明实例化Webserver类对象server
if (argv.length==1) {
server.startServer(Integer.parseInt(argv[0]));//数据类型强制转换为整型(默认
java将键盘输入的数据看作字符串)
} else {
server.startServer(Webserver.HTTP_PORT);//启动服务
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Processor.java部分,是一个线程类,处理客户浏览器的访问请求,并把所请求的资源发送给客户机
package webbook.chapter2; //程序包声明
import java.io.*; //导入输入输出流包,且是导入包中所有类
import java.io.BufferedReader; //字符缓冲输入流包导入
import java.io.File; //文件流包导入
import java.io.IOException; //输入输出流异常处理包导入
import java.io.InputStream; //字节输入流包导入
import java.io.PrintStream; //字节输出打印流包导入,它是
OutputStream的子类
import java.net.Socket; //网络功能包套接字子包导入
public class Processor extends Thread { //声明线程子类Processor,他的超类是
thread
private PrintStream out; //声明私有成员变量—out(输出流)
private InputStream input; //声明私有成员变量—input(输入流)
public static final String WEB_ROOT="C:\\Users\\Administrator\\workspace\\webbook.chapter2";
//声明并初始化静态最终类字符串常量---服务器提供文件存储位置
public Processor(Socket socket) { //声明构造成员方法---创建线程
try { //创建异常处理机制
input=socket.getInputStream(); //赋值表达式—从套接字获得客户端
连接输入,返回字节输入流
inputstream对象并赋值给input
out=new PrintStream(socket.getOutputStream());//实例化输出流对
象—输出到客户端
} catch (IOException e) { //捕捉匹配异常
e.printStackTrace(); //显示异常信息
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void run() { //创建run方法,它是对象的线程体(这里涉及子类对超类的
方法覆盖)-----在线程被创建启动后,直接调用
try { //构造监视块,捕捉异常
String fileName=parse(input); // 调用方法parse解析客户端请求信息
获取请求文件名
readFile(fileName); // 调用方法readFile打开,读取文件
} catch (IOException e) { //捕捉匹配异常
e.printStackTrace(); //显示异常信息
}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public String parse(InputStream input) throws IOException { //声明并
构造方法parse解析客户端输入请求,并抛出可能引发的所有异常
BufferedReader in=new BufferedReader(new InputStreamReader(input));
//声明并实例化字符缓冲输入流对象—用于服务器缓冲输入
String inputContent=in.readLine();//声明成员变量,用于存储调用in的成
员方法读入的客户端输入数据
if (inputContent==null||inputContent.length()==0) {
sendError(400,"Client invoke error");//如果变量值为空或零,输出
客户端调用错误信息
return null; //并返回空值,关闭连接
}
String request[]=inputContent.split(" "); //声明字符串数组,存放成
员变量inputstream中内容以空格为界分割后的数据信息
if(request.length !=3) { //判断字符串数组长度是否为3
sendError(400,"Client invoke error");//若不为3,输出错误信息
return null;
}
String method=request[0];//声明成员变量method并赋值第一个字符数组中
内容
String fileName=request[1];
String httpVersion=request[2];
System.out.println("Method:"+method+",file name:"
+fileName +", HTTP version:"+httpVersion); //显示输出信息
return fileName;
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void readFile(String fileName) throws IOException {//构造方法读取文件内容,若发生错误抛出所有异常,并将异常处理交给调用者
File file=new File(Processor.WEB_ROOT+fileName);//实例化文件对象并
与实际文件建立关联
if (!file.exists()) { //判断文件是否存在
sendError(404,"File Not Found");//文件不存在则输出错误信息—是输
出显示在客户端
return;
}
InputStream in=new FileInputStream(file);//创建文件输入流对象并打开
源文件
byte content[]=new byte[(int) file.length()]; //实例化字节数组 in.read(content); //从输入流对象中读入数据到字节数组content中
out.println("HTTP/1.1 200 sendFile"); //输出流打印信息
out.println("Content-length:"+content.length);
out.println();//这段打印信息并不会显示在web浏览器网页源代码中,应该是
给http协议理解的,过滤掉了
out.write(content); //将字节数组内容写入输出流—真正的请求文件信息
out.flush(); //强制输出缓冲区数据
out.close(); //关闭打印流
in.close(); //关闭输入流
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public void sendError(int errNum,String errMsg) {//构造错误信息发送方
法
out.println("HTTP/1.1"+errNum+" "+errMsg);
out.println("Content-type:text/html");
out.println(); //输出信息---不会在客户端请求web页面原代码中出现
out.println("<html>"); //以下信息会出现在客户端请求web页面原代码中
out.println("<head><title>Error"+errNum+"--"+errMsg+"</title></head>"); //输出HTML语言信息
out.println("<h1"+errNum+" "+errMsg+"</h1>");
out.println("</html>");
out.println();
out.flush(); //强制输出缓冲区数据
out.close(); //关闭输出流
}
}
四、实验效果