网络结构

BS 网络传输的分解方式有两种: 一种是标准的 OSI 参考模型,另一种是 TCP/IP 参考模型,如下图所示:

网络结构

TCP/IP 4 层模型中,

  • 网络接入层:将需要相互连接的节点接入网络层,为数据传输提供条件。

  • 网际互连层:找到要传输的数据的目标节点。典型:IPICMP

  • 传输层:实际传输数据。典型:TCP 协议、UDP

  • 应用层:使用接收到的数据。典型:HttpDNSRPC(会话层协议)

Tips:在 HTTP 层,Java Web 开发中使用的是 Servlet 标准

DNS 协议 和 IP 地址

DNS 协议的作用是将域名解析为 IP 地址。

IP 地址中,

A 类地址:以 0 开头, 第一个字节范围:1~127(1.0.0.0 - 127.255.255.255);

B 类地址:以 10 开头, 第一个字节范围:128~191(128.0.0.0 - 191.255.255.255);

C 类地址:以 110 开头, 第一个字节范围:192~223(192.0.0.0 - 223.255.255.255);

D 类地址:以 1110 开头,第一个字节范围:224~239(224.0.0.0 - 239.255.255.255);(作为多播使用)

E 类地址:保留

其中 A、B、C 是基本类,D、E 类作为多播和保留使用。

以下是留用的内部私有地址:

A 类 10.0.0.0–10.255.255.255

B 类 172.16.0.0–172.31.255.255

C 类 192.168.0.0–192.168.255.255

TCP/IP 协议与 Socket

TCP 三次握手:

第一次握手:客户端发送 syn(syn=x) 到服务器,并进入 SYN_SEND 状态,等待服务器确认;

第二次握手:服务器收到 syn 包,必须确认客户的 SYN(ack=x+1),同时自己也发送一个 SYN 包 (syn=y),即 SYN+ACK 包,此时服务器进入 SYN_RECV 状态;

第三次握手:客户端收到服务器的 SYN+ACK 包,向服务器发送确认包 ACK(ack=y+1),此包发送完毕,客户端和服务器进入 ESTABLISHED 状态,完成三次握手。

握手过程中传送的包里不包含数据,三次握手完毕后,客户端与服务器才正式开始传送数据。理想状态下,TCP 连接一旦建立,在通信双方中的任何一方主动关闭连接之前,TCP 连接都将被一直保持下去。

TCP 四次挥手

第一次挥手:主动关闭方发送一个 FIN,用来关闭主动方到被动关闭方的数据传送,也就是主动关闭方告诉被动关闭方:我已经不 会再给你发数据了 (当然,在 fin 包之前发送出去的数据,如果没有收到对应的 ack 确认报文,主动关闭方依然会重发这些数据),但是,此时主动关闭方还可 以接受数据。

第二次挥手:被动关闭方收到 FIN 包后,发送一个 ACK 给对方,确认序号为收到序号 + 1(与 SYN 相同,一个 FIN 占用一个序号)。

第三次挥手:被动关闭方发送一个 FIN,用来关闭被动关闭方到主动关闭方的数据传送,也就是告诉主动关闭方,我的数据也发送完了,不会再给你发数据了。

第四次挥手:主动关闭方收到 FIN 后,发送一个 ACK 给被动关闭方,确认序号为收到序号 + 1,至此,完成四次挥手。

TCP

其中两个序号和三个标志位含义如下:

  • seq:sequence number 所传数据的序号。

  • ack:acknoledge number 表示确认号。

  • ACK:确认位。

  • SYN:同部位。

  • FIN:终止位。

HTTP 协议

HTTP 中的报文分为:请求报文、响应报文。这两种类型都包括:首行、头部和主体。

HTTP

请求报文中的方法指 GETHEADPOSTPUTDELETE 等类型,响应报文中的状态码就是 Response 中的 status, 一共可以分为 5 类:

  • 1XX: 信息性状态码。

  • 2XX: 成功状态码,如 200 表示成功。

  • 3XX: 重定向状态码, 如 301 表示重定向。

  • 4XX: 客户端错误状态码,如 404 表示没找到请求的资源。

  • 5XX: 服务端错误状态码,如 500 表示内部错误。

Servlet 与 Java Web 开发

Servlet 是 J2EE 标准的一部分,是 JavaWeb 开发的标准。Servlet 的作用是对接收到的数据进行处理并生成要返回给客户端的结果。要想使用 Servlet 需要相应的 Servlet 容器,常见的 Servlet 容器时 Tomcat.

实例

在浏览器中输入 www.baidu.com 后执行的全部过程

  1. 客户端浏览器通过 DNS 解析到 www.baidu.com 的 IP 地址 220.181.27.48,通过这个 IP 地址找到客户端到服务器的路径。客户端浏览器发起一个 HTTP 会话到 220.181.27.48,然后通过 TCP 进行封装数据包,输入到网络层。

  2. 在客户端的传输层,把 HTTP 会话请求分成报文段,添加源和目的端口,如服务器使用 80 端口监听客户端的请求,客户端由系统随机选择一个端口如 5000,与服务器进行交换,服务器把相应的请求返回给客户端的 5000 端口。然后使用 IP 层的 IP 地址查找目的端。

  3. 客户端的网络层不用关心应用层或者传输层的东西,主要做的是通过查找路由表确定如何到达服务器,期间可能经过多个路由器,这些都是由路由器来完成的工作,我不作过多的描述,无非就是通过查找路由表决定通过那个路径到达服务器。

  4. 客户端的链路层,包通过链路层发送到路由器,通过邻居协议查找给定 IP 地址的 MAC 地址,然后发送 ARP 请求查找目的地址,如果得到回应后就可以使用 ARP 的请求应答交换的 IP 数据包现在就可以传输了,然后发送 IP 数据包到达服务器的地址。

动态代理和静态代理

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的. class 文件就已经存在了。

动态:在程序运行时运用反射机制动态创建而成。

正向代理和反向代理

正向代理(forward proxy) ,一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并制定目标(原始服务器),然后代理向原始服务器转发请求并将获得的内容返回给客户端,客户端才能使用正向代理。我们平时说的代理就是指正向代理。

反向代理(Reverse Proxy),以代理服务器来接受 internet 上的连接请求,然后将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给 internet 上请求的客户端,此时代理服务器对外表现为一个反向代理服务器。

TCP UDP 区别

TCP 与 UDP 的区别:

  1. 基于连接与无连接;
  2. 对系统资源的要求(TCP 较多,UDP 少);
  3. UDP 程序结构较简单;
  4. 流模式与数据报模式 ;
  5. TCP 保证数据正确性,UDP 可能丢包,TCP 保证数据顺序,UDP 不保证。

Tcp连接原理

TCP 协议是端到端的传输控制协议,之所以是 “端到端” 的协议,是因为” 路由 “是由 IP 协议负责的,TCP 协议负责为两个通信端点提供可靠性保证,这个可靠性不是指一个端点发送的数据,另一个端点肯定能收到(这显然是不可能的),而是指,数据的可靠投递或者故障的可靠通知。

Http post和get

在客户机和服务器之间进行请求 - 响应时,两种最常被用到的方法是:GET 和 POST。

  • GET - 从指定的资源请求数据。
  • POST - 向指定的资源提交要被处理的数据

http 和 https 的区别 https 的加密方式

HTTP 协议传输的数据都是未加密的,也就是明文的,因此使用 HTTP 协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了 SSL(Secure Sockets Layer)协议用于对 HTTP 协议传输的数据进行加密,从而就诞生了 HTTPS。简单来说,HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,要比 http 协议安全。

HTTPS 和 HTTP 的区别主要如下:

1、https 协议需要到 ca 申请证书,一般免费证书较少,因而需要一定费用。

2、http 是超文本传输协议,信息是明文传输,https 则是具有安全性的 ssl 加密传输协议。

3、http 和 https 使用的是完全不同的连接方式,用的端口也不一样,前者是 80,后者是 443。

4、http 的连接很简单,是无状态的;HTTPS 协议是由 SSL+HTTP 协议构建的可进行加密传输、身份认证的网络协议,比 http 协议安全。

Cookie和session

1、cookie 数据存放在客户的浏览器上,session 数据放在服务器上。

2、cookie 不是很安全,别人可以分析存放在本地的 COOKIE 并进行 COOKIE 欺骗,考虑到安全应当使用 session。

3、session 会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用 COOKIE。

4、单个 cookie 保存的数据不能超过 4K,很多浏览器都限制一个站点最多保存 20 个 cookie。

Tomcat如何处理http

Tomcat如何处理http

  1. 用户在浏览器中输入网址 localhost:8080/test/index.jsp,请求被发送到本机端口 8080,被在那里监听的 Coyote HTTP/1.1 Connector 获得;

  2. Connector 把该请求交给它所在的 Service 的 Engine(Container)来处理,并等待 Engine 的回应;

  3. Engine 获得请求 localhost/test/index.jsp,匹配所有的虚拟主机 Host;

  4. Engine 匹配到名为 localhost 的 Host(即使匹配不到也把请求交给该 Host 处理,因为该 Host 被定义为该 Engine 的默认主机),名为 localhost 的 Host 获得请求 / test/index.jsp,匹配它所拥有的所有 Context。Host 匹配到路径为 / test 的 Context(如果匹配不到就把该请求交给路径名为 “ ” 的 Context 去处理);

  5. path=“/test” 的 Context 获得请求 / index.jsp,在它的 mapping table 中寻找出对应的 Servlet。Context 匹配到 URL Pattern 为 *.jsp 的 Servlet,对应于 JspServlet 类;

  6. 构造 HttpServletRequest 对象和 HttpServletResponse 对象,作为参数调用 JspServlet 的 doGet() 或 doPost(), 执行业务逻辑、数据存储等;

  7. Context 把执行完之后的 HttpServletResponse 对象返回给 Host;

  8. Host 把 HttpServletResponse 对象返回给 Engine;

  9. Engine 把 HttpServletResponse 对象返回 Connector;

  10. Connector 把 HttpServletResponse 对象返回给客户 Browser。

普通 Socket 用法

Java 中的网络通信通过 Socket 实现的,Socket 分为 ServerSocketSocket 两大类。ServerSocket 用于服务端,可以通过 accept() 方法监听请求返回 SocketSocket 用于具体完成数据传输。客户端直接使用 Socket 发起请求并传输数据。

ServerSocket 的使用分为三步:

  1. 创建 ServerSocket

  2. 调用创建出来的 ServerSocketaccept 方法。

  3. 使用 accept 方法返回 Socket 与客户端进行通信。

Socket 客户端使用:创建一个 Socket,和服务器进行通行,传输数据,释放连接。

ServerSocket 简单示例:

1
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
public class Server {
public static void main(String[] args) {
try {
// 创建一个 ServerSocket 监听 8080 端口
ServerSocket server = new ServerSocket (8080);
// 等待请求
Socket socket = server.accept ();
// 接收到请求后使用 socket 通信,创建 BufferedReader 用于读取数据
BufferedReader br = new BufferedReader (new InputStreamReader ( socket.getInputStream () ) );
String line = br.readLine();
System.out.println ("received from client:" + line);
// 创建 PrintWriter 用于发送数据
PrintWriter pw = new PrintWriter (socket.getOutputStream () );
pw.println ("received data:" + line);
// 关闭读写流之前先 flush(),先清空数据
pw.flush ();
// 关闭资源
pw.close ();
br.close ();
socket.close ();
server.close ();
} catch (Exception e) {
e.printStackTrace ();
}
}
}

ClientSocket 简单示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Clinet {
public static void main(String[] args) {
String msg = "Client Data";
try {
// 创建一个 Socket 和本机的 8080 端口连接
Socket socket = new Socket ("127.0.0.1", 8080);
// 使用 Socket 创建 PrintWriter 和 BufferReader 进行读写数据
PrintWriter pw = new PrintWriter (socket.getOutputStream () );
BufferedReader is = new BufferedReader (new InputStreamReader ( socket.getInputStream () ) );
// 发送数据
pw.println (msg);
pw.flush ();
// 接收数据
String line = is.readLine ();
System.out.println ("received from server" + line);
// 关闭资源
pw.close ();
is.close ();
socket.close ();
} catch (Exception e) {
e.printStackTrace ();
}
}
}

NioSocket 的用法

ServerSocketChannelSocketCHannel,分别对应以前的 ServerSocketSocket

NioSocket 中服务端的处理过程步骤:

  1. 创建 ServerSocketChannel 并设置相应采参数。

  2. 创建 Selector 并注册到 ServerSocketChannel

  3. 调用 Selectorselect 方法等待请求。

  4. Selector 接收请求后使用 selectedKeys 返回 SelectionKey 集合。

  5. 使用 SelectionKey 获取到 ChannelSelector 和操作类型并进行具体的操作。

1
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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
public class NIOServer {
private static class Handler {
private int bufferSize = 1024;
private String localCharset = "UTF-8";
public Handler() {}
public Handler(int bufferSize) {
this(bufferSize, null);
}
public Handler(String LocalCharset) {
this(-1, LocalCharset);
}
public Handler(int bufferSize, String localCharset) {
if(bufferSize> 0) {
this.bufferSize = bufferSize;
}
if(localCharset!=null) {
this.localCharset = localCharset;
}
}
public void handleAccept(SelectionKey key) throws IOException {
SocketChannel sc = ((ServerSocketChannel) key.channel ()). accept();
sc.configureBlocking (false);
sc.register (key.selector (),SelectionKey.OP_READ, ByteBuffer.allocate ( bufferSize ) );
}
public void handleReader(SelectionKey key) throws IOException {
// 获取 channel
SocketChannel sc = (SocketChannel)key.channel ();
// 获取 buffer 并重置
ByteBuffer buffer = (ByteBuffer) key.attachment ();
buffer.clear ();
// 没有读到内容则关闭
if(sc.read ( buffer) == -1) {
sc.close ();
} else {
// 将 buffer 转化为读状态
buffer.flip ();
// 将 buffer 中接收到的值按 localCharset 格式编码到 receivedString
String receivedString = Charset.forName (localCharset).newDecoder(). decode( buffer). toString();
System.out.println ("revceived from client:" + receivedString);
// 返回数据给客户端
String sendString = "received data" + receivedString;
buffer = ByteBuffer.wrap (sendString.getBytes (localCharset) );
sc.write (buffer);
// 关闭 Socket
sc.close ();
}
}
}
public static void main(String[] args) throws Exception {
// 创建 ServerSocketChannel,监听 8080 端口
ServerSocketChannel ssc = ServerSocketChannel.open ();
ssc.socket ().bind ( new InetSocketAddress ( 8080) );
// 设置非阻塞模式
ssc.configureBlocking (false);
// 为 ssc 注册选择器
Selector selector = Selector.open ();
ssc.register (selector, SelectionKey.OP_ACCEPT);
// 创建处理器
Handler handler = new Handler(1024);
while(true) {
// 等待请求,每次等待阻塞 3s,超过 3s 后线程继续向下运行,如果传入 0 或者不传参数将一直阻塞
if(selector.select (3000) == 0) {
System.out.println ("等待请求超时……");
continue;
}
System.out.println ("处理请求……");
// 获取待处理的 SelectionKey
Iterator<SelectionKey> keyIterator = selector.selectedKeys (). iterator ();
while(keyIterator.hasNext ()) {
SelectionKey key = keyIterator.next ();
try {
// 接收到连接请求时
if(key.isAcceptable ()) {
handler.handleAccept (key);
}
// 读数据
if(key.isReadable ()) {
handler.handleReader (key);
}
} catch (IOException ex) {
keyIterator.remove ();
continue;
}
keyIterator.remove ();
}
}
}
}

详解 Servlet

ServletServer + Applet 的缩写,表示一个服务器应用。Servlet 是一套规范,按照此规范编写的代码就能在 java 的服务器上运行

Servlet

Servlet 接口

Servlet 接口定义:

1
2
3
4
5
6
7
8
9
10
11
public interface Servlet {
void init(ServletConfig var1) throws ServletException;
ServletConfig getServletConfig();
void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
String getServletInfo();
void destroy();
}

init 方法:容器启动时被容器调用,只会调用一次。

getServletConfig 方法:用于获得 ServletConfig.
service 方法:用于具体处理一次请求。
getServletInfo 方法:获取一些 Servlet 相关信息,如作者、版权等。
destroy 方法:主要用于 Servlet 销毁时释放一些资源,只会调用一次。

web.xml 中定义 Servlet 时通过 init-param 标签配置的参数就是通过 Servlet 来保存的。Tomcat 中的 Servletinit 方法是在 ocrg.apache.catalina.core.StandardWrapperinitServlet 方法中调用的,ServletConfig 传入的是 StandardWrapper 自身的门面类 StandardWrapperFacade

1
2
3
4
5
6
7
8
9
10
11
12
13
package javax.servlet;
import java.util.Enumeration;
public interface ServletConfig {
String getServletName();
ServletContext getServletContext();
String getInitParameter(String var1);
Enumeration<String> getInitParameterNames();
}

ServletConfig(Servlet 级别)和 ServletContext(Application 级别)是最常见的传递初始化参数的。

GenericServlet

GenericServletServlet 的默认实现,主要做了三件事:实现 ServletConfig 接口,可以直接调用 ServletConfig 里面的方法;提供了无参的 init 方法;提供了 log 方法。

1
2
3
4
5
6
7
8
public ServletContext getServletContext() {
ServletConfig sc = this.getServletConfig();
if (sc == null) {
throw new IllegalStateException(lStrings.getString("err.servlet_config_not_initialized"));
} else {
return sc.getServletContext();
}
}

GenericServlet 实现了 ServletConfig,可以在需要用 ServletConfig 中的方法的时候可以直接调用,而不需要先获取 ServletConfig,不过底层还是使用 ServletConfig 进行调用。

1
2
3
4
5
6
7
8
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();
}
public void init() throws ServletException {
}

GenericServlet 实现了 Servletinit(ServletConfig config)方法,现将 config 设置给了内部变量 config, 然后调用了无参的 init() 方法,这个方法是模板方法,子类可以通过覆盖它来完成自己的初始化工作。

1
2
3
4
5
6
7
public void log(String msg) {
this.getServletContext().log(this.getServletName() + ":" + msg);
}
public void log(String message, Throwable t) {
this.getServletContext().log(this.getServletName() + ":" + message, t);
}

GenericServlet 提供了 2 个 log 方法,一个记录日志,一个记录异常,具体实现是通过传给 ServletContext 的日志实现的。

HttpServlet

HttpServletHTTP 协议实现的 Servlet 的基类,HttpServlet 主要重写了 service 方法,首先将 ServletRequestServletResponse 转换为了 HttpServletRequestHttpServletResponse, 然后根据 http 请求的类型不同将请求路由到了不同的处理方法。

1
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
41
42
43
44
45
46
47
48
49
50
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try {
request = (HttpServletRequest)req;
response = (HttpServletResponse)res;
} catch (ClassCastException var6) {
throw new ServletException("non-HTTP request or response");
}
this.service(request, response);
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
long lastModified;
if (method.equals("GET")) {
lastModified = this.getLastModified(req);
if (lastModified == -1L) {
this.doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified) {
this.maybeSetLastModified(resp, lastModified);
this.doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")) {
lastModified = this.getLastModified(req);
this.maybeSetLastModified(resp, lastModified);
this.doHead(req, resp);
} else if (method.equals("POST")) {
this.doPost(req, resp);
} else if (method.equals("PUT")) {
this.doPut(req, resp);
} else if (method.equals("DELETE")) {
this.doDelete(req, resp);
} else if (method.equals("OPTIONS")) {
this.doOptions(req, resp);
} else if (method.equals("TRACE")) {
this.doTrace(req, resp);
} else {
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[]{method};
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(501, errMsg);
}
}

Tomcat 分析

Tomcat 的顶层结构

Tomcat

Tomcat

Tomcat 的启动过程

Bootstrap 的启动过程

Catalina 的启动过程

Server 的启动过程

Service 的启动过程

Tomcat 生命周期

Lifecyle 接口

LifecycleBase

Container 分析

Pipeline-Value 管道

Connector 分析