什么是服务器软件
# 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开发,这两个也是目前使用率比较高的。