什么是服务器软件
# 00.什么是服务器软件
在学习 JavaWeb 开发之前,我们简单回顾下网络相关的概念
# Web 相关概念回顾
# 软件架构
目前软件架构主要分为 2 种:
- C/S:客户端/服务器端,简单来说就是需要在客户/用户的电脑上安装软件后才能使用的,例如浏览器,VSCode,IDEA 等工具,安装后才能使用这些软件。
- B/S:浏览器/服务器端,简单来说就是在浏览器里访问的应用,不用安装特定的软件也可以使用,客户只需用浏览器访问网页即可使用,例如淘宝等电商网站,B 站等视频网站,在浏览器里打开就能使用。
目前流行的 BS 架构,好处在于:
- BS 架构免安装,打开浏览器就能用,非常方便;
- 解决了兼容性问题,只要有浏览器,不管是 Windows,Linux 还是 Mac 电脑,都能访问得到。如果是 CS 架构,通常还需要对不同平台做兼容性处理(虽然目前有一些跨平台的计数,但兼容性处理还是有一定的麻烦)
- 更新快:如果是 CS 架构,有更新的话还需要用户下载更新包,或者安装包,然后才能更新。而如果是网页应用,用户不需要更新,刷新网页即可。
当然 CS 架构也有优点,资源都是在本地的,打开速度快,方便离线使用。
# 资源分类
在 BS 架构中,用户访问的资源可以分为两类:
- 静态资源:所有用户访问后,得到的结果都是一样的,称为静态资源。静态资源可以直接被浏览器解析,浏览器本身就有静态资源解析引擎。常见的静态资源: HTML,CSS 和 JavaScript
- 动态资源:每个用户访问相同资源后,得到的结果可能不一样。称为动态资源。动态资源被访问后,需要先转换为静态资源,在返回给浏览器。如:Servlet,JSP,PHP,ASP 等
- 客户端请求资源(也叫请求,request),服务器返回资源(也叫响应,response)
举个例子,用户打开一个网站的登录页,所有用户的登录页都是一样的,所以登录页是一种静态资源,里面还包含了登录页的 HTML、CSS 和 JS 等静态资源;
用户登录后,每个用户的信息都是不同的,例如用户名不同,此时需要根据情况动态的展示网页,这样不同用户登录后,看到的东西才是不一样的,才是他们自己的。例如我登录自己的 B 站账号,显示的就是我的登录名
根据用户请求的资源不同,请求可以划分为动态请求和静态请求。
# 网络通信三要素
学习 JavaWeb 之前,读者应该对计算机网络有基本的概念,知道如下几个术语:
IP:电子设备(计算机)在网络中的唯一标识。
端口:应用程序在计算机中的唯一标识。 0~65536
传输协议:规定了数据传输的规则基础协议。例如两个人之间交流,使用的语言得一致,不然一个人说英文,一个人说中文,两者互相听不懂。基础的传输协议如下:
- TCP:安全协议,三次握手,速度稍慢
- UDP:不安全协议, 速度快
- 应用层的传输协议,例如 HTTP,IMAP 等,都是基于上述协议的。
# 什么是服务器软件
服务器:安装了服务器软件的计算机。例如邮件服务器、web 服务器,游戏服务器,MySQL 服务器等等
服务器软件:运行后监听某个端口,接收用户的请求,处理请求,做出响应。例如邮件进程,游戏进程,MySQL 进程等等。
Web 服务器软件:接收用户的 HTTP 请求,处理请求,做出响应。在 Web 服务器软件中,可以部署 Web 项目,让用户通过浏览器来访问这些项目。
之前我们说的动态资源,就得依赖于 Web 服务器去运行,因此 Web 服务器软件也叫 Web 容器。
HTTP 服务器本质上也是一种应用程序,绑定服务器的 IP 地址并监听某一个 TCP 端口来接收并处理 HTTP 请求,这样客户端(一般来说是 IE, Firefox,Chrome 这样的浏览器)就能够通过 HTTP 协议来获取服务器上的网页(HTML 格式)、文档(PDF 格式)、音频(MP4 格式)、视频(MOV 格式)等等资源。下图描述的就是这一过程:
# 自己实现服务器软件
我们来看一下如何编写 HTTP Server。一个 HTTP Server 本质上是一个 TCP 服务器,我们先用 TCP 编程 的多线程实现的服务器端框架:
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(8080); // 监听指定端口
System.out.println("server is running...");
for (;;) {
Socket sock = ss.accept();
System.out.println("connected from " + sock.getRemoteSocketAddress());
Thread t = new Handler(sock);
t.start();
}
}
}
class Handler extends Thread {
Socket sock;
public Handler(Socket sock) {
this.sock = sock;
}
public void run() {
try (InputStream input = this.sock.getInputStream()) {
try (OutputStream output = this.sock.getOutputStream()) {
handle(input, output);
}
} catch (Exception e) {
try {
this.sock.close();
} catch (IOException ioe) {
}
System.out.println("client disconnected.");
}
}
private void handle(InputStream input, OutputStream output) throws IOException {
var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
// TODO: 处理HTTP请求
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
只需要在 handle()
方法中,用 Reader 读取 HTTP 请求,用 Writer 发送 HTTP 响应,即可实现一个最简单的 HTTP 服务器。编写代码如下:
private void handle(InputStream input, OutputStream output) throws IOException {
System.out.println("Process new http request...");
var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
// 读取HTTP请求:
boolean requestOk = false;
String first = reader.readLine();
if (first.startsWith("GET / HTTP/1.")) {
requestOk = true;
}
for (;;) {
String header = reader.readLine();
if (header.isEmpty()) { // 读取到空行时, HTTP Header读取完毕
break;
}
System.out.println(header);
}
System.out.println(requestOk ? "Response OK" : "Response Error");
if (!requestOk) {
// 发送错误响应:
writer.write("HTTP/1.0 404 Not Found\r\n");
writer.write("Content-Length: 0\r\n");
writer.write("\r\n");
writer.flush();
} else {
// 发送成功响应:
String data = "<html><body><h1>Hello, world!</h1></body></html>";
int length = data.getBytes(StandardCharsets.UTF_8).length;
writer.write("HTTP/1.0 200 OK\r\n");
writer.write("Connection: close\r\n");
writer.write("Content-Type: text/html\r\n");
writer.write("Content-Length: " + length + "\r\n");
writer.write("\r\n"); // 空行标识Header和Body的分隔
writer.write(data);
writer.flush();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
这里的核心代码是,先读取 HTTP 请求,这里我们只处理 GET /
的请求。当读取到空行时,表示已读到连续两个 \r\n
,说明请求结束,可以发送响应。
发送响应的时候,首先发送响应代码 HTTP/1.0 200 OK
表示一个成功的 200 响应,使用 HTTP/1.0
协议,然后,依次发送 Header,发送完 Header 后,再发送一个空行标识 Header 结束,紧接着发送 HTTP Body,在浏览器输入 http://localhost:8080/
就可以看到响应页面:
可以看到编写一个简单的 HTTP 服务器并不难,只需要先编写基于多线程的 TCP 服务,然后在一个 TCP 连接中读取 HTTP 请求,发送 HTTP 响应即可。
但是,要编写一个完善的 HTTP 服务器,以 HTTP/1.1 为例,需要考虑的包括:
- 识别正确和错误的 HTTP 请求;
- 识别正确和错误的 HTTP 头;
- 复用 TCP 连接;
- 复用线程;
- IO 异常处理;
- ...
这些基础工作需要耗费大量的时间,并且经过长期测试才能稳定运行。如果我们只需要输出一个简单的 HTML 页面,就不得不编写上千行底层代码,那就根本无法做到高效而可靠地开发。
因此,在 JavaEE 平台上,处理 TCP 连接,解析 HTTP 协议这些底层工作统统扔给现成的 Web 服务器去做,我们只需要把自己的应用程序跑在 Web 服务器上。
# 常见的 Web 服务器软件
开源且常见的:
- Nginx:一款自由开源的、高性能的 HTTP 服务器和反向代理服务器,使用率较高。
- Tengine:Tengine 是阿里巴巴在 Nginx 的基础上,添加了很多高级功能和特性改造而成,做了一定的优化
- Tomcat:归属于 Apache 基金组织,中小型的 JavaEE 服务器,开源免费。
- Apache:非常老牌的 WEB 服务器,稳定、开源、跨平台,性能较 Nginx 低
- Lighttpd:开源,CPU 占用率低,内存开销小,也有不少人使用
商业级且常见的:
- WebShare:通常简称 WAS,IBM 公司开发的,大型的 JavaEE 服务器,支持所有的 JavaEE 规范,收费的。
- WebLogic:Oracle 公司开发的,大型的 JavaEE 服务器,支持所有的 JavaEE 规范,收费的。
- JBOSS 公司的,大型的 JavaEE 服务器,支持所有的 JavaEE 规范,收费的。
一般来说商业级的都是重量级的,安装包大、功能强大且繁多。一般得专人支持才能使用,因为是商业级的东西,网上也比较少入门的资料,毕竟是付费产品,如果想要自己学习的话比较难,因为文档资料不会公开,想要安装的话还得购买许可证,门槛高。
Apache 和 Nginx 本身不支持生成动态页面(不提供动态资源),但它们可以通过其他模块来支持(例如通过 Shell、PHP、Python 脚本程序来动态生成内容)。
如果想要使用 Java 程序来动态生成资源内容,使用这一类 HTTP 服务器很难做到。Java Servlet 技术以及衍生的 Java Server Pages 技术可以让 Java 程序也具有处理 HTTP 请求并且返回内容(由程序动态控制)的能力,Tomcat 正是支持运行 Servlet/JSP 应用程序的容器(Container)。后续我们就学习 JavaWeb 技术,使得 Java 可以开发一个网站并且发布。
本系列主要使用 Nginx 和 Tomcat 用于学习 JavaWeb 开发,这两个也是目前使用率比较高的。