I/O 模型
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达。当所等待数据到达时,它被复制到内核中的某个缓冲区。第二步就是把数据从内核缓冲区复制到应用进程缓冲区。
阻塞式 I/O
应用进程被阻塞,直到数据从内核缓冲区复制到应用进程缓冲区中才返回。
在阻塞的过程中,其它应用进程还可以执行,因此阻塞不意味着整个操作系统都被阻塞。因为其它应用进程还可以执行,所以不消耗 CPU 时间,这种模型的 CPU 利用率会比较高。
data = socket.read()
非阻塞式 I/O
应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成,这种方式称为轮询(polling)。
由于 CPU 要处理更多的系统调用,因此这种模型的 CPU 利用率比较低。
多路复用 I/O
selector线程轮询多个socket状态,只有socket有读写事件时,才会通知用户线程进行 I/O。
只需一个线程就能管理多个socket,并且在系统内核中检查状态,大大节约系统资源,适用于连接数多单消息体不大的情况。
linux有 select,poll,epoll
信号驱动 I/O
用户需要 I/O 时,系统为该请求对应的socket注册一个信号函数,然后立即返回;内核数据就绪时,系统发送信号到用户线程。
异步 I/O
用户线程发起一个 asynchronous read 操作到内核,内核立即返回是否请求成功;数据准备完成并转移到用户线程中后,通知用户线程,直接使用数据即可。
五大 I/O 模型比较
- 同步 I/O:将数据从内核缓冲区复制到应用进程缓冲区的阶段(第二阶段),应用进程会阻塞。
- 异步 I/O:第二阶段应用进程不会阻塞。
同步 I/O 包括阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O ,它们的主要区别在第一个阶段。
非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
NIO
selector 监听 channel 的事件,使用 buffer 进行读写
Channel:类似于Stream,但是是双向的,可读可写。实现类:FileChannel、DatagreamChannel、SocketChannel、ServerSocketChannel
Buffer:内部通过一个连续的字节数组存储数据。Channel 在文件、网络上的读写都必须经过 buffer。实现类:ByteBuffer、IntBuffer、CharBuffer等
Selector:用于检测在多个注册的 Channel 上是否有 I/O 事件发生。
NIO 与传统 I/O 的区别:
- 传统 I/O 面向流,NIO面向缓存区。
- 传统 I/O 阻塞式,NIO非阻塞。
TCP编程
Server端
public class TCPServer {
public static void main(String[] args) throws IOException {
// 监听指定端口
ServerSocket ss = new ServerSocket(6666);
System.out.println("server is running...");
for (;;) {
// 每当有新的客户端连接进来后,就返回一个Socket实例
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;
}
@Override
public void run() {
// Socket流
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 writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
writer.write("hello\n");
// 调用flush()强制把缓冲区数据发送出去
writer.flush();
for (;;) {
String s = reader.readLine();
if (s.equals("bye")) {
writer.write("bye\n");
writer.flush();
break;
}
writer.write("ok: " + s + "\n");
writer.flush();
}
}
}
Client端
public class TCPClient {
public static void main(String[] args) throws IOException {
// 连接指定服务器和端口
Socket sock = new Socket("localhost", 6666);
try (InputStream input = sock.getInputStream()) {
try (OutputStream output = sock.getOutputStream()) {
handle(input, output);
}
}
sock.close();
System.out.println("disconnected.");
}
private static void handle(InputStream input, OutputStream output) throws IOException {
var writer = new BufferedWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8));
var reader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
Scanner scanner = new Scanner(System.in);
System.out.println("[server] " + reader.readLine());
for (;;) {
System.out.print(">>> "); // 打印提示
String s = scanner.nextLine(); // 读取一行输入
writer.write(s);
writer.newLine();
writer.flush();
String resp = reader.readLine();
System.out.println("<<< " + resp);
if (resp.equals("bye")) {
break;
}
}
}
}
UDP编程
Server端
public class UDPServer {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket(6666); // 监听指定端口
for (;;) { // 无限循环
// 数据缓冲区:
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet); // 收取一个UDP数据包
// 收取到的数据存储在buffer中,由packet.getOffset(), packet.getLength()指定起始位置和长度
// 将其按UTF-8编码转换为String:
String s = new String(packet.getData(), packet.getOffset(), packet.getLength(), StandardCharsets.UTF_8);
System.out.println(packet.getAddress() + " :" + s);
// 发送数据:
byte[] data = "ACK".getBytes(StandardCharsets.UTF_8);
packet.setData(data);
ds.send(packet);
}
}
}
Client端:
public class UDPClient {
public static void main(String[] args) throws IOException {
DatagramSocket ds = new DatagramSocket();
ds.setSoTimeout(1000);
ds.connect(InetAddress.getByName("localhost"), 6666); // 连接指定服务器和端口
// 发送:
byte[] data = "Hello szy".getBytes();
DatagramPacket packet = new DatagramPacket(data, data.length);
ds.send(packet);
System.out.println("send :" + new String(data));
// 接收:
byte[] buffer = new byte[1024];
packet = new DatagramPacket(buffer, buffer.length);
ds.receive(packet);
String resp = new String(packet.getData(), packet.getOffset(), packet.getLength());
System.out.println("return :" + resp);
ds.disconnect();
}
}
Email收发
发送邮件前,我们首先要确定作为MTA的邮件服务器地址和端口号。邮件服务器地址通常是smtp.example.com
,端口号由邮件服务商确定使用25、465还是587。以下是一些常用邮件服务商的SMTP信息:
- QQ邮箱:SMTP服务器是smtp.qq.com,端口是465/587;
- 163邮箱:SMTP服务器是smtp.163.com,端口是465;
- Gmail邮箱:SMTP服务器是smtp.gmail.com,端口是465/587。
创建一个Maven工程,并把JavaMail相关的两个依赖加入进来:
<dependency>
<groupId>javax.mail</groupId>
<artifactId>javax.mail-api</artifactId>
<version>1.6.2</version>
</dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<version>1.6.2</version>
</dependency>
通过JavaMail API连接到SMTP服务器上:
// 服务器地址:
String smtp = "smtp.qq.com";
// 登录用户名:
String username = "773508803@qq.com";
// 登录口令:
String password = "xuhxspjtabwxbfbh";
// 连接到SMTP服务器端口:
Properties props = new Properties();
props.put("mail.smtp.host", smtp); // SMTP主机名
props.put("mail.smtp.port", "587"); // 主机端口号
props.put("mail.smtp.auth", "true"); // 是否需要用户认证
props.put("mail.smtp.starttls.enable", "true"); // 启用TLS加密
// 获取Session实例:
Session session = Session.getInstance(props, new Authenticator() {
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
// 设置debug模式便于调试:
session.setDebug(true);
发送邮件
发送邮件时,我们需要构造一个Message
对象,然后调用Transport.send(Message)
即可完成发送:
MimeMessage message = new MimeMessage(session);
// 设置发送方地址:
message.setFrom(new InternetAddress("me@example.com"));
// 设置接收方地址:
message.setRecipient(Message.RecipientType.TO, new InternetAddress("xiaoming@somewhere.com"));
// 设置邮件主题:
message.setSubject("Hello", "UTF-8");
// 设置邮件正文:
message.setText("Hi Xiaoming...", "UTF-8");
// 发送html邮件
message.setText(body, "UTF-8", "html");
// 发送:
Transport.send(message);
发送附件
要在电子邮件中携带附件,我们就不能直接调用message.setText()
方法,而是要构造一个Multipart
对象:
Multipart multipart = new MimeMultipart();
// 内嵌图片
// 添加text:
BodyPart textpart = new MimeBodyPart();
textpart.setContent("<h1>Hello!This is SZY!</h1><p><img src=\"cid:img01\"></p>", "text/html;charset=utf-8");
multipart.addBodyPart(textpart);
// 添加image:
BodyPart imagepart = new MimeBodyPart();
imagepart.setFileName("表情包.png");
imagepart.setDataHandler(new DataHandler(new ByteArrayDataSource("D:\\img.jpg", "image/jpeg")));
// 与HTML的<img src="cid:img01">关联:
imagepart.setHeader("Content-ID", "<img01>");
multipart.addBodyPart(imagepart);
// 添加附件:
BodyPart enclosepart = new MimeBodyPart();
enclosepart.setFileName("java.md");
enclosepart.setDataHandler(new DataHandler(new ByteArrayDataSource("D:\\Java.md", "application/octet-stream")));
multipart.addBodyPart(enclosepart);
// 设置邮件内容为multipart:
message.setContent(multipart);
一个Multipart
对象可以添加若干个BodyPart
,其中第一个BodyPart
是文本,即邮件正文,后面的BodyPart是附件。
BodyPart
依靠setContent()
决定添加的内容:
-
用
setContent("...", "text/plain;charset=utf-8")
添加纯文本 -
用
setContent("...", "text/html;charset=utf-8")
添加HTML文本 -
添加附件,需要设置文件名(不一定和真实文件名一致),并且添加一个
DataHandler()
,传入文件的MIME类型。二进制文件可以用application/octet-stream
,Word文档是application/msword
,图片是image/jpeg
或image/png
Servlet
通过Maven来引入Servlet API:
<!--Java Web Application Archive-->
<packaging>war</packaging>
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
实现一个最简单的Servlet:
// WebServlet注解表示这是一个Servlet,并映射到地址/:
@WebServlet(urlPatterns = "/")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
// 设置响应类型:
res.setContentType("text/html");
// 获取输出流:
PrintWriter pw = res.getWriter();
// 写入响应:
pw.write("<h1>Hello, world!</h1>");
// 最后不要忘记flush强制输出:
pw.flush();
}
}
我们还需要在工程目录下创建一个web.xml
描述文件,放到src/main/webapp/WEB-INF
目录下(固定目录结构,不要修改路径,注意大小写)。文件内容可以固定如下:
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<display-name>Archetype Created Web Application</display-name>
</web-app>
整个工程结构如下:
web-servlet-hello
├── pom.xml
└── src
└── main
├── java
│ └── com
│ └── itranswarp
│ └── learnjava
│ └── servlet
│ └── HelloServlet.java
├── resources
└── webapp
└── WEB-INF
└── web.xml
HttpServletRequest
通过HttpServletRequest
提供的接口方法可以拿到HTTP请求的几乎全部信息,常用的方法有:
- getMethod():返回请求方法,例如,
"GET"
,"POST"
; - getRequestURI():返回请求路径,但不包括请求参数,例如,
"/hello"
; - getQueryString():返回请求参数,例如,
"name=Bob&a=1&b=2"
; - getParameter(name):返回请求参数,GET请求从URL读取参数,POST请求从Body中读取参数;
- getContentType():获取请求Body的类型,例如,
"application/x-www-form-urlencoded"
; - getContextPath():获取当前Webapp挂载的路径,对于ROOT来说,总是返回空字符串
""
; - getCookies():返回请求携带的所有Cookie;
- getHeader(name):获取指定的Header,对Header名称不区分大小写;
- getHeaderNames():返回所有Header名称;
- getInputStream():如果该请求带有HTTP Body,该方法将打开一个输入流用于读取Body;
- getReader():和getInputStream()类似,但打开的是Reader;
- getRemoteAddr():返回客户端的IP地址;
- getScheme():返回协议类型,例如,
"http"
,"https"
;
此外,HttpServletRequest
还有两个方法:setAttribute()
和getAttribute()
,可以给当前HttpServletRequest
对象附加多个Key-Value,相当于把HttpServletRequest
当作一个Map<String, Object>
使用。
HttpServletResponse
HttpServletResponse
封装了一个HTTP响应。由于HTTP响应必须先发送Header,再发送Body,所以,操作HttpServletResponse
对象时,必须先调用设置Header的方法,最后调用发送Body的方法。
常用的设置Header的方法有:
- setStatus(sc):设置响应代码,默认是
200
; - setContentType(type):设置Body的类型,例如,
"text/html"
; - setCharacterEncoding(charset):设置字符编码,例如,
"UTF-8"
; - setHeader(name, value):设置一个Header的值;
- addCookie(cookie):给响应添加一个Cookie;
- addHeader(name, value):给响应添加一个Header,因为HTTP协议允许有多个相同的Header;
写入响应时,需要通过getOutputStream()
获取写入流,或者通过getWriter()
获取字符流,二者只能获取其中一个。
重定向与转发
重定向
HttpServletResponse
提供了快捷的redirect()
方法实现302重定向:
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
// 构造重定向的路径:
String name = req.getParameter("name");
String redirectToUrl = "/hello" + (name == null ? "" : "?name=" + name);
// 发送重定向响应:
res.sendRedirect(redirectToUrl);
}
如果要实现301永久重定向,可以这么写:
resp.setStatus(HttpServletResponse.SC_MOVED_PERMANENTLY); // 301
resp.setHeader("Location", "/hello");
Forward
Forward是指内部转发。当一个Servlet处理请求的时候,它可以决定自己不继续处理,而是转发给另一个Servlet处理
ForwardServlet
在收到请求后,它并不自己发送响应,而是把请求和响应都转发给路径为/hello
的Servlet,即下面的代码:
req.getRequestDispatcher("/hello").forward(req, res);
转发和重定向的区别在于,转发是在Web服务器内部完成的,对浏览器来说,它只发出了一个HTTP请求
Session
我们把这种基于唯一ID识别用户身份的机制称为Session。每个用户第一次访问服务器后,会自动获得一个Session ID。如果用户在一段时间内没有访问服务器,那么Session会自动失效,下次即使带着上次分配的Session ID访问,服务器也认为这是一个新用户,会分配新的Session ID
对于大型Web应用程序来说,通常需要避免使用Session机制
当一个用户登录成功后,我们就可以把这个用户的名字放入一个HttpSession
对象,以便后续访问其他页面的时候,能直接从HttpSession
取出用户名:
@WebServlet(urlPatterns = "/signin")
public class SignInServlet extends HttpServlet {
// 模拟一个数据库:
private Map<String, String> users = Map.of("bob", "bob123", "alice", "alice123", "tom", "tomcat");
// GET请求时显示登录页:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Sign In</h1>");
pw.write("<form action=\"/signin\" method=\"post\">");
pw.write("<p>Username: <input name=\"username\"></p>");
pw.write("<p>Password: <input name=\"password\" type=\"password\"></p>");
pw.write("<p><button type=\"submit\">Sign In</button> <a href=\"/\">Cancel</a></p>");
pw.write("</form>");
pw.flush();
}
// POST请求时处理用户登录:
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("username");
String password = req.getParameter("password");
String expectedPassword = users.get(name.toLowerCase());
if (expectedPassword != null && expectedPassword.equals(password)) {
// 登录成功:
req.getSession().setAttribute("user", name);
resp.sendRedirect("/");
} else {
resp.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
}
在IndexServlet
中,可以从HttpSession
取出用户名:
@WebServlet(urlPatterns = "/")
public class IndexServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从HttpSession获取当前用户名:
String user = (String) req.getSession().getAttribute("user");
resp.setContentType("text/html");
resp.setCharacterEncoding("UTF-8");
resp.setHeader("X-Powered-By", "JavaEE Servlet");
PrintWriter pw = resp.getWriter();
pw.write("<h1>Welcome, " + (user != null ? user : "Guest") + "</h1>");
if (user == null) {
// 未登录,显示登录链接:
pw.write("<p><a href=\"/signin\">Sign In</a></p>");
} else {
// 已登录,显示登出链接:
pw.write("<p><a href=\"/signout\">Sign Out</a></p>");
}
pw.flush();
}
}
如果用户已登录,可以通过访问/signout
登出。登出逻辑就是从HttpSession
中移除用户相关信息:
@WebServlet(urlPatterns = "/signout")
public class SignOutServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 从HttpSession移除用户名:
req.getSession().removeAttribute("user");
resp.sendRedirect("/");
}
}
Cookie
实际上,Servlet提供的HttpSession
本质上就是通过一个名为JSESSIONID
的Cookie来跟踪用户会话的。除了这个名称外,其他名称的Cookie我们可以任意使用。
设置Cookie,例如,记录用户选择的语言:
@WebServlet(urlPatterns = "/pref")
public class LanguageServlet extends HttpServlet {
private static final Set<String> LANGUAGES = Set.of("en", "zh");
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String lang = req.getParameter("lang");
if (LANGUAGES.contains(lang)) {
// 创建一个新的Cookie:
Cookie cookie = new Cookie("lang", lang);
// 该Cookie生效的路径范围:
cookie.setPath("/");
// 该Cookie有效期:
cookie.setMaxAge(8640000); // 8640000秒=100天
// 将该Cookie添加到响应:
resp.addCookie(cookie);
}
resp.sendRedirect("/");
}
}
如果访问的是https网页,还需要调用setSecure(true)
,否则浏览器不会发送该Cookie
读取Cookie,例如,在IndexServlet
中,读取名为lang
的Cookie以获取用户设置的语言:
private String parseLanguageFromCookie(HttpServletRequest req) {
// 获取请求附带的所有Cookie:
Cookie[] cookies = req.getCookies();
// 如果获取到Cookie:
if (cookies != null) {
// 循环每个Cookie:
for (Cookie cookie : cookies) {
// 如果Cookie名称为lang:
if (cookie.getName().equals("lang")) {
// 返回Cookie的值:
return cookie.getValue();
}
}
}
// 返回默认值:
return "en";
}
JSP(后端为主)
JSP是Java Server Pages的缩写,它的文件必须放到/src/main/webapp
下,文件名必须以.jsp
结尾,整个文件与HTML并无太大区别,但需要插入变量,或者动态输出的地方,使用特殊指令<% ... %>
:
- 包含在
<%--
和--%>
之间的是JSP的注释,它们会被完全忽略; - 包含在
<%
和%>
之间的是Java代码,可以编写任意Java代码; - 如果使用
<%= xxx %>
则可以快捷输出一个变量的值。
JSP页面内置了几个变量:
- out:表示HttpServletResponse的PrintWriter;
- session:表示当前HttpSession对象;
- request:表示HttpServletRequest对象。
JSP在执行前首先被编译成一个Servlet。在Tomcat的临时目录下,可以找到一个hello_jsp.java
的源文件,这个文件就是Tomcat把JSP自动转换成的Servlet源码
<body>
<%-- JSP Comment --%>
<h1>Hello World!</h1>
<p>
<%
out.println("Your IP address is ");
%>
<span style="color:red">
<%= request.getRemoteAddr() %>
</span>
</p>
</body>
JSP页面本身可以通过page
指令引入Java类:
<%@ page import="java.io.*" %>
<%@ page import="java.util.*" %>
这样后续的Java代码才能引用简单类名而不是完整类名。
使用include
指令可以引入另一个JSP文件:
<html>
<body>
<%@ include file="header.jsp"%>
<h1>Index Page</h1>
<%@ include file="footer.jsp"%>
</body>
MVC
使用原生Servlet和JSP的MVC框架
**Model:**假设我们已经编写了几个JavaBean:
public class User {
public long id;
public String name;
public School school;
}
public class School {
public String name;
public String address;
}
**Controller:**在UserServlet
中,我们可以从数据库读取User
、School
等信息,然后,把读取到的JavaBean先放到HttpServletRequest中,再通过forward()
传给user.jsp
处理:
@WebServlet(urlPatterns = "/user")
public class UserServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 假装从数据库读取:
School school = new School("No.1 Middle School", "101 South Street");
User user = new User(123, "Bob", school);
// 放入Request中:
req.setAttribute("user", user);
// forward给user.jsp:
req.getRequestDispatcher("/WEB-INF/user.jsp").forward(req, resp);
}
}
**View:**在user.jsp
中,我们只负责展示相关JavaBean的信息,不需要编写访问数据库等复杂逻辑:
<%@ page import="com.itranswarp.learnjava.bean.*"%>
<%
User user = (User) request.getAttribute("user");
%>
<html>
<head>
<title>Hello World - JSP</title>
</head>
<body>
<h1>Hello <%= user.name %>!</h1>
<p>School Name:
<span style="color:red">
<%= user.school.name %>
</span>
</p>
<p>School Address:
<span style="color:red">
<%= user.school.address %>
</span>
</p>
</body>
</html>
SpringBoot+Thymeleaf
参数
// 通过model
@RequestParam()
// 通过/{id}
@PathVariable("id")
返回值
// 什么都不加,返回视图,进入视图解析器拼接前后缀形成页面
return "login";
modelAndView.setViewName("login");
// 重定向
return "redirect:/login";
response.sendRedirect("/login");
// 返回各种值
@ResponseBody
// 返回模板参数
request.setAttribute("msg","输入有误");
model.addAttribute("msg","输入有误");
modelMap.addAttribute("msg","输入有误");
modelAndView.addObject("msg","输入有误");
Session
// 获取session
session = request.getSession()
// 获取属性
session.getAttribute("loginUser");
// 设置session
session.setAttribute("loginUser", username);
// 设置session失效
session.invalidate();
MVVC
Nginx&Apache&Tomcat
定义
-
Apache
Apache HTTP Server(简称Apache)是Apache软件基金会的一个开放源码的网页,它是一个模块化的服务器,可以运行在几乎所有广泛使用的计算机平台上。其属于应用服务器。
Apache支持模块多,性能稳定,Apache本身是静态解析,适合静态HTML、图片等,但可以通过扩展脚本、模块等支持动态页面等。
缺点:配置相对复杂,自身不支持动态页面。
优点:相对于Tomcat服务器来说处理静态文件是它的优势,速度快。Apache是静态解析,适合静态HTML、图片等。
(Apche可以支持PHPcgiperl,但是要使用Java的话,你需要Tomcat在Apache后台支撑,将Java请求由Apache转发给Tomcat处理。) -
Tomcat:
Tomcat 是Apache 软件基金会(Apache Software Foundation)的Jakarta 项目中的一个核心项目。Tomcat 服务器是一个免费的开放源代码的Web 应用服务器,属于轻量级应用服务器。
Tomcat是应用(Java)服务器,它只是一个Servlet(JSP也翻译成Servlet)容器,可以认为是Apache的扩展,但是可以独立于Apache运行。
缺点:可以说Tomcat 只能用做java服务器
优点:动态解析容器,处理动态请求,是编译JSP/Servlet的容器。三个端口:
8005:关闭tomcat通信接口
8009:与其他http服务器通信接口,用于http服务器集合
8080:建立http连接 用,如浏览器访问 -
Nginx
Nginx是俄罗斯人编写的十分轻量级的HTTP服务器,Nginx,它的发音为“engine X”,是一个高性能的HTTP和反向代理服务器,同时也是一个IMAP/POP3/SMTP 代理服务器。其特点是占有内存少,并发能力强,易于开发,部署方便。Nginx 支持多语言通用服务器。
缺点:Nginx 只适合静态和反向代理。
优点:负载均衡、反向代理、处理静态文件优势。Nginx 处理静态请求的速度高于Apache。
Nginx有动态分离机制,静态请求直接就可以通过Nginx处理,动态请求才转发请求到后台交由Tomcat进行处理。
Tomcat结合Apache、Nginx实现高性能的web服务器
Tomcat虽然是一个servlet和jsp容器,但是它也是一个轻量级的web服务器。它既可以处理动态内容,也可以处理静态内容。不过,tomcat的最大优势在于处理动态请求,处理静态内容的能力不如apache和nginx,并且经过测试发现,tomcat在高并发的场景下,其接受的最大并发连接数是有限制的,连接数过多会导致tomcat处于"僵死"状态,因此,在这种情况下,我们可以利用nginx的高并发,低消耗的特点与tomcat一起使用。因此,tomcat与nginx、apache结合使用共有如下几点原因:
1、tomcat处理html的能力不如Apache和nginx,tomcat处理静态内容的速度不如apache和nginx。
2、tomcat接受的最大并发数有限,连接数过多,会导致tomcat处于"僵尸"状态,对后续的连接失去响应,需要结合nginx一起使用。
通常情况下,tomcat与nginx、Apache结合使用,nginx、apache既可以提供web服务,也可以转发动态请求至tomcat服务器上。但在一个高性能的站点上,通常nginx、apache只提供代理的功能,也就是转发请求至tomcat服务器上,而对于静态内容的响应,则由前端负载均衡器来转发至专门的静态服务器上进行处理。其架构类似于如下图:
在这种架构中,当haproxy或nginx作为前端代理时,如果是静态内容,如html、css等内容,则直接交给静态服务器处理;如果请求的图片等内容,则直接交给图片服务器处理;如果请求的是动态内容,则交给tomcat服务器处理,不过在tomcat服务器上,同时运行着nginx服务器,此时的nginx作为静态服务器,它不处理静态请求,它的作用主要是接受请求,并将请求转发给tomcat服务器的,除此之外,nginx没有任何作用。
区别
1)Nginx和tomcat的区别
nginx常用做静态内容服务和代理服务器,直接外来请求转发给后面的应用服务器(tomcat,Django等),tomcat更多用来做一个应用容器,让java web app泡在里面的东西。严格意义上来讲,Apache和nginx应该叫做HTTP Server,而tomcat是一个Application Server是一个Servlet/JSO应用的容器。
客户端通过HTTP Server访问服务器上存储的资源(HTML文件,图片文件等),HTTP Server是中只是把服务器上的文件如实通过HTTP协议传输给客户端。
应用服务器往往是运行在HTTP Server的背后,执行应用,将动态的内容转化为静态的内容之后,通过HTTP Server分发到客户端
注意:nginx只是把请求做了分发,不做处理!!!
2)nginx和Apache的区别
Apache是同步多进程模型,一个连接对应一个进程,而nginx是异步的,多个连接(万级别)可以对应一个进程。
nginx轻量级,抗并发,处理静态文件好
Apache超稳定,对PHP支持比较简单,nginx需要配合其他后端用,处理动态请求有优势,建议使用前端nginx抗并发,后端apache集群,配合起来会更好
nignx的正向代理和反向代理
3、优缺点比较
- nginx相对于apache的优点
轻量级,同样起web 服务,比apache占用更少的内存及资源 抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能高度模块化的设计,编写模块相对简单提供负载均衡
社区活跃,各种高性能模块出品迅速 - apache 相对于nginx 的优点
apache的 rewrite(重写) 比nginx 的强大 ;
支持动态页面;
支持的模块多,基本涵盖所有应用;
性能稳定,而nginx相对bug较多。 - 两者优缺点比较
Nginx 配置简洁, Apache 复杂 ;
Nginx 静态处理性能比 Apache 高 3倍以上 ;
Apache 对 PHP 支持比较简单,Nginx 需要配合其他后端用;Apache 的组件比 Nginx 多 ;
apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程;
nginx处理静态文件好,耗费内存少;
动态请求由apache去做,nginx只适合静态和反向;
Nginx适合做前端服务器,负载性能很好;
Nginx本身就是一个反向代理服务器 ,且支持负载均衡
总结
**Nginx优点:**负载均衡、反向代理、处理静态文件优势。nginx处理静态请求的速度高于apache;
**Apache优点:**相对于Tomcat服务器来说处理静态文件是它的优势,速度快。Apache是静态解析,适合静态HTML、图片等。
**Tomcat:**动态解析容器,处理动态请求,是编译JSPServlet的容器,Nginx有动态分离机制,静态请求直接就可以通过Nginx处理,动态请求才转发请求到后台交由Tomcat进行处理。
Apache在处理动态有优势,Nginx并发性比较好,CPU内存占用低,如果rewrite频繁,那还是Apache较适合。
3、优缺点比较
- nginx相对于apache的优点
轻量级,同样起web 服务,比apache占用更少的内存及资源 抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能高度模块化的设计,编写模块相对简单提供负载均衡
社区活跃,各种高性能模块出品迅速 - apache 相对于nginx 的优点
apache的 rewrite(重写) 比nginx 的强大 ;
支持动态页面;
支持的模块多,基本涵盖所有应用;
性能稳定,而nginx相对bug较多。 - 两者优缺点比较
Nginx 配置简洁, Apache 复杂 ;
Nginx 静态处理性能比 Apache 高 3倍以上 ;
Apache 对 PHP 支持比较简单,Nginx 需要配合其他后端用;Apache 的组件比 Nginx 多 ;
apache是同步多进程模型,一个连接对应一个进程;nginx是异步的,多个连接(万级别)可以对应一个进程;
nginx处理静态文件好,耗费内存少;
动态请求由apache去做,nginx只适合静态和反向;
Nginx适合做前端服务器,负载性能很好;
Nginx本身就是一个反向代理服务器 ,且支持负载均衡
总结
**Nginx优点:**负载均衡、反向代理、处理静态文件优势。nginx处理静态请求的速度高于apache;
**Apache优点:**相对于Tomcat服务器来说处理静态文件是它的优势,速度快。Apache是静态解析,适合静态HTML、图片等。
**Tomcat:**动态解析容器,处理动态请求,是编译JSPServlet的容器,Nginx有动态分离机制,静态请求直接就可以通过Nginx处理,动态请求才转发请求到后台交由Tomcat进行处理。
Apache在处理动态有优势,Nginx并发性比较好,CPU内存占用低,如果rewrite频繁,那还是Apache较适合。