How Tomcat Works 1:一个简单的Web 服务器

我们可以将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 浏览器做了什么限制。

猜你喜欢

转载自blog.csdn.net/u014209205/article/details/82874018