我们可以将Tomcat 简单理解成一个Web 服务器。这一节主要是讲解Web 服务器是如何工作的。
Web 服务器也可以看作是Http 服务器,主要是因为它是使用Http 来与客户端进行通信的。一个基于Java 的Web 服务器,最重要的两个类是java.net.Socket 和 java.net.ServerSocket , 并通过Http 消息进行通信。其中前者是Socket 客户端,后者是Socket 服务端。
1.Socket 类
套接字(Socket)指的就是网络连接的一个端点。可以从网络中读取和写入数据。
Java 中创建一个Socket 有很多构造方法。可以通过主机名/IP地址、端口创建一个Socket 对象
new Socket("127.0.0.1",80);
Socket 创建以后,就可以使用它来发送和接收数据。
// 1.创建一个Socket 客户端对象
Socket socket = new Socket("127.0.0.1", 80);
// 2.关闭Socket
socket.close();
为了从Web 服务器获取适当的响应,需要发送一个遵守Http 协议的 Http 请求。
2.ServerSocket 类
上面Socket 类表示的是客户端的套接字,在你需要远程连接到一个服务器的时候,你需要创建的套接字。但是如果,你是创建的服务器应用,那么显然这里的逻辑会有所不同,应为作为服务端,你需要监听客户端的行为。
这里我们采用ServerSocket 类,ServerSocket 会等待客户端发来的请求,一旦ServerSocket 获得一个连接请求,它会创建一个Socket 实例来与客户端进行通信。
创建ServerSocket 也有多个构造方法,构造方法中有一个重要的属性:backlog,表示服务端开始拒绝传入的请求前,可传入的连接请求的最大值。
new ServerSocket(80,3);
在实际工作中,可以通过ServerSocket 的accept 方法监听客户端是否有连接请求,当接收到连接的请求时,accept 方法会返回一个Socket 对象,用来与客户端进行交互。
ServerSocket serverSocket = new ServerSocket(80,10);
int count = 0;
while(true){
System.out.println(count++);
Socket socket = serverSocket.accept();
System.out.println("New connection accepted "+
socket.getInetAddress()+":"+socket.getPort());
socket.close();
}
3.模拟简单的Web 服务器
三个核心的类:HttpServer、Request、Response。
下面的示例就是这三个核心类的具体代码。
其中主方法main 在HttpServer 中,它初始化了一个ServerSocket 然后一直await 等待监听到客户端的程序。监听到了之后,将输入作为Request ,然后输出Response ,整个过程一直这样监听者直到接收到一个SHUTDOWN 的指令,退出程序。
public class HttpServer {
// 这里定义的目录是用来存放一些静态的html 文件或者txt 文件或者jpg 等静态文件
public static final String WEB_ROOT = System.getProperty("user.dir")
+ File.separator + "webroot";
// 关闭命令
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
private boolean shutdown = false;
public static void main(String[] args) {
HttpServer server = new HttpServer();
server.await();
}
public void await() {
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();
// 创建请求对象然后解析
Request request = new Request(input);
request.parse();
// 创建响应对象
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
// 关闭socket
socket.close();
// 判断是否有SHUTDOWN 指令
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
} catch (Exception e) {
e.printStackTrace();
continue;
}
}
}
}
一个HttpServer 相当于一个ServerSocket 服务端的套接字,等待Socket 客户端的请求。
再收到客户端的请求后(我们这里是浏览器访问 http://localhost:8080/index.html),创建Request对象,并返回Response对象。
public class Request {
private InputStream input;
private String uri;
public Request(InputStream input) {
this.input = input;
}
public void parse() {
// 从Socket 中读取字符集合
StringBuffer request = new StringBuffer(2048);
int i;
byte[] buffer = new byte[2048];
try {
i = input.read(buffer);
} catch (IOException e) {
e.printStackTrace();
i = -1;
}
for (int j = 0; j < i; j++) {
request.append((char) buffer[j]);
}
System.out.println("=================== REQUEST PARSE STRING START ===");
System.out.print(request.toString());
System.out.println("=================== REQUEST PARSE STRING END ===");
uri = parseUri(request.toString());
}
/**
* 根据http 请求头的格式,读取出请求的uri
* @param requestString
* @return
*/
private String parseUri(String requestString) {
int index1, index2;
index1 = requestString.indexOf(' ');
if (index1 != -1) {
index2 = requestString.indexOf(' ', index1 + 1);
if (index2 > index1)
return requestString.substring(index1 + 1, index2);
}
return null;
}
public String getUri() {
return uri;
}
}
目前这里的请求Request 对象主要是用来解析浏览器的请求uri 。
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) {
output.write(bytes, 0, ch);
ch = fis.read(bytes, 0, BUFFER_SIZE);
}
} 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();
}
}
}
Response 对象主要是用来向浏览器发送静态资源。我们这里可以发送一个静态文本,图片或者html 文件。
演示:
但是到目前为止,测试 在IE下能够成功,在Google 浏览器下不能成功加载。不知道是不是Google 浏览器做了什么限制。