内容为《深入剖析Tomcat》第一章重点,以及自己的总结,如有描述不清的,可查看原书。
一、HTTP协议:
1、定义:用于服务器与客户端的通讯的协议,允许web服务器和浏览器通过互联网进行发送和接收数据。是一种请求和响应协议,使用可靠的TCP协议,TCP协议的端口为80,是一种面向连接的协议。
2、HTTP协议请求的三个组成部分:这三部分之间用回车换行符(CRLF)隔开
请求部分:方法(GET/POST等7种,其他的很少用,书上有介绍)[空格,该部分内容以空格隔开] 统一资源标识符URI[空格,该部分内容以空格隔开 ] 协议/协议版本
URL通常都是相对服务器的根目录,因此以“/”开头。
请求头部:请求的头部包含了关于客户端环境和请求的主体内容的有用信息。例如它可能包括浏览器设置的语言,主体内容的长度等等。每个头部通过一个回车换行符(CRLF)来分隔的。
请求主体内容:对于HTTP请求格式来说,头部和主体内容之间有一个回车换行符(CRLF)是相当重要的。CRLF告诉HTTP服务器主体内容是在什么地方开始的。在一些互联网编程书籍中,CRLF还被认为是HTTP请求的第四部分。
3、 HTTP响应也包括三个部分:
· 方法—统一资源标识符(URI)—协议/版本
· 响应的头部
· 主体内容
二、服务器与客户端通讯
1、服务器与客户端通讯需要用到两个部分:Socket(客户端)和ServerSocket(服务器端)
(1)ServerSocket(java.net.ServerSocket, 服务器端套接字),要创建一个服务器套接字,你需要使用ServerSocket类提供的四个构造方法中的一个。你需要指定IP地址和服务器套接字将要进行监听的端口号。通常,IP地址将会是127.0.0.1,也就是说,服务器套接字将会监听本地机器。服务器套接字正在监听的IP地址被称为是绑定地址。服务器套接字的另一个重要的属性是backlog,这是服务器套接字开始拒绝传入的请求之前,传入的连接请求的最大队列长度,四种构造方法分别为:
ServerSocket ss = new ServerSocket();//创建一个未绑定的ServerSocket
ServerSocket ss = new ServerSocket(int port);//创建一个绑定到某端口的ServerSocket
ServerSocket ss = new ServerSocket(int port, int log);//创建一个绑定到某端口的ServerSocket,并设置了最大队列长度。
ServerSocket ss = new ServerSocket(int port, int log, InetAddress address);//创建一个绑定到某地址、某端口的ServerSocket,并设置了最大队列长度。
对于第四个构造方法,绑定地址必须是InetAddress的一个实例,一种构造InetAddress对象的简单的方法是调用它的静态方法getByName,传入一个包含主机名称的字符串,就像下面的代码一样。
InetAddress.getByName("127.0.0.1");
创建ServerSocket的常用方法:
new ServerSocket(8080, 1, InetAddress.getByName("127.0.0.1"));
代码构造了一个监听本地机器8080端口的ServletSocket,它的队列长度为1.
服务器创建后就保持等待状态(TCP协议,可靠的传输协议,是同步协议,即没有响应返回就一直等待)
(2)Socket(java.net.Socket类,客户端套接字):需要知道要访问的服务器端的IP/主机名和端口号,即可向服务器端发送请求。可以用Socket的众多构造方法中的一个来建立Socket
new Socket ("yahoo.com", 80);
一旦你成功创建了一个Socket类的实例,你可以使用它来发送和接受字节流。要发送字节流,你首先必须调用Socket类的getOutputStream方法来获取一个java.io.OutputStream对象。要发送文本到一个远程应用,你经常要从返回的OutputStream对象中构造一个java.io.PrintWriter对象。要从连接的另一端接受字节流,你可以调用Socket类的getInputStream方法用来返回一个java.io.InputStream对象。
(3)服务器端通过accept()方法来接收客户端的连接请求,并与客户端建立连接,同时返回一个Socket
Socket s = ss. accept();
(4)通过Socket可以获得一个输入流和一个输出流, 输入流用于读取客户端请求数据,输出流用于向客户端返回响应信息。
比如:InputStream input = s.getInputStream();
OutputStream output = s.getOutputStream();
三、简单的Web服务器通讯示例(建议拷贝到MyEclipse来看并执行)
1、服务器类
package com.socket.httpservertest; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; public class HttpServer { public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot"; // shutdown command private static final String SHUTDOWN_COMMAND = "/SHUTDOWN"; // the shutdown command received private boolean shutdown = false; public static void main(String[] args) { HttpServer server = new HttpServer(); server.await(); } public void await() { // System.out.println(System.getProperty("user.dir")); ServerSocket serverSocket = null; int port = 8080; try { serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.0.1")); } catch (IOException e) { e.printStackTrace(); System.exit(1); } while (!shutdown) { Socket socket = null; InputStream input = null; OutputStream output = null; try { //接收了客户端发来的请求,否则一致是等待状态 socket = serverSocket.accept(); input = socket.getInputStream(); output = socket.getOutputStream(); // create Request object and parse Request request = new Request(input); request.parse(); //从请求中读取内容 // create Response object Response response = new Response(output); response.setRequest(request); response.sendStaticResource(); // Close the socket socket.close(); //check if the previous URI is a shutdown command shutdown = request.getUri().equals(SHUTDOWN_COMMAND); }catch (Exception e) { e.printStackTrace (); continue; } } } }
2、请求类
package com.socket.httpservertest; import java.io.IOException; import java.io.InputStream; public class Request { private InputStream input; private String uri; public Request(InputStream input) { this.input = input; } public void parse() { // Read a set of characters from the socket StringBuffer request = new StringBuffer(2048); int i; byte[] buffer = new byte[2048]; try { i = input.read(buffer); //将从输入流取2048长度的内容存到buffer字节数组中,如果内容不到2048,数组其他空间剩余空着 } catch (IOException e) { e.printStackTrace(); i = -1; } for (int j=0; j<i; j++) { request.append((char) buffer[j]); } System.out.print(request.toString()); uri = parseUri(request.toString()); } private String parseUri(String requestString) { int index1, index2; index1 = requestString.indexOf(‘ ‘); /* * http请求行的结构:方法 统一资源标识符(URI) 协议/版本(它们之间以空格分隔) * 例如:POST //examples/default.jsp HTTP/1.1 */ if (index1 != -1) {// index1 == -1表示没找到 index2 = requestString.indexOf(‘ ‘, index1 + 1);//从index+1位置开始寻找‘ ’ if (index2 > index1) return requestString.substring(index1 + 1, index2); } return null; } public String getUri() { return uri; } }
3、响应类
package com.socket.httpservertest; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.OutputStream; public class Response { private static final int BUFFER_SIZE = 1024; Request request; OutputStream output; public Response(OutputStream output) { this.output = output; } public void setRequest(Request request) { this.request = request; } public void sendStaticResource() throws IOException { byte[] bytes = new byte[BUFFER_SIZE]; FileInputStream fis = null; try { File file = new File(HttpServer.WEB_ROOT, request.getUri()); if (file.exists()) { fis = new FileInputStream(file); int ch = fis.read(bytes, 0, BUFFER_SIZE); while (ch!=-1) { //ch==-1表示读到末尾了 output.write(bytes, 0, ch); //写出到浏览器 ch = fis.read(bytes, 0, BUFFER_SIZE);//再读会接上一次读的位置往下读,如果读到末尾就会返回-1,终止输出 } } else { // file not found String errorMessage = "HTTP/1.1 404 File Not Found\r\n" + "Content-Type: text/html\r\n" + "Content-Length: 23\r\n" + "\r\n" + "<h1>File Not Found</h1>"; output.write(errorMessage.getBytes()); } }catch (Exception e) { // thrown if cannot instantiate a File object System.out.println(e.toString() ); } finally { if (fis!=null) fis.close(); } } }
4、示例代码功能描述:浏览器输入http请求,比如http://localhost:8080/MyHtml.html;服务器接收请求后用输入流读取请求内容获得文件位置,再用文件输入流读取文件中的内容;最后给浏览器客户端返回响应,把html文件中的内容显示在浏览器上,如图:
5、示例代码跨职能流程图: