概念
计算机网络定义
利用通信线路将地理上分散的、具有独立功能的计算机系统和通信设备按不同的形式连接起来,以功能完善的网络软件及协议实现资源共享和信息传递的系统。
计算机网络分类
按覆盖范围分:
- 局域网
作用范围一般为几米到几十公里。
- 城域网
界于WAN与LAN之间。
- 广域网
作用范围一般为几十到几千公里。
按拓扑结构分:
- 总线型
- 环型
- 星型
- 网状
按信息的交换方式分:
- 电路交换
- 报文交换
- 报文分组交换
计算机网络体系结构
OSI 七层模型
开放系统互连参考模型(Open System Interconnect 简称OSI)。
一共分为七层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层。
图示:
TCP/IP 模型
Transmission Control Protocol/Internet Protocol的简写,中译名为传输控制协议/因特网互联协议。
OSI模型比较复杂且学术化,所以我们实际使用的TCP/IP模型。
TCP/IP是Internet最基本的协议,Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。协议采用了5层的层级结构。然而在很多情况下,它是利用IP进行通信时所必须用到的协议群的统称。也就是说,它其实是个协议家族,由很多个协议组成,并且是在不同的层,是互联网的基础通信架构。
一共分为五层:物理层、数据链路层(也有TCP/IP模型将物理层、数据链路层合称为网络接口层,与之对应的,协议就被称为TCP/IP四层协议模型)、网络层、传输层、应用层。
两个模型之间的对应关系如图所示:
无论什么模型,每一个抽象层建立在低一层提供的服务上,并且为高一层提供服务。
- 应用层
HTTP是应用层,主要负责如何组装传输的数据。
- 传输层
TCP/IP是传输层,主要负责数据的传输。
Socket并不是协议,只是一个接口而已,是对TCP/IP的封装,通过调用Socket我们才能使用TCP/IP。
- 网络层
协议,ip、蓝牙、无线网。
网络层有个ICMP协议,一般云服务器默认开放该协议,用于ping命令探测网络。
- 数据链路层
网卡,MAC 地址。
- 物理层
光纤、绞线。
MAC/IP/端口号
(1)MAC地址
Media Access Control,媒体访问控制地址。是局域网地址(LAN Address)、以太地址(Ethernet Address)或物理地址(Physical Address),由网络设备制造商生产时写在硬件内部。MAC地址与网络无关。每一个网卡对应一个MAC地址。
MAC地址共48位(6个字节)。前24位由IEEE(电气和电子工程师协会)决定如何分配,后24位由实际生产该网络设备的厂商自行制定。例如:FF:FF:FF:FF:FF:FF
或FF-FF-FF-FF-FF-FF
。
图示:
(2)IP地址
IP地址(Internet Protocol Address)的全称叫作互联网协议地址,它的本义是为互联网上的每一个网络和每一台主机配置一个唯一的逻辑地址,用来与物理地址作区分。
所以IP地址用来识别TCP/IP网络中互连的主机和路由器。IP地址基于逻辑,比较灵活,不受硬件限制,也容易记忆。
IP地址分为:IPv4和IPv6。我们这里着重讲的是IPv4地址,IP地址是由32位的二进制数组成,它们通常被分为4个“8位二进制数”,我们可以把它理解为4个字节,格式表示为:A.B.C.D
。其中,A、B、C、D这四个英文字母表示为0-255的十进制的整数。例:192.168.1.1。
IP地址和MAC地址之间的区别?
- 对于网络中的一些设备,路由器或者是PC及而言,IP地址的设计是出于拓扑设计出来的,只要在不重复IP地址的情况下,它是可以随意更改的;而MAC地址是根据生产厂商烧录好的,它一般不能改动的,一般来说,当一台PC机的网卡坏了之后,更换了网卡之后MAC地址就会变了。
- 在前面的介绍里面,它们最明显的区别就是长度不同,IP地址的长度为32位,而MAC地址为48位。
- 它们的寻址协议层不同。IP地址应用于OSI模型的网络层,而MAC地址应用在OSI模型的数据链路层。 数据链路层协议可以使数据从一个节点传递到相同链路的另一个节点上(通过MAC地址),而网络层协议使数据可以从一个网络传递到另一个网络上(ARP根据目的IP地址,找到中间节点的MAC地址,通过中间节点传送,从而最终到达目的网络)。
- 分配依据不同。IP地址的分配是基于我们自身定义的网络拓扑,MAC地址的分配是基于制造商。
(3)端口号
在传输层也有这种类似于地址的概念,那就是端口号。端口号用来识别同一台计算机中进行通信的不同应用程序。因此,它也被称为程序地址。
一台计算机上同时可以运行多个程序。传输层协议正是利用这些端口号识别本机中正在进行通信的应用程序,并准确地将数据传输。
图示:
端口号为什么只有65535个?
因为在TCP、UDP协议报文的开头,会分别有16位二进制来存储源端口号和目标端口号,所以端口个数是 2^16=65536
个,但是0号端口用来表示所有端口,所以实际可用的端口号是65535个。
本机应用如何确定端口号?
- 标准既定的端口号
这种方法也叫静态方法。它是指每个应用程序都有其指定的端口号。但并不是说可以随意使用任何一个端口号。例如HTTP、FTP、TELNET等广为使用的应用协议中所使用的端口号就是固定的。这些端口号被称为知名端口号,分布在0~1023之间,我们在编写自己的网络应用服务时,尽量不要使用这些端口号。
- 时序分配法
服务器有必要确定监听端口号,以让客户端程序访问服务器上的服务。但是客户端没必要确定端口号。在这种方法下,客户端应用程序完全可以不用自己设置端口号,而全权交给操作系统进行分配,客户端使用的临时端口号,操作系统分配的一般都是大于10000的。
(4)总结
ip区分电脑,端口区分软件,URL定义资源;
一个物理网卡对应一个MAC地址,MAC地址不会变。每个网卡正常工作情况下都有对应的IP地址,IP地址是可变的。该计算机中的应用程序都有自己对应的端口,看口不能重复。
操作系统是通过源IP地址、目标IP地址、协议号(协议类型)、源端口号以及目标端口号这五个元素唯一性的识别一个网络上的通信。
网关和子网掩码
(1)网关
多台计算机之间需要通讯,就需要路由器,路由器连接我们电脑上的端口ip即为网关。
也就是说连接同一个路由器上的所有机器的网关都是一样的。
(2)子网掩码
子网掩码实际上是配合IP地址起到划分不同网络的目的。
两台计算机之间需要通讯,必须满足两个计算机的ip处在同一个网段,怎样判断两个ip处于同一个网段呢?将两个ip与自己的子网掩码与计算后比较,如果相同则为同一网段,允许通讯。
短连接和长连接
(1)短连接
- 说明
连接》传输数据》关闭连接。
应用比较广泛,Client和Server完成一次读写操作后就随即关闭连接。
- 优点
保证存在的连接都是可用的连接;
- 缺点
每一次连接都会消耗系统资源,短连接不停地连接会考验系统的性能。
(2)长连接
- 说明
连接》传输数据》保持链接》传输数据》。。。》关闭连接。
Client和Server建立一次连接后不会主动去关闭连接,该连接可以复用,Server端会定时向Client发送探测报文,满足条件会关闭该连接。
- 优点
一个连接可以处理多个交易,减少了建立连接(比如TCP三次握手)时消耗掉的系统性能;
- 缺点
可能存在大量的僵尸连接。
(3)总结
长连接多用于操作频繁,点对点通讯,例如数据库的连接用长连接。
Web服务Http规范用的是短连接,Web端可能有成千上万甚至上亿的连接,长连接耗费服务端资源。Http1.1,尤其是Http2、Http3已经开始向长连接演化。
拆包和黏包
(1)产生原因
tcp以流动的方式传递数据,传输的最小单位为一个报文段(segment);
连接层每次传输的数据有最大限制,一般为1500bit,超过这个限制就要拆分成多个segment;
tcp有报文头,最大限制减去报文头大小,最后的报文数据(MSS)大概在1460bit,180多个字节;
为了提供性能,tcp的发送端和接收端都有缓冲区,如实就会出现以下几种情况:
1、应用程序写入的数据大于MSS,发生拆包;
2、应用程序写入的数据小于MSS,发生黏包;
3、接收端不及时读取套接字缓冲区,发生黏包;
(2)解决办法
设置定长消息,服务端每次读取既定长度内容作为一条完整消息;
一条完整消息前后通过特定分隔符分隔,例如{“type”:“message”,“content”:“hello”}/n;
用带消息头的协议,消息头存储消息开始标识及消息长度信息,服务端获取消息头解析消息长度,然后向后读取消息内容;
比如,http协议的header中cotent-length,标识消息体大小;
URL/URI
- URI
- URL
统一资源定位符,可以解析协议、域名、端口、资源、参数、锚点,获取网络资源流;
直接内存
(1)堆外内存的优点和缺点
堆外内存相比于堆内内存有几个优势:
- 减少了垃圾回收的工作,因为垃圾回收会暂停其他的工作(可能使用多线程或者时间片的方式,根本感觉不到)。
2. 加快了复制的速度。因为堆内在flush到远程时,会先复制到直接内存(非堆内存),然后再发送;而堆外内存相当于省略掉了这个工作。
缺点:
- 堆外内存难以控制,如果内存泄漏,那么很难排查;
- 堆外内存相对来说,不适合存储很复杂的对象。一般简单的对象或者扁平化的比较适合;
- 堆外内存申请空间耗费更高的性能,当频繁申请到一定量时尤为明显;
(2)用处
TCP 的 socket 就用了直接内存。
JVM 的元空间;
(3)使用
# 分配JVM堆内存
java.nio.ByteBuffer#allocate
# 分配计算机内存直接内存
java.nio.ByteBuffer#allocateDirect
零拷贝
零拷贝介绍
(1)概念
零拷贝(英语: Zero-copy)技术是指计算机执行操作时,CPU 不需要先将数据从某处内存复制到另一个特定区域。这种技术通常用于通过网络传输文件时节省 CPU 周期和内存带宽。
优点:
- 零拷贝技术可以减少数据拷贝和共享总线操作的次数,消除传输数据在存储器之间不必要的中间拷贝次数,从而有效地提高数据传输效率;
- 零拷贝技术减少了用户进程地址空间和内核地址空间之间因为上下文切换而带来的开销;
(2)实现方案
实现方案有三种:mmap、sendfile、slice,比如 Kafka 通过 sendfile 实现的,RocketMQ 是通过 mmap 实现的;
减少拷贝次数:
正常是四次拷贝,零拷贝是减少了用户空间与内核空间的数据拷贝,只有 2 次。
正常时,磁盘》内核空间》用户空间(应用程序)》SocketBuffer(用户空间)》网卡(内核空间);
零拷贝时,磁盘》内核空间》网卡(内核空间);
直接内存与堆内存:
直接内存对应 ByteBuf 中没有数组数据,只有 MermoryAddress 对应堆外内存地址,而 JVM 内存对应的 ByteBuf 中有数组数据;
(3)使用场景
NIO、Kafka、Netty、RocketMQ、Nginx、Apache 等。
DMA
在早期计算机中,用户进程需要读取磁盘数据,需要 CPU 中断和 CPU 参与,因此效率比较低,发起 IO 请求,每次的 IO 中断,都带来 CPU 的上下文切换。因此出现了 DMA。
DMA(Direct Memory Access,直接内存存取)是所有现代电脑的重要特色,它允许不同速度的硬件装置来沟通,而不需要依赖于 CPU 的大量中断负载。
DMA 控制器,接管了数据读写请求,减少 CPU 的负担。这样一来,CPU 能高效工作了。现代硬盘基本都支持 DMA。
因此 IO 读取,涉及两个过程:
1、DMA 等待数据准备好,把磁盘数据读取到操作系统内核缓冲区;
2、用户进程,将内核缓冲区的数据 copy 到用户空间。
TCP
TCP/UDP
- TCP
TCP提供了一种可靠的数据传输服务,TCP是面向连接的。也就是说,利用TCP通信的两台主机首先要经历一个建立连接的过程,等到连接建立后才开始传输数据,而且传输过程中采用“带重传的肯定确认”技术来实现传输的可靠性。TCP还采用一种称为“滑动窗口”的方式进行流量控制,发送完成后还会关闭连接。
- UDP
用户数据报协议(User Datagram Protocol)。
UDP是把数据直接发出去,而不管对方是不是在接收,也不管对方是否能接收的了,也不需要接收方确认,属于不可靠的传输,可能会出现丢包现象,实际应用中要求程序员编程验证。
基于UDP的协议还有UDT和QUID,这里不做详细介绍。
UDP单播和广播:
单播的传输模式,定义为发送消息给一个由唯一的地址所标识的单一的网络目的地。面向连接的协议和无连接协议都支持这种模式。
由于通讯不需要连接,所以可以实现广播发送,所谓广播——传输到网络(或者子网)上的所有主机。
UDP因为没有TCP等一系列复杂机制,所以使用也非常广泛:
- 使用UDP的服务包括NTP(网络时间协议)和DNS(DNS也使用TCP),包总量较少的通信(DNS、SNMP等);
- 视频、音频等多媒体通信(即时通信);
- 限定于LAN等特定网络中的应用通信;
- DHCP等协议就利用了UDP的广播功能;
常用的QQ,就是一个以UDP为主,TCP为辅的通讯协议。
总结:
- TCP比UDP可靠得多。
- 常见的网络应用基本上都是基于TCP和UDP的,这两个协议又会使用网络层的IP协议。但是我们完全可以绕过传输层的TCP和UDP,直接使用IP,比如Linux内核中的LVS就可以直接基于IP层进行负载平衡调度。甚至还可以直接访问链路层,比如tcpdump程序就是直接和链路层进行通信的。
TCP 特性
- 面向连接
TCP(Transmission Control Protocol)是面向连接的通信协议,通过三次握手建立连接,然后才能开始数据的读写,通讯完成时要拆除连接,由于TCP是面向连接的所以只能用于端到端的通讯。
- 可靠传输
TCP提供的是一种可靠的数据流服务,数据有可能被拆分后发送,那么采用超时重传机制和应答确认机制是组成TCP可靠传输的关键设计。
面向连接的服务(例如Telnet、FTP、rlogin、X Windows和SMTP)需要高度的可靠性,所以它们使用了TCP。DNS在某些情况下使用TCP(发送和接收域名数据库),但使用UDP传送有关单个主机的信息。
- 超时重传
超时重传机制中最最重要的就是重传超时(RTO,Retransmission TimeOut)的时间选择。如果超时时长为固定值,这种设计是不智能的,所以超时的时长就需要根据网络情况动态调整,根据请求响应时间采样统计计算出重传超时时间,这个时长就是RTT,学名round-trip time
,再根据这个RTT通过各种算法和公式平滑RTT值后,最终确定重传超时值。
- 数据排序
IP层进行数据传输时,是不能保证数据包按照发送的顺序达到目的机器。当IP把它们向‘上’传送到TCP层后,TCP将包排序并进行错误检查。TCP数据包中包括序号和确认,所以未按照顺序收到的包可以被排序,而损坏的包可以被重传。
- 流量控制
TCP还采用一种称为“滑动窗口”的方式进行流量控制,所谓窗口实际表示接收能力,用以限制发送方的发送速度。
- 全双工传输
同时TCP还允许在一个TCP连接上,通信的双方可以同时传输数据,也就是所谓的全双工。
TCP 三次握手
TCP提供面向有连接的通信传输。面向有连接是指在数据通信开始之前先做好两端之间的准备工作。
所谓三次握手是指建立一个TCP连接时需要客户端和服务器端总共发送三个包以确认连接的建立。在Socket编程中,这一过程由客户端执行connect来触发,所以网络通信中,发起连接的一方我们称为客户端,接收连接的一方我们称之为服务端。
图示:
说明:
第一次握手:客户端将请求报文标志位SYN置为1,请求报文的Sequence Number字段(简称seq)中填入一个随机值J,并将该数据包发送给服务器端,客户端进入SYN_SENT状态,等待服务器端确认。
第二次握手:服务器端收到数据包后由请求报文标志位SYN=1知道客户端请求建立连接,服务器端将应答报文标志位SYN和ACK都置为1,应答报文的Acknowledgment Number字段(简称ack)中填入ack=J+1,应答报文的seq中填入一个随机值K,并将该数据包发送给客户端以确认连接请求,服务器端进入SYN_RCVD状态。
第三次握手:客户端收到应答报文后,检查ack是否为J+1,ACK是否为1,如果正确则将第三个报文标志位ACK置为1,ack=K+1,并将该数据包发送给服务器端,服务器端检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,客户端和服务器端进入ESTABLISHED状态,完成三次握手,随后客户端与服务器端之间可以开始传输数据了。
为什么TCP握手需要三次?
TCP是可靠的传输控制协议,而三次握手是保证数据可靠传输又能提高传输效率的最小次数。为什么?RFC793,也就是TCP的协议RFC中就谈到了原因,这是因为:
为了实现可靠数据传输,TCP协议的通信双方,都必须维护一个序列号,以标识发送出去的数据包中,哪些是已经被对方收到的。通过该序列化好进行数据重传。
举例说明:
发送方在发送数据包(假设大小为10byte)时,同时送上一个序号(假设为500),那么接收方收到这个数据包以后,就可以回复一个确认号(510=500+10)告诉发送方 “我已经收到了你的数据包,你可以发送下一个数据包,序号从511开始”。
三次握手的过程即是通信双方相互告知序列号起始值,并确认对方已经收到了序列号起始值的必经步骤。
如果只是两次握手,至多只有连接发起方的起始序列号能被确认,另一方选择的序列号则得不到确认。
至于为什么不是四次,很明显,三次握手后,通信的双方都已经知道了对方序列号起始值,也确认了对方知道自己序列号起始值,第四次握手已经毫无必要了。
TCP三次握手的漏洞-SYN洪泛攻击:
在TCP三次握手中有一个缺陷,被称为SYN洪泛攻击。三次握手中有一个第二次握手,服务端向客户端应答请求,应答请求是需要客户端IP的,而且因为握手过程没有完成,操作系统使用队列维持这个状态(Linux2.2以后,这个队列大小参数可以通过/proc/sys/net/ipv4/tcp_max_syn_backlog
设置)。于是攻击者就伪造这个IP,往服务器端狂发第一次握手的内容,当然第一次握手中的客户端IP地址是伪造的,从而服务端忙于进行第二次握手,但是第二次握手是不会有应答的,所以导致服务器队列满,而拒绝连接。
面对这种攻击,有以下的解决方案,最好的方案是防火墙。
- 无效连接监视释放:
这种方法不停监视所有的连接,包括三次握手的,还有握手一次的,反正是所有的,当达到一定(与)阈值时拆除这些连接,从而释放系统资源。这种方法对于所有的连接一视同仁,不管是正常的还是攻击的,所以这种方式不推荐。
- 延缓TCB分配方法
一般做完第一次握手之后,服务器就需要为该请求分配一个TCB(连接控制资源),通常这个资源需要200多个字节。延迟TCB的分配,当正常连接建立起来后再分配TCB则可以有效地减轻服务器资源的消耗。
- 使用防火墙
防火墙在确认了连接的有效性后,才向内部的服务器(Listener)发起SYN请求。
TCP 四次挥手
四次挥手即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在Socket编程中,这一过程由客户端或服务端任一方执行close来触发。
由于TCP连接是全双工的,因此,每个方向都必须要单独进行关闭。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。
图示:
说明:
第一次握手:某个应用进程首先调用close,我们称该端执行主动关闭(active close)。该端的TCP于是发送一个FIN分节,表示数据发送完毕,应用进程进入FIN-WAIT-1(终止等待1)状态。
第二次握手:接收到这个FIN的对端执行被动关闭(passive close),发出确认报文。因为FIN的接收意味着接收端应用进程在相应连接上再无额外数据可接收,接收端进入了CLOSE-WAIT(关闭等待)状态,这时候处于半关闭状态,即主动关闭端已经没有数据要发送了,但是被动关闭端若发送数据,主动关闭端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。主动关闭端收到确认报文后进入FIN-WAIT-2(终止等待2)状态。
第三次握手:一段时间后,被动关闭的应用进程将调用close关闭它的套接字。这导致它的TCP也发送一个FIN,表示它也没数据需要发送了。
第四次握手:接收这个最终FIN的原发送端TCP(即执行主动关闭的那一端)确认这个FIN发出一个确认ACK报文,并进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗MSL
(最长报文段寿命/最长分节生命期 max segement lifetime
,MSL是任何IP数据报能够在因特网中存活的最长时间,任何TCP实现都必须为MSL选择一个值。RFC 1122[Braden 1989]的建议值是2分钟,不过源自Berkelcy的实现传统上改用30秒这个值。这意味着TIME_WAIT状态的持续时间在1分钟到4分钟之间)的时间后,当主动关闭端撤销相应的TCB后,才进入CLOSED状态。
被动关闭端只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCP后,就结束了这次的TCP连接。可以看到,被动关闭端结束TCP连接的时间要比主动关闭端早一些。
既然每个方向都需要一个FIN和一个ACK,因此通常需要4个分节。我们使用限定词“通常”是因为:某些情形下步骤1的FIN随数据一起发送。另外,步骤2和步骤3发送的分节都出自执行被动关闭那一端,有可能被合并成一个分节。
为什么TCP的挥手需要四次?
TCP是全双工的连接,必须两端同时关闭连接,连接才算真正关闭。
如果一方已经准备关闭写,但是它还可以读另一方发送的数据。发送给FIN结束报文给对方,对方收到后,回复ACK报文。当这方也已经写完了准备关闭,发送FIN报文,对方回复ACK。两端都关闭,TCP连接正常关闭。
为什么需要TIME-WAIT状态?
TIME_WAIT状态存在的原因有两点:
- 可靠的终止TCP连接。
- 保证让迟来的TCP报文有足够的时间被识别并丢弃。
根据前面的四次握手的描述,我们知道,客户端收到服务器的连接释放的FIN报文后,必须发出确认。如最后这个ACK确认报文丢失,那么服务器没有收到这个ACK确认报文,就要重发FIN连接释放报文,客户端要在某个状态等待这个FIN连接释放报文段然后回复确认报文段,这样才能可靠的终止TCP连接。
在Linux系统上,一个TCP端口不能被同时打开多次,当一个TCP连接处于TIME_WAIT状态时,我们无法使用该链接的端口来建立一个新连接。反过来思考,如果不存在TIME_WAIT状态,则应用程序能过立即建立一个和刚关闭的连接相似的连接(这里的相似,是指他们具有相同的IP地址和端口号)。这个新的、和原来相似的连接被称为原来连接的化身。新的化身可能受到属于原来连接携带应用程序数据的TCP报文段(迟到的报文段),这显然是不该发生的。这是TIME_WAIT状态存在的第二个原因。
协议
Http
(1)状态码分类
(2)常用状态码
200,请求成功;
301,请求的资源已被永久移动到新的URL;
302,临时移动,重定向;
400,请求语法错误,服务端无法理解;
401,请求需要客户端的身份认证;
403,服务器拒绝;
404,服务器资源不存在;
500,服务器内部错误;
502,作为网关或代理服务器尝试执行请求时,从目标服务器接受了无效的响应;
504,网关请求目标服务器超时;
http 请求体格式:
请求行
请求 header
请求体
Https
(1)概述
Hyper Text Transfer Protocol Secure,超文本传输协议安全;
在http基础上加了ssl层,对传输报文进行了加解密处理;
需要第三方CA认证;
AES加密公钥,RSA加密报文;
(2)原理
涉及到的秘钥:
服务端的公钥和私钥,用来进行非对称加解密;
客户端生成的随机数,用来进行对称加解密;
(3)请求流程
涉及到两次http请求,第一次获取服务端的公钥,第二次发送请求报文;
1、客户端向服务端发起https请求,连接到服务端端口443;
2、服务端将公钥发送给客户端;
3、客户端收到公钥后,进行合法性校验,如果公钥合法,客户端生成一个随机值,通过服务端公钥对该随机值加密,得到对称加密秘钥,用于加密请求报文;
4、客户端通过随机值加密请求报文,并将加密报文和加密后的随机值发送到服务端;
5、服务端收到客户端请求后,通过私钥解密客户端对称秘钥,得到客户端随机值并解密请求报文;
6、服务端通过客户端随机值加密响应报文,返回给客户端;
7、客户端通过本地随机值解密响应报文;
(4)流程图
Socket
(1)什么是Socket
Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口,一般由操作系统提供。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议处理和通信缓存管理等都隐藏在Socket接口后面,对用户来说,使用一组简单的接口就能进行网络应用编程,让Socket去组织数据,以符合指定的协议。主机A的应用程序要能和主机B的应用程序通信,必须通过Socket建立连接。比如封装 TCP 的三次握手和四次挥手。
客户端连接上一个服务端,就会在客户端中产生一个socket接口实例,服务端每接受一个客户端连接,就会产生一个socket接口实例和客户端的socket进行通信,有多个客户端连接自然就有多个socket接口实例。
图示:
(2)api说明
类名有Server或ServerSocket的类,都是为Socket服务端提供的类。类名只包含Socket的类,表示负责网络读写的类。
ServerSocket接收客户端连接(IP和端口),Socket具体与客户端沟通。
- socket.setKeepAlive(true)
keepalive不是说TCP的长连接,当我们作为服务端,一个客户端连接上来,如果设置了keeplive为true,当对方没有发送任何数据过来,超过一定时间(看系统内核参数配置),那么我们这边会发送一个ack探测包发到对方,探测双方的TCP/IP连接是否有效(对方可能断点,断网), 在Linux好像这个时间是75秒。如果不设置,那么客户端宕机时,服务器永远也不知道客户端宕机了,仍然保存这个失效的连接。
- InetAddress
InetSocketAddress,包含端口;
通过URL获取HttpURLConnection;
(3)示例
// a、创建ServerSocket,指定监听端口
// b、调用accept()方法开始监听,等待客户端的连接,得到Socket对象
// c、通过输入流接收客户端数据
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String info = null;
while((info=br.readLine())!=null){
}
// d、通过输出流响应客户端请求
// e、关闭相应资源
// 客户端:
Socket socket = new Socket("localhost",8080);
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os);
pw.write("XXX");
pw.flush();
// 关闭资源
// 用多线程来实现第一个客户端的连接:
ServerThread extends Thread{
// 构造方法接收socket
run(){
// 接收客户端输入,输出到客户端
}
}
// 在Server类中,
while(true){
socket.accept();
// 设置线程的优先级
serverThread.start();
}
IO 模型
BIO(blocking-io)
(1)说明
阻塞+同步通讯模式,一个客户端连接对应一个处理线程,一个请求一个应答,比如io流的读写、MySQL。
阻塞主要体现在两个地方:
- 若一个服务器启动就绪,那么主线程就一直在等待着客户端的连接,这个等待过程中主线程就一直在阻塞。
- 在连接建立之后,在读取到socket信息之前,线程也是一直在等待,一直处于阻塞的状态下的。
(2)缺点
缺乏弹性伸缩的能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系。
Java中的线程资源很宝贵,虽然可以用线程池来优化线程的使用效率,实现1个或多个线程处理N个客户端的模型(但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“。
NIO(no-blocking-io)
NIO 介绍
(1)说明
非阻塞+同步通讯模式,一个线程可以处理多个请求(连接),即多路复用,通过 linux 支持的 epoll 来实现多路复用。
弥补了原来BIO的不足,jdk1.4 引入的。
(2)原理
启动服务端时,会将socket注册到selector上并生成一个连接channel,下次客户端写数据到服务端生成服务端的读channel,selector通过accept监听服务端的事件,selector通过轮循channel分别处理连接事件和读事件。
NIO三大核心组件:
- Selector
Selector的英文含义是“选择器”,也可以称为为“轮询代理器”、“事件订阅器”、“channel容器管理机”。
一个单独的线程通过 Selector 来监视多个输入通道。应用程序向Selector对象注册需要它关注的Channel,以及具体的某一个Channel会对哪些IO事件感兴趣。Selector中也会维护一个“已经注册的Channel”的容器。
SelectionKey 将 Channel 与 Selector 建立关系,并维护了 channel 事件。SelectionKey 中有四种事件类型:OP_READ、OP_WRITE、OP_CONNECT、OP_ACCEPT;
- Channels
通道,被建立的一个应用程序和操作系统交互事件、传递内容的渠道。应用程序通过通道读取数据、写入数据,可以同时进行读写。
channel类似于流,每个channel对应一个buffer缓冲区,buffer底层就是个数组;
channel会注册到selector上,由selector根据channel读写事件的发生将其交由某个空闲的线程处理;
- Buffer
Buffer是个缓冲,用于和NIO通道进行交互,数据是从通道读入缓冲区,从缓冲区写入到通道中的。
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存(其实就是数组)。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
(3)适用场景
NIO适用于连接数目多且比较短(轻操作)的架构,比如聊天服务器、弹幕系统、服务器通讯、编程比较复杂;
(4)BIO与NIO的对比
- BIO是面向流的,NIO是面向缓冲区的
BIO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。不能前后移动流中的数据。NIO面向缓冲区,数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动,增加了处理过程中的灵活性。
- BIO阻塞与NIO非阻塞
BIO阻塞,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。
NIO非阻塞,线程通过通道发送/接收请求数据时,如果目前没有可用数据,线程不会阻塞,直至数据变的可以读取之前,该线程可以继续做其他的事情。线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程可以管理多个输入和输出通道(channel)。
Reactor
Reactor即“反应”器,名字中”反应“的由来:
“反应”即“倒置”,“控制逆转”,具体事件处理程序不调用反应器,而向反应器注册一个事件处理器,表示自己对某些事件感兴趣,有事件来了,具体事件处理程序通过事件处理器对某个指定的事件发生做出反应。不要调用我,让我来调用你。
单线程 Reactor 模式:单线程完成客户端连接、读事件、写事件等操作,Redis5.0 及之前为这种模式。
单线程 Reactor,工作者线程池:将非 IO 操作交由工作者线程池来处理,提高 IO 处理性能;
多线程主从 Reactor 模式:mainReactor 由一个线程专门负责连接,另外的多个 subReactor 由线程执行读事件和写事件。
IO 多路复用
io多路复用是相对于阻塞io来说的,阻塞时io当处理一个请求时只能阻塞式等待返回;
io多路复用,一个线程记录io状态同时管理多个io,在处理请求的同时也可以处理响应或者多个请求;
一个io进来时,将io注册到selector,selector轮训io的key集合,通过key找到对应的channel,通过channel绑定的buffer进行读写;
多路复用几种实现:
io多路复用,一般用linux底层的api(select、poll、epoll)来实现,区别如下:
select | poll | epoll(jdk1.5以上) | |
---|---|---|---|
操作方式 | 遍历 | 遍历 | 回调 |
底层实现 | 数组 | 链表 | hash表 |
IO效率 | 每次调用都进行线性遍历,时间复杂度O(n) | 每次调用都进行线性遍历,时间复杂度O(n) | 事件通知方式,每次有IO事件就绪,系统注册的回调函数就会被调用,时间复杂度O(1) |
最大连接 | 有上限 | 无上限 | 无上限 |
AIO(asynchronous-io)
异步+非阻塞,通过回调处理channel事件,不常用;
与NIO不同在于不需要多路复用选择器,而是由操作系统回调通知处理请求的线程执行后续操作;
适用于连接数较多,且连接时间较长的场景;
jdk1.7开始支持;
理解 RPC
(1)什么是 RPC
RPC(Remote Procedure Call,远程过程调用),它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络的技术。
一次完整的 RPC 同步调用流程:
- 服务消费方(client)以本地调用方式调用客户端存根;
- 什么叫客户端存根?就是远程方法在本地的模拟对象,一样的也有方法名,也有方法参数,client stub 接收到调用后负责将方法名、方法的参数等包装,并将包装后的信息通过网络发送到服务端;
- 服务端收到消息后,交给代理存根在服务器的部分后进行解码为实际的方法名和参数
- server stub 根据解码结果调用服务器上本地的实际服务;
- 本地服务执行并将结果返回给 server stub;
- server stub 将返回结果打包成消息并发送至消费方;
- client stub 接收到消息,并进行解码;
- 服务消费方得到最终结果;
RPC 框架的目标就是要中间步骤都封装起来,让我们进行远程方法调用的时候感觉到就像在本地方法调用一样。
(2)RPC 和 HTTP
rpc 字面意思就是远程过程调用,只是对不同应用间相互调用的一种描述,一种思想。
具体怎么调用?实现方式可以是最直接的 tcp 通信,也可以是 http 方式,在很多的消息中间件的技术书籍里,甚至还有使用消息中间件来实现 RPC 调用的,我们知道的 dubbo 是基于 tcp 通信的,gRPC 是 Google 公布的开源软件,基于最新的 HTTP2.0 协议,底层使用到了 Netty 框架的支持。
所以总结来说,rpc 和 http 是完全两个不同层级的东西,他们之间并没有什么可比性。
(3)实现RPC框架
- 代理
被调用的服务本质上是远程的服务,但是调用者不知道也不关心,调用者只要结果,具体的事情由代理的那个对象来负责这件事。通过代理模式来实现远程服务调用与增强。
代理分为静态代理和动态代理,静态代理指在编译期间生成代理对象,动态代理指在运行期间生成代理对象,比如 jdk 和 cglib 都属于动态代理。
- 序列化
序列化分为序列化和反序列化。计算机指能识别和存储二进制,所以在网络请求时需要将字符串或者对象转换成二进制,在网络响应时需要将二进制 01 转换成对应程序能识别的字符串或者对象。
java 提供了序列化机制 Serializable。
- 通讯
通讯是指将二进制 01 串在网络中进行传输,比如 BIO;
- 服务实例化
将服务接口名转换成对应的实例,通过反射机制可以实现对象的实例化;
反射机制是什么?
反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为 java 语言的反射机制。
反射机制作用?
- 在运行时判断任意一个对象所属的类;
- 在运行时构造任意一个类的对象;
- 在运行时判断任意一个类所具有的成员变量和方法;
- 在运行时调用任意一个对象的方法;
- 生成动态代理;
序列化
(1)说明
java序列化将Object对象序列化为byte数组进行传输,也可以将获取的byte数组反序列化为Object对象,仅限于java应用之间的传输;
(2)json序列化
Json序列表是跨语言的,比java序列化更通用;
Java序列化执行效率明显低于Json序列化;
HTTP 客户端
okhttp、RestTemplate。