赞
踩
主要协议分为:
TCP/IP协议模型(Transmission Control Protocol/Internet Protocol),包含了一系列构成互联网基础的网络协议,是Internet的核心协议。
TCP/IP是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成。
TCP/IP定义了电子设备如何连入因特网,以及数据如何在它们之间传输的标准。协议采用了4层的层级结构,每一层都呼叫它的下一层所提供的协议来完成自己的需求。
通俗而言:TCP负责发现传输的问题,一有问题就发出信号,要求重新传输,直到所有数据安全正确地传输到目的地。而IP是给因特网的每一台联网设备规定一个地址。
基于TCP/IP的参考模型将协议分成四个层次,它们分别是链路层、网络层、传输层和应用层。
下图表示TCP/IP模型与OSI模型各层的对照关系。
TCP/IP协议族按照层次由上到下,层层包装。
TCP/IP模型是互联网的基础,它是一系列网络协议的总称。这些协议可以划分为四层,分别为链路层、网络层、传输层和应用层。
上图清楚地表示了TCP/IP协议中每个层的作用,而TCP/IP协议通信的过程其实就对应着数据入栈与出栈的过程。
入栈的过程,数据发送方每层不断地封装首部与尾部,添加一些传输的信息,确保能传输到目的地。
出栈的过程,数据接收方每层不断地拆除首部与尾部,得到最终传输的数据。
IP 用于计算机之间的通信。
IP 是无连接的通信协议。它不会占用两个正在通信的计算机之间的通信线路。这样,IP 就降低了对网络线路的需求。每条线可以同时满足许多不同的计算机之间的通信需要。
通过 IP,消息(或者其他数据)被分割为小的独立的包,并通过因特网在计算机之间传送。
IP 负责将每个包路由至它的目的地。
TCP/IP 意味着 TCP 和 IP 在一起协同工作。
TCP 负责应用软件(比如你的浏览器)和网络软件之间的通信。
IP 负责计算机之间的通信。
TCP 负责将数据分割并装入 IP 包,然后在它们到达的时候重新组合它们。
IP 负责将包发送至接受者。
看TCP的英文全称就知道,其主要作用就是传输 、控制,传输的是数据,控制的是在传输过程中丢包后的重发 、分包乱序后的有序重组 、控制数据传输的速率防止网络拥塞等
这也是我们口中一直说的TCP是一种可靠的传输协议的原因。
TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议。
服务端流程:
客户端流程:
TCP不是拿到一整个包就直接原封不动地传给接收端的,因为若这样做,即使是发生了数据丢失,也不知道到底丢失了哪部分的数据,因此其采用的就是将数据分段发送的方式
这里先说明一点,不光建立和断开连接时接收端需要向发送端发送请求应答,在数据交互时也是需要的
例如有一个数据包,我们可以将其按顺序给每一个字节都标上一个序号,然后我们假设每次发送1000个序号区间的数据给接收端,所以第一次发送的是 序号 1 ~ 1000 的数据,接收端接收到了以后会返回给发送端一个请求应答,告知发送端下一次请发送 序号 1001 ~ 2000 的数据过来,过程如图所示
我们都知道,在数据传输过程中可能会因为各种原因出现丢包现象,而当出现丢包现象时,即发送端在发完数据以后等待一段时间,并未收到接收端的确认应答,则视为丢包,于是就会进行重发
其中丢包现象又分为两种:
以上两种情况如下图所示:
第一种
第二种
那么,发送端发送完数据后多久没收到确认应答才判定数据丢包了呢?这个都是随着网络环境的变化而变化的,TCP会在每次发包时计算往返时间以及偏差来决定等待的时间
若重发后又出现了丢包,则下一次等待的时间会以2倍、4倍的指数函数延长
但其又不会无限进行重发,当重发次数达到一定程度后,会判定为网络异常,两端通信就会被强制关闭
若使用了滑动窗口控制这一技术后,即使某段数据出现了丢包现象,也不会造成太大的影响,因为接收端会一边接收发送端传过来的数据,一边用某种方式告知发送端刚才丢失了哪段数据
接下来我们来介绍一下其作用过程,如图所示
图中,在发送第二段数据(1001 ~ 2000)时发生了丢包,因此接收端没有接收到对应的包,所以当发送端传过来第三段数据的时候,接收端返回的仍是第二段的确认应答,紧接着发送端分别发送了第四段、第五段数据,可接收端都返回的是第二段的确认应答
就这样连着三次发送了同一个确认应答给发送端,所以发送端得知刚才传输数据的过程中第二段数据发生了丢包,因此此时会将丢失的数据重发一份
然后接收端在接收到之前丢掉的那段数据以后,因为之前的数据都成功接收了,所以下一次就开始请求 5001 ~ 6000 这段数据了
有时,发送端发送给接收端的数据超过了接收端的最大承载能力,因此会造成数据无法接收的情况,从而导致之后会进行数据重发,这非常得浪费性能。
为了防止上述情况得发生,TCP提供了一种机制可以使发送端每次发送的数据尽可能得在接收端得承载能力之内,而其实现得方式就是接收端向发送端告知自己能够接收的数据大小,因此发送端每次发送的数据就都不会超过该值,我们称该值为窗口大小
一旦接收端暂时无法接收任何数据,它会告知发送端,因此发送端会暂停数据的发送,但为了后续数据的正常发送,发送端会不时地向接收端发送一个窗口探测,试探性地看一下接收端是否能继续接收数据了
具体的过程如下图所示
1.相同点
(1)现象都是丢包;
(2)实现机制都是让发送方发的慢一点,发的少一点
2.不同点
3.联系
从tcp协议的本质出发,tcp是面向连接的,需要客户端和服务器连接后才能进行数据传输,连接后传输数据才能清楚数据的传输是否是成功的。所以tcp协议是可靠的。那么传输数据之前,我们需要确认客户端和服务器是否已经建立连接。如何评判是否建立连接呢?那么只需要保证双端都可以进行数据的接收和发送即可,也就是客户端和服务器要确定对方具有数据发送和接收的能力。
在三次握手的情况下,服务端收不到第三次握手的ack确认包,不会认为连接成功
序列号seq:32位,用来标记数据段的顺序,TCP把连接中发送的所有数据字节都编上一个序号,第一个字节的编号由本地随机产生;给字节编上序号后,就给每一个报文段指派一个序号;序列号seq就是这个报文段中的第一个字节的数据编号。就是用来标识从TCP源端向目的端发送的字节流,发起方发送数据时对此进行标记。
确认序号ack:32位,期待对方下一个报文段的第一个数据字节的序号,列号表示报文段携带数据的第一个字节的编号;而确认号指的是期望接收到下一个字节的编号;因此当前报文段最后一个字节的编号+1即为确认号。
6个标志位:
说明:ACK、AYN和FIN这些大写的字母表示标志位,其值要么是1,要么是0,;ack,seq小写的表示序号;
第一次握手:建立连接时,客户端将标志位SYN置为1,随机产生一个值seq=x,并将该数据包送给服务端,进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到数据包后由标志位SYN=1知道客户请求建立连接,服务器将标志位都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给客户端以确认连接请求,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并将该数据包发送给服务器,服务器检查ack是否为y+1,如果正确则建立成功,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手,随后客户端和服务器之间可以开始传输数据了。
由于客户对报文段进行了编号,它知道哪些序号是期待的,哪些序号是过时的。当客户发现报文段的序号是一个过时的序号时,就会拒绝该报文段,这样就不会造成重复连接。
TCP四次挥手
数据传输结束后,通信双方都可以释放连接。
一旦四次挥手的 ack包在网络中丢失,服务端将一致停留在最后的确认状态。如果服务端没有收到第四次挥手的ack包,服务端会重新发送FIN包,客户端会重发四次挥手ACK包刷新超时时间, 这个机制也是在不可靠的网络传输中,进行可靠连接。
Client发送一个请求连接的位码SYN和一个随机产生的序列号给Seq
面向字节流
面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。
面向报文
面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。
什么时候应该使用TCP?
当对网络通讯质量有要求的时候,比如:整个数据要准确无误的传递给对方,这往往用于一些要求可靠的应用,比如HTTP、HTTPS、FTP等传输文件的协议,POP、SMTP等邮件传输的协议。
什么时候应该使用UDP?
当对网络通讯质量要求不高的时候,要求网络通讯速度能尽量的快,这时就可以使用UDP。
报文重传是TCP最基本的错误恢复功能,它的目的是防止报文丢失。
报文丢失的可能因素有很多种,包括应用故障,路由设备过载,或暂时的服务宕机。报文级别速度是很高的,而通常报文丢失是暂时的,因此TCP能够发现和恢复报文丢失显得尤为重要。
重传机制在实现数据可靠传输功能的同时,也引起了相应的性能问题:何时进行数据重传?如何保证较高的传输效率?
重传时间过短:在网络因为拥塞引起丢包时,频繁的重传会进一步加剧网络拥塞,引起丢包,恶化网络传输性能。
重传时间过长:接收方长时间无法完成数据接收,引起长时间占用连接线路造成资源损耗、传输效率较低等问题。
针对上述问题,TCP中设计了超时重传机制。该机制规定当发送方A向B发送数据包P1时,开启时长为RTO(Retransmission Timeout)的重传定时器,如果A在RTO内未收到B对P1的确认报文,则认为P1在网络中丢失,此时重新发送P1。由此,引出RTO大小的设定问题。
决定报文是否有必要重传的主要机制是重传计时器(retransmission timer),它的主要功能是维护重传超时(RTO)值。当报文使用TCP传输时,重传计时器启动,收到ACK时计时器停止。报文发送至接收到ACK的时间称为往返时间(RTT)。对若干次时间取平均值,该值用于确定最终RTO值。在最终RTO值确定之前,确定每一次报文传输是否有丢包发生使用重传计时器,
超时重传机制用来保证TCP传输的可靠性。每次发送数据包时,发送的数据报都有seq号,接收端收到数据后,会回复ack进行确认,表示某一seq号数据已经收到。发送方在发送了某个seq包后,等待一段时间,如果没有收到对应的ack回复,就会认为报文丢失,会重传这个数据包。
接受数据一方发现有数据包丢掉了(并不是所期望的值。这意味着报文在传送中丢失。接收端注意到报文乱序,并且在第三个报文中发送重复ACK)。就会发送重复ACK报文告诉发送端重传丢失的报文。
当重传主机从发送端接收到3个重复ACK时,它会假设此报文确实在传送中丢失,并且立即发送一个快速重传。一旦触发了快速重传,所有正在传输的其他报文都被放入队列中,直到快速重传报文发送为止。
什么是流量控制?流量控制的目的?
如果发送者发送数据过快,接收者来不及接收,那么就会有分组丢失。为了避免分组丢失(阻塞掉包),控制发送者的发送速度,使得接收者来得及接收,这就是流量控制。流量控制根本目的是防止分组丢失,它是构成TCP可靠性的一方面。
如何实现流量控制?
tcp给出的方法是,每一个tcp header中专门预制一个字段:接收窗口,表示该tcp数据源的接收能力。因此,接收方可以通过该字段:接收窗口,告诉发送方自己的接收能力。(接收方返回的 ACK 中会包含自己的接收窗口的大小,并且利用大小来控制发送方的数据发送。)
由于每接收到一个数据后,数据如果没有被上层立即,那么下一次接收窗口肯定得变小。如果数据被上层都处理完,下一次接收窗口肯定得变大。所以,窗口会一直变化,即所谓的滑动窗口。通过窗口大小的改变(滑动),发送方就知道自己发送数据时,大概一次可以发多少,而不是盲目地发送。可见滑动窗口,在流控中的作用至关重要,可以说没有滑动窗口,流控无从说起。
流量控制引发的死锁?怎么避免死锁的发生?
当发送者收到了一个窗口为0的应答,发送者便停止发送,等待接收者的下一个应答。但是如果这个窗口不为0的应答在传输过程丢失,发送者一直等待下去,而接收者以为发送者已经收到该应答,等待接收新数据,这样双方就相互等待,从而产生死锁。
为了避免流量控制引发的死锁,TCP使用了持续计时器。每当发送者收到一个零窗口的应答后就启动该计时器。时间一到便主动发送报文询问接收者的窗口大小。若接收者仍然返回零窗口,则重置该计时器继续等待;若窗口不为0,则表示应答报文丢失了,此时重置发送窗口后开始发送,这样就避免了死锁的产生。
2016年底google的开发者Neal Cardwell等5个⼤⽜在acmqueue上发表了论⽂BBR: Congestion-Based Congestion Control,提出了TCP拥塞控制的BBR算法。BBR即Bottleneck Bandwidth and Round-trip propagation time,正如其名,它是⼀种基于瓶颈带宽和往返传播时间的拥塞控制算法。
网络传输过程中,某段时间如果网络中某一资源的需求超过了该资源所能提供的可用部分,网络性能就会变差,这种情况就叫做网络拥塞
为什么需要拥塞控制
考虑一下这样的场景:某一时刻网络上的延时突然增加,那么,TCP对这个事做出的应对只有重传数据,但是,重传会导致网络的负担更重,于是会导致更大的延迟以及更多的丢包,于是,这个情况就会进入恶性循环被不断地放大。试想一下,如果一个网络内有成千上万的TCP连接都这么行事,那么马上就会形成“网络风暴”,TCP这个协议就会拖垮整个网络。
为解决这个问题,TCP中使用了四种拥塞控制算法
滑动窗用来做流量控制。流量控制只关注发送端和接受端自身的状况,而没有考虑整个网络的通信情况。拥塞控制,则是基于整个网络来考虑的。
慢开始的核心思想:指数级由小到大逐渐增加拥塞窗口大小,如果网络出现阻塞,拥塞窗口就减小。
先试探一下网络的负荷,由小到大逐渐增大发送窗口,也就是说,由小到达逐渐增大拥塞窗口值。指数增长。设立慢启动门限 **ssthresh(slow start threshold)**初始值。
发送方会维持一个拥塞窗口cwnd的状态变量,拥塞窗口的大小取决于拥塞程度,并且会在收发包过程中动态的进行变化。发送方会让本端的发送窗口等于拥塞窗口。
判断出现网络拥塞的依据:没有按时收到应当到达的确认报文(即发生重传)。
维护一个慢开始门限ssthresh状态变量:
当cwnd < ssthresh 时,使用慢开始算法。
当cwnd > ssthresh 时,停止使用慢开始算法而改用拥塞避免算法。
当cwnd = ssthresh 时,既可以使用慢开始算法,也可以使用拥塞避免算法。
从指数增长变为线性增长。
拥塞避免算法是让拥塞窗口缓慢增长,每经过一个往返时间RTT就把发送方的拥塞窗口cwnd加1,而不是加倍,拥塞窗口按线性规律缓慢增长。
解决什么问题:
快速重传机制是什么呢?
考虑下面这种情况,在数据传送过程中,网络有可能不太稳定,个别报文段在网络中丢失了,但是实际上网络并没有发生拥塞。这样会导致发送方超时重传,误以为网络上发生了拥塞,由于有慢开始和拥塞避免机制,发送方错误的启动了慢开始算法,并且把拥塞窗口cwnd又设置为最小值1,因为降低了传输效率。
为解决这个问题,快重传要求接收方在收到一个失序的报文段后立即发出重复确认,为的是让发送方知道有一个报文丢失了,快速重传算法规定,发送方只要一连收到三个重复确认就应当立即重传对方还没有接收到的报文段,而不必继续等待设置的重传计时器时间到期。
个别报文丢失,但是网络没有拥塞。此时接收方对上一个报文一直发送确认包,发送了3次重复的确认包,接收方立即重传丢失的报文。
调整门限值为ssthresh/2,然后执行拥塞避免算法。
网络通信程序实际开发中,或者技术面试时,面试官通常会问的比较多的一个问题是:网络通信时,如何解决粘包?
原因:避免重复连接
比如在网络环境比较复杂的情况,客户端可能会连续发送多次请求。如果只设计成两次握手的情况,服务端只能一直接收请求,然后返回请求信息,也不知道客户端是否请求成功。这些过期请求的话就会造成网络连接的混乱。
所以TCP设计成三次握手的目的就是为了避免重复连接。
然后可以设计成四次握手?五次握手?不可以?
答案是也是可以的,不过为了节省资源,三次握手就可以符合实际情况,所以就没必要设计成四次握手、五次握手等等情况
在TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SYN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的半连接队列,使得服务器不能为正常用户服务。
TCP三次握手是建立TCP连接的过程,其中客户端和服务端都需要参与。如果服务端在握手过程中中断,客户端应该采取以下措施:
超时重传:客户端会等待服务端发送确认消息,如果在一定时间内没有收到确认消息,客户端会重新发送连接请求。客户端会不断重试,直到建立连接或达到最大重试次数。
错误处理:客户端可以通过错误处理来处理服务端中断的情况。如果客户端收到服务端的重置消息(RST),则表示连接已被中断,客户端应该关闭连接并报告错误。
超时处理:如果客户端在一定时间内没有收到服务端的响应消息,则可能是服务端已经中断连接。客户端可以关闭连接并报告错误。
总之,客户端需要采取适当的措施来处理服务端中断的情况,以确保能够及时地识别和处理连接中断。
用户通过UDP协议传输时,操作系统不会对消息进行拆分,直接组装头部就交给网络层处理
所以每个UDP报文就是一个用户消息的边界,读一个UDP报文就能读取到完整的用户消息
操作系统在收到UDP报文后,将其插入到队列中,队列中每一个元素都是一个UDP报文,这样用户调用recvfrom()系统调用读取数据的时候,就会从队列拿出数据,从内核里拷贝给用户缓冲区
通过TCP协议传输时,消息可能会被操作系统分组成多个TCP报文
面向字节流和面向报文段的区别在于:用户读数据的时候,是不是有边界。
一般TCP/IP的应用层或者OSI的会话、表示、应用层把数据称为数据或者信息,到了传输层把数据称为报文,到了最底层就是比特流了也就是字节流
字节:字节就是散乱的数据
报文就是添加了标记,封装后的数据
字节流是由字节组成的, 字节流是最基本的,所有的InputStrem和OutputStream的子类都是,主要用在处理二进制数据,它是按字节来处理的
面向报文的传输方式是应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。
因此,应用程序必须选择合适大小的报文。若报文太长,则IP层需要分片,降低效率。若太短,会是IP太小。
UDP对应用层交下来的报文,既不合并,也不拆分,而是保留这些报文的边界。
这也就是说,应用层交给UDP多长的报文,UDP就照样发送,即一次发送一个报文。
面向字节流的话,虽然应用程序和TCP的交互是一次一个数据块(大小不等),但TCP把应用程序看成是一连串的无结构的字节流。TCP有一个缓冲,当应用程序传送的数据块太长,TCP就可以把它划分短一些再传送。如果应用程序一次只发送一个字节,TCP也可以等待积累有足够多的字节后再构成报文段发送出去。
前者是多个http用同一个tcp连接,后者是tcp实现的类似“心跳"的机制
通过上图,我们可以看到TIME_WAIT状态是在tcp断开链接时产生的,因为TCP连接是双向的,所以在关闭连接的时候,两个方向各自都需要关闭。
先发FIN包的一方执行的是主动关闭;后发FIN包的一方执行的是被动关闭。
主动关闭的一方会进入TIME_WAIT状态,并且在此状态停留两倍的MSL时长。
MSL指的是报文段的最大生存时间,如果报文段在网络活动了MSL时间,还没有被接收,那么会被丢弃。关于MSL的大小,RFC 793协议中给出的建议是两分钟,不过实际上不同的操作系统可能有不同的设置,以Linux为例,通常是半分钟,两倍的MSL就是一分钟,也就是60秒,并且这个数值是硬编码在内核中的,也就是说除非你重新编译内核,否则没法修改它。
TIME_WAIT状态存在的必要性。
虽然双方都同意关闭连接了,而且握手的4个报文也都协调和发送完毕,按理可以直接回到CLOSED状态(就好比从SYN_SEND状态到ESTABLISH(establish)状态那样);但是因为我们必须要假想网络是不可靠的,你无法保证你最后发送的ACK报文会一定被对方收到,比如丢包或者延迟到达,因此对方处于LAST_ACK状态下的SOCKET可能会因为超时未收到ACK报文,而重发FIN报文,所以这个TIME_WAIT状态的作用就是用来重发可能丢失的ACK报文,并保证于此。
简单说timewait之所以等待2MSL的时长,是为了避免因为网络丢包或者网络延迟而造成的tcp传输不可靠,而这个time_wait状态则可以最大限度的提升网络传输的可靠性。
应用层传到 TCP 协议的数据,不是以消息报为单位向目的主机发送,而是以字节流的方式发送到下游,这些数据可能被切割和组装成各种数据包,接收端接收到这些数据包后没有正确还原之前的消息,因此出现粘包现象。
https://baijiahao.baidu.com/s?id=1744728859176967544&wfr=spider&for=pc
如果是 TCP 协议,在大多数场景下,是不存在丢包和包乱序问题的,TCP 通信是可靠通信方式,TCP 协议栈通过序列号和包重传确认机制保证数据包的有序和一定被正确发到目的地;
如果是 UDP 协议,如果不能接受少量丢包,那就要自己在 UDP 的基础上实现类似 TCP 这种有序和可靠传输机制了(例如 RTP协议、RUDP 协议)。所以,问题拆解后,只剩下如何解决粘包的问题。
TCP产生拆包和粘包的原因:
TCP 是基于字节流的,虽然应用层和 TCP 传输层之间的数据交互是大小不等的数据块,但是 TCP 把这些数据块仅仅看成一连串无结构的字节流,没有边界;
从 TCP 的帧结构也可以看出,在 TCP 的首部没有表示数据长度的字段。
(1)发送方原因
因为TCP是面向流,没有边界,而操作系统在发送TCP数据时,会通过缓冲区来进行优化,例如缓冲区为1024个字节大小。
如果一次请求发送的数据量比较小,没达到缓冲区大小,TCP则会将多个请求合并为同一个请求进行发送,这就形成了粘包问题。
如果一次请求发送的数据量比较大,超过了缓冲区大小,TCP就会将其拆分为多次发送,这就是拆包。
(发送端需要等缓冲区满才发送出去,造成粘包)
(2)接收方原因
TCP接收到数据包时,并不会马上交到应用层进行处理,或者说应用层并不会立即处理。实际上,TCP将接收到的数据包保存在接收缓存里,然后应用程序主动从缓存读取收到的分组。这样一来,如果TCP接收数据包到缓存的速度大于应用程序从缓存中读取数据包的速度,多个包就会被缓存,应用程序就有可能读取到多个首尾相接粘到一起的包。(接收方不及时接收缓冲区的包,造成多个包接收)
针对这个问题,一般有3种解决方案:即直到边界的位置来划分出等效的用户消息
固定长度的消息:这种方式灵活性不高,实际很少用
特殊字符作为边界,比如HTTP的消息头中,使用回车符和换行符
注意的是,这个特殊字符如果出现在了消息内容里,需要对这个字符转义
自定义消息结构(把消息的尺寸与消息一块发送):可以自定义一个消息结构,head固定大小,head里存储一个字段来说明后面的数据有多大
TCP为了保证可靠传输并减少额外的开销(每次发包都要验证),采用了基于流的传输,基于流的传输不认为消息是一条一条的,是无保护消息边界的(保护消息边界:指传输协议把数据当做一条独立的消息在网上传输,接收端一次只能接受一条独立的消息)。
UDP则是面向消息传输的,是有保护消息边界的,接收方一次只接受一条独立的信息,所以不存在粘包问题。
举个例子:有三个数据包,大小分别为2k、4k、6k,如果采用UDP发送的话,不管接受方的接收缓存有多大,我们必须要进行至少三次以上的发送才能把数据包发送完,但是使用TCP协议发送的话,我们只需要接受方的接收缓存有12k的大小,就可以一次把这3个数据包全部发送完毕。
谢希仁版《计算机网络》中的例子:
"已失效的连接请求报文段”的产生在这样一种情况下:
client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。
本来这是一个早已失效的报文段,但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。
于是就向client发出确认报文段,同意建立连接。
假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。
由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据,但server却以为新的运输连接已经建立,并一直等待client发来数据。
这样,server的很多资源就白白浪费掉了
采用“三次握手”的办法可以防止上述现象发生
拥塞控制和慢启动等“缺点”
UDP要想可靠,就要接收方收到UDP之后回复个确认包,发送方有个机制,收不到确认包就要重新发送,每个包有递增的序号,接收方发现中间丢了包就要发重传请求,当网络太差时候频繁丢包,防止越丢包越重传的恶性循环,要有个发送窗口的限制,发送窗口的大小根据网络传输情况调整,调整算法要有一定自适应性。
。
好多游戏的tcp都是这么做的,自己实现udp的可靠传输
tcp为建立的每一个链接 建立了发送缓冲区,从建立的第一个序列号为0,后面每个字节的序列号都会+1
默认情况下建立 TCP 连接不会断开,只有在请求报头中声明 Connection: close 才会在请求完成后关闭连接
这东西其实就是 TCP 的保活机制
在S和C建立连接后,若双方均不发送数据只保持连接,则再两小时后系统会自动启动保活机制向peer发送包,看对方是否回应ack,若可以收到则继续保持,否则无效。
https://zhuanlan.zhihu.com/p/390939380
开启keepalive
如果两端的 TCP 连接一直没有数据交互,达到了触发 TCP 保活机制的条件,那么内核里的 TCP 协议栈就会发送探测报文。
如果对端程序是正常工作的(服务端)。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。
如果对端主机崩溃(客户端),或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡。
所以,TCP 保活机制可以在双方没有数据交互的情况,通过探测报文,来确定对方的 TCP 连接是否存活。
主机崩溃(无keepalive)
在没有开启 TCP keepalive,且双方一直没有数据交互的情况下,如果客户端的「主机崩溃」了,会发生什么。
如果客户端主机崩溃了,服务端是无法感知到的,在加上服务端没有开启 TCP keepalive,又没有数据交互的情况下,服务端的 TCP 连接将会一直处于 ESTABLISHED 连接状态,直到服务端重启进程。
进程崩溃的情况(无keepalive)
kill -9 来模拟进程崩溃的情况,发现在 kill 掉进程后,服务端会发送 FIN 报文,与客户端进行四次挥手。
所以,即使没有开启 TCP keepalive,且双方也没有数据交互的情况下,如果其中一方的进程发生了崩溃,这个过程操作系统是可以感知的到的,于是就会发送 FIN 报文给对方,然后与对方进行 TCP 四次挥手。
在 TCP 四次握手过程中,当客户端发送了 FIN 报文后,服务器进入了 CLOSE_WAIT 状态,表示服务器已经接收到了客户端发来的 FIN 报文,并向客户端发送了 ACK 报文作为响应,但服务器还有数据需要发送给客户端,因此仍然保持连接。
在 CLOSE_WAIT 状态下,服务器可以发送数据,但不能再发送 FIN 报文。只有在服务器的应用程序结束了对该连接的所有数据传输,并主动关闭了连接后,服务器才能发送 FIN 报文,即进入最后的 TIME_WAIT 状态,等待可能出现的重传报文。
因此,只有在服务器应用程序结束了对该连接的所有数据传输,并调用了 close() 函数关闭连接时,服务器才能发送 FIN 报文,进行 TCP 的最后一次握手,完成连接的关闭。
第一,为了保证A发送的最后一个ACK报文(四次挥手)能够到达B。这个ACK报文段有可能丢失,因而使处在LAST-ACK状态的B收不到对已发送的FIN+ACK报文段的确认。B会超时重传这个FIN+ACK报文段,而A就能在2MSL时间内收到这个重传的FIN+ACK报文段。
如果A在TIME-WAIT状态不等待一段时间,而是在发送完ACK报文段后就立即释放连接,就无法收到B重传的FIN+ACK报文段,因而也不会再发送一次确认报文段。这样,B就无法按照正常的步骤进入CLOSED状态。
第二,A在发送完ACK报文段后,再经过2MSL时间,就可以使本连接持续的时间所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求的报文段。
HTTP是一个属于应用层的面向对象的协议,主要特点概括主要有5点:
1、无状态:协议对于事务处理无记忆能力,每次客户端发起请求,都会新开一个TCP连接。
2、支持客户/服务器模式。
3、简单快速:客户端请求只需发送请求方法和路径。
4、灵活:允许任意类型的数据对象,使用Content-Type加以标记。
5、无连接:TCP不可复用,即服务器处理完客户请求并收到应答后,断开连接,节省传输时间。
特性
HTTP/2的通过支持请求与响应的多路复用来减少延迟,通过压缩HTTP首部字段将协议开销降至最低,同时增加对请求优先级和服务器端推送的支持。
二进制分帧,按帧方式传输
多路复用,代替原来的序列和阻塞机制
header 头部压缩,通过 HPACK 压缩格式
HTTP/2在客户端和服务器端使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不再通过每次请求和响应发送;
首部表在HTTP/2的连接存续期内始终存在,由客户端和服务器共同渐进地更新;
每个新的首部键-值对要么被追加到当前表的末尾,要么替换表中之前的值。
服务器推送,服务端可以主动推送资源
服务端可以在发送页面HTML时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。例如服务端可以主动把JS和CSS文件推送给客户端,而不需要客户端解析HTML时再发送这些请求。
服务端可以主动推送,客户端也有权利选择是否接收。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送RST_STREAM帧来拒收。主动推送也遵守同源策略,服务器不会随便推送第三方资源给客户端。
连接建立延时低,一次往返可建立HTTPS连接
改进的拥塞控制,高效的重传确认机制
切换网络保持连接,从4G切换到WIFI不用重建连接
首先在HTTP 0.9版本中,每次请求都需要新开一个TCP连接,是不可复用的,这非常的消耗性能。
因此在HTTP 1.0版本,引入了Keep-Alive 持久连接的概念,使用Connection: Keep-Alive的首部来操作TCP的持久连接,有效的解决了TCP连接不可复用的问题,减少性能损耗。
但是,他也存在一定的问题,在HTTP 1.0中其实默认的是短链接,没有正式规定Connection: Keep-Alive操作,这时HTTP 1.1版本出现了。
HTTP1.1规定了默认保持长连接(HTTP persistent connection ,也有翻译为持久连接),数据传输完成了保持TCP连接不断开(不发RST包、不四次握手),等待在同域名下继续用这个通道传输数据;支持长连接(PersistentConnection)和请求的流水线(Pipelining)处理,相反的就是短连接,它只支持短连接。
顺序接收
http2完全摒弃http1.1半双工通信的方式,实现了全双工通信,具体表现为:浏览器针对同一个域名的资源,只建立一个tcp连接通道,所有的针对这个域名的请求全部在这个通道中完成,并且引入了流的机制,这条通道可以同时处理多个request,这不同于http1.1的pepeline,http2的多路复用,对于request的响应并不会因为上一个request的响应未完成而阻塞,http2彻底解决了http层面的队头阻塞。
http2中在一个tcp通道中的所有http请求不分先后,不会阻塞,同样是一个页面中多个资源同时去请求
- 数据格式
- http1.1是明文协议,解析http1.1的明文是基于文本。
- http2.0的协议采用的是二进制格式。
数据分帧
就在这时,光芒照在了HTTP 2.0上,HTTP 2.0 over TCP出现了,它通过数据分帧 — 多个请求复用一个TCP连接(最多6个),然后每个request-response都被拆分成若干个frame帧发送,这样即使一个请求被阻塞了,也不会影响其他请求,如下图,其实它只解决了一部分问题~
HTTP2.0通过在应用层和传输层之间增加一个二进制分帧层,突破了HTTP1.1的性能限制、改进传输性能。
多路复用
概念
ps:如果队头阻塞的粒度是http request级别,那么HTTP/2 over TCP的确解决了HTTP 1.1中的问题。但是在HTTP 2.0 over TCP版本都是基于TCP实现。因此,HTTP 2.0 over TCP并没有解决数据传输层的队头阻塞问题。
HTTP 2.0 over QUIC也可以称为HTTP 3.0,它使用UDP实现了一个可靠的多路复用传输层。我们所知的UDP是面向数据报文的,数据包之间没有约束(这也带来了一定的安全性问题,详见UDP协议及其安全隐患),QUIC就是充分利用这个特性解决传输层的队头阻塞问题的。当然,QUIC的协议实现有非常多的细节,而这方面Google确实做得非常好,如果你想进一步了解,可以关注他们的 开源实现。
在TCP传输中客户端与服务端通过socket进行通信,通信流程:
对于服务器端开发主要流程–类似于 接电话过程
socket()[找到一个可以通话的手机]----->bind()[插入一个固定号码]------>listen()-------> accept------->recv()------->send()------>close();
对于客户端开发主要流程----类似于打电话过程
socket()----->connect()------>recv/read/send------>close()
对于TCP协议 =建立连接就在客户端connect()与服务器listen之间 建立TCP连接(三次握手)
一个服务器启动的时候,会监听一个端口。其实就是新建了一个socket。
那么如果有一个连接到来的时候,我们通过accept就能拿到这个新连接对应的socket。那么这个socket和监听的socket是不是同一个呢?
其实socket分为监听型和通信型的。表面上,服务器用一个端口实现了多个连接,但是这个端口是用于监听的,底层用于和客户端通信的其实是另一个socket。
所以每一个连接过来,负责监听的socket发现是一个建立连接的包(syn包),他就会生成一个新的socket与之通信(accept的时候返回的那个)。
accept()系统调用要做的事情:
对于客户端的 connect() 函数,该函数的功能为客户端主动连接服务器,建立连接是通过三次握手,而这个连接的过程是由内核完成,不是这个函数完成的,这个函数的作用仅仅是通知 Linux 内核,让 Linux 内核自动完成 TCP 三次握手连接(三次握手详情,请看《浅谈 TCP 三次握手》),最后把连接的结果返回给这个函数的返回值(成功连接为0, 失败为-1)。
通常的情况,客户端的 connect() 函数默认会一直阻塞,直到三次握手成功或超时失败才返回(正常的情况,这个过程很快完成)。
对于服务器,它是被动连接的。举一个生活中的例子,通常的情况下,移动的客服(相当于服务器)是等待着客户(相当于客户端)电话的到来。而这个过程,需要调用listen()函数。
listen() 函数的主要作用就是将套接字( sockfd )变成被动的连接监听套接字(被动等待客户端的连接),至于参数 backlog 的作用是设置内核中连接队列的长度(这个长度有什么用,后面做详细的解释),TCP 三次握手也不是由这个函数完成,listen()的作用仅仅告诉内核一些信息。
这里需要注意的是,listen()函数不会阻塞,它主要做的事情为,将该套接字和套接字对应的连接队列长度告诉 Linux 内核,然后,listen()函数就结束。
这样的话,当有一个客户端主动连接(connect()),Linux 内核就自动完成TCP 三次握手,将建立好的链接自动存储到队列中,如此重复。
所以,只要 TCP 服务器调用了 listen(),客户端就可以通过 connect() 和服务器建立连接,而这个连接的过程是由内核完成。
accept()函数功能是,从处于 established 状态的连接队列头部取出一个已经完成的连接,如果这个队列没有已经完成的连接,accept()函数就会阻塞,直到取出队列中已完成的用户连接为止。
如果,服务器不能及时调用 accept() 取走队列中已完成的连接,队列满掉后会怎样呢?UNP(《unix网络编程》)告诉我们,服务器的连接队列满掉后,服务器不会对再对建立新连接的syn进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。但实际上Linux的并不是这样的!
https是在TCP协议与http之间加了一个控制安全传输的SSL协议,也就是说,直接运行在TCP之上的HTTP是普通的HTTP,运行在SSL/TLS上的HTTP则是HTTPS。这几个协议在计算机网络的OSI七层模型中的位置如下表所示:
TCP连接建立好后,对于HTTP而言,服务器就可以发数据给客户端。
但是对于HTTPS,它还要运行SSL/TLS协议,SSL/TLS协议分两层,第一层是记录协议,主要用于传输数据的加密压缩;第二层是握手协议,它建立在第一层协议之上,主要用于数据传输前的双方身份认证、协商加密算法、交换密钥。
HTTPS验证过程就是SSL握手协议的交互过程。“HTTPS验证”这个说法其实不准确的,应该是“SSL验证”,这两种说法网上都能看到。
HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。
双向认证,顾名思义,客户端和服务器端都需要验证对方的身份,在建立HTTPS连接的过程中,握手的流程比单向认证多了几步。
单向认证的过程,客户端从服务器端下载服务器端公钥证书进行验证,然后建立安全通信通道。
双向通信流程,客户端除了需要从服务器端下载服务器的公钥证书进行验证外,还需要把客户端的公钥证书上传到服务器端给服务器端进行验证,等双方都认证通过了,才开始建立安全通信通道进行数据传输。
单向认证流程中,服务器端保存着公钥证书和私钥两个文件,整个握手过程如下:
双向认证流程
HTTP是要基于TCP连接基础上的,简单的说,TCP就是单纯建立连接,不涉及任何我们需要请求的实际数据,简单的传输。HTTP是用来收发数据,即实际应用上来的。
TCP/IP是一个分层的协议,计算机里面很多东西都是这样分层来设计的,这样设计的好处是每一层只需要考虑自己需要处理的数据。
在TCP/IP协议的四层模型里,HTTP属于应用层,之所以叫应用层是因为这些是我们直接接触到的具体应用,比如用于网页浏览的HTTP,用于传输文件的FTP,或者远程登录的Telnet和SSH。应用层需要交换数据的时候,比如你需要访问一个网页,浏览器会先生成HTTP的数据包,然后交给传输层去建立连接。
TCP是传输层,它通过流量控制,重传等机制来在主机间建立一个可靠的连接。TCP使用不同的端口来标记不同的应用发起的连接。为了避免在网络层产生分片,TCP会通过协商MSS,Path MTU Discovery的方式来对数据包进行分段交给网络层。
IP是网络层,负责处理不同的网络之间发送的数据包,它不需要考虑具体应用或者端口的信息。它的主要工作是分片(fragmentation)和重组(reassembly)数据包(TCP需要通过重传来保证可靠性,所以不依赖网络层的分片,主要是UDP使用)以及根据IP地址做路由(route)。
TCP是底层通讯协议,定义的是数据传输和连接方式的规范
HTTP是应用层协议,定义的是传输数据的内容的规范
HTTP协议中的数据是利用TCP协议传输的,所以支持HTTP也就一定支持TCP
HTTP/1.* 一次请求-响应,建立一个连接,用完关闭;每一个请求都要建立一个连接;
HTTP/1.1 通过设定 Connection:keep-alive 字段来保持TCP的长连接 ,下一个请求需要在上一个请求的响应之后发送,因此会存在队头阻塞。HTTP1.1进一步地支持在持久连接上使用管道化(pipelining)特性。管道化允许客户端在已发送的请求收到服务端的响应之前发送下一个请求,借此来减少等待时间提高吞吐率。但是需要响应的顺序是按照请求顺序进行,因此也会存在队头阻塞。一旦有某请求超时等,后续请求只能被阻塞,毫无办法,也就是人们常说的线头阻塞;
HTTP/2多个请求可同时在一个连接上并行执行(一个连接被多个流复用。一个流表示一次请求-响应过程。)。某个请求任务耗时严重,不会影响到其它连接的正常执行;
这个过程有两个概念:流和帧。帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。
好处:
减少服务端连接压力,减少占用内存,提升连接吞吐量;
连接数的减少改善了网络拥塞状况,慢启动时间减少,拥塞和丢包恢复速度更快;
避免连接频繁创建和关闭(三次连接、四次挥手);
挂掉,则一段时间之后,保活时间到期,则关闭。
或者TCP等待时间结束,关闭TCP连接。或者采用应用层周期发送心跳包来检测是否对方还在。
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此,HTTP协议不适合传输一些敏感信息,比如:信用卡号、密码等支付信息。
为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
HTTPS其实是有两部分组成:HTTP + SSL / TLS,也就是在HTTP上又加了一层处理加密信息的模块。服务端和客户端的信息传输都会通过TLS进行加密,所以传输的数据都是加密后的数据。
HTTP特点:
HTTPS特点:
基于HTTP协议,通过SSL或TLS提供加密处理数据、验证对方身份以及数据完整性保护
通过抓包可以看到数据不是明文传输,而且HTTPS有如下特点:
HTTPS和HTTP的区别主要如下:
1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。
大致流程
一、URL 解析
地址解析:
首先判断你输入的是一个合法的 URL 还是一个待搜索的关键词,并且根据你输入的内容进行自动完成、字符编码等操作。
HSTS
由于安全隐患,会使用 HSTS 强制客户端使用 HTTPS 访问页面。详见:你所不知道的 HSTS。
其他操作
浏览器还会进行一些额外的操作,比如安全检查、访问限制(之前国产浏览器限制 996.icu)。
检查缓存
二、DNS 查询
基本步骤
首先我们先说一下什么是文件描述符(File descriptor),根据它的英文首字母也简称FD,它是一个用于表述指向文件的引用的抽象化概念。
它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。
writefds、readfds、和exceptfds是三个文件描述符集合。
select会遍历每个集合的前nfds个描述符,分别找到可以读取、可以写入、发生错误的描述符,统称为就绪的描述符。
https://www.dmozdir.org/News/?CID=54444
netpoll会调用epollwait获取就绪的 fd 列表,对应的epoll函数是epoll_wait。toRun是一个 g 的链表,存储要恢复的 goroutines,最后返回给调用方。
如果epollwait返回的n大于零,那么表示被监控的文件描述符出现了待处理的事件,那么需要调用for循环进行处理。
循环里面会根据时间类型设置mode,然后拿出对应的pollDesc,调用netpollready方法。
远程过程调用,简称为RPC,是一个计算机通信协议,它允许运行于一台计算机的程序调用另一台计算机的子程序,而无需额外地为这个交互作用编程。
RPC与传统的HTTP对比
优点:
传输效率高(二进制传输)
发起调用的一方无需知道RPC的具体实现,如同调用本地函数般调用
缺点:
因为用户程序不能操作内核空间,所以只能给协程分配用户栈,而操作系统对于协程一无所知,所以协程被成为“用户态线程”
在创建协程时,都要指定执行入口
协程的关键思想在于 控制流的 ‘主动让出’和‘ 恢复’
每个协程都有自己的执行栈,可以保存自己的执行现场
可以由用户程序,按需创建协程。
协程让出执行权时,都需要保存执行现场,后从中断处恢复,继续执行。
这样实现了灵活、轻量、由用户态调度的多任务模型
高并发,多线程模型下,内核、用户态两头忙
进程打开文件、创建的socket等 都会记录在文件描述符表,socket所有操作都由操作系统来提供(完成)。返回给应用程序的,只有一个描述符 fd,用于识别不同的socket。
在操作系统创建每个socket时,都会创建一个读缓冲区、写缓冲区,
阻塞式io, 要处理一个socket就要占用一个线程,等这个socket处理完才能接手下一个,高并发会加剧调度开销。
非阻塞式io(不让出cpu),但是需要频繁的检查socket是否就绪了,忙等待,很难把握轮训时间间隔时间,容易造成空耗cpu,加剧响应延迟。
io多路复用 有操作系统提供支持,把需要等待的socket加入到监听集合,这样可以通过一次系统调用,同时监听多个socket,有socket就绪了,就可以逐个处理了。不用为等待某个socket而阻塞或空耗cpu,也不会陷入“忙等待”中,并发能力大幅提升
I/O多路复用,I/O就是指的我们网络I/O,多路指多个TCP连接(或多个Channel),复用指复用一个或少量线程。串起来理解就是很多个网络I/O复用一个或少量的线程来处理这些连接。
每次都只需要传入一个要操作的fd,无需传入所有监听集合,而且只需要注册这一次,无需遍历所有集合。
Linux下的select、poll和epoll就是干这个的。将用户socket对应的fd注册进epoll,然后epoll帮你监听哪些socket上有消息到达,这样就避免了大量的无用操作。此时的socket应该采用非阻塞模式。
这样,整个过程只在调用select、poll、epoll这些调用的时候才会阻塞,收发客户消息是不会阻塞的,整个进程或者线程就被充分利用起来,这就是事件驱动,所谓的reactor模式。
例如,一个socket可读,但是只读半条,我们需要随着事件的等待和就绪, 频繁的保存(并切换下一个就绪的fd)和恢复现场
fd交给协程来负责,需要等待时就注册io事件,然后让出
协程拥有自己的栈,保存和恢复现场都很容易
阻塞式I/O和I/O复用,两个阶段都阻塞,那区别在哪里呢?就在于第三节讲述的Selector,虽然第一阶段都是阻塞,但是阻塞式I/O如果要接收更多的连接,就必须创建更多的线程。
I/O复用模式下在第一个阶段大量的连接 统统都可以过来直接注册到Selector复用器上面,同时只要单个或者少量的线程来循环处理这些连接事件就可以了,一旦达到“就绪”的条件,就可以立即执行真正的I/O操作。
这就是I/O复用与传统的阻塞式I/O最大的不同。也正是I/O复用的精髓所在。
那么使用select的优势在于我们可以等到网络事件就绪,那么用少量的线程去轮询Selector上面注册的事件,不就绪的不处理,就绪的拿出来立即执行真正的I/O操作。这个使得我们就可以用极少量的线程去HOLD住大量的连接。
1、支持一个进程所能打开的最大连接数
select:单个进程所能打开的最大连接数有FD_SETSIZE宏定义,其大小是32个整数的大小(在32位的机器上,大小就是3232,同理64位机器上FD_SETSIZE为3264),当然我们可以对进行修改,然后重新编译内核,但是性能可能会受到影响,这需要进一步的测试。
poll:poll本质上和select没有区别,但是它没有最大连接数的限制,原因是它是基于链表来存储的
epoll:虽然连接数有上限,但是很大,1G内存的机器上可以打开10万左右的连接,2G内存的机器可以打开20万左右的连接
2、FD剧增后带来的IO效率问题
select:因为每次调用时都会对连接进行线性遍历,所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。
poll:同上
epoll: 因为epoll内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epoll没有前面两者的线性下降的性能问题,但是所有socket都很活跃的情况下,可能会有性能问题。
3、 消息传递方式
select:内核需要将消息传递到用户空间,都需要内核拷贝动作
poll:同上
epoll:epoll通过内核和用户空间共享一块内存来实现的。
(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。
(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。
当有事件准备就绪之后,会存到第二个参数epoll_event结构体中。通过访问这个结构体就可以得到所有已经准备好事件的套接字描述符(fd)。
这里就不用再像之前select和poll那样,遍历所有的套接字描述符之后才能知道究竟是哪个描述符已经准备就绪了,这样减少了一次O(n)的遍历,大大提高了效率。
当服务程序需要承载大量TCP链接的时候,比如我们的消息推送系统,IM通讯,web聊天等等,在我们已经理解Selector原理的情况下,知道使用I/O复用可以用少量的线程处理大量的链接。I/O多路复用技术以事件驱动编程为基础。它运行在单一进程上下文中,因此每个逻辑流都能访问该进程的全部地址空间,这样在流之间共享数据变得很容易。
定义如下:
301 Moved Permanently
被请求的资源已永久移动到新位置,并且将来任何对此资源的引用都应该使用本响应返回的若干个URI之一。如果可能,拥有链接编辑功能的客户端应当自动把请求的地址修改为从服务器反馈回来的地址。除非额外指定,否则这个响应也是可缓存的。
302 Found
请求的资源现在临时从不同的URI响应请求。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。只有在Cache-Control或Expires中进行了指定的情况下,这个响应才是可缓存的。
字面上的区别就是301是永久重定向,而302是临时重定向。 当然,他们之间也是有共同点的,就是用户都可以看到url替换为了一个新的,然后发出请求。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。