长短连接
- 短连接就是建立连接后,发送一次request,接收一次response连接就断开。
- httlp/1.0使用的就是短连接。当我们需要一次性发起多次request时,建立连接,销毁连接就成了主要矛盾,需要浪费大量的资源。
- 所以就有了长连接,在一定时间内,只需要建立一次连接,即可接收和发送数据。Connection: keep-alive。
再谈端口号
- 端口号用来唯一标识一台主机上通信的进程,在tcp协议中用“源ip”、“源端口”、“目的IP”、“目的端口”、“协议号”这样一个五元组来标识一个通信。
- 打开浏览器,用不同的网页会绑定不同的端口号,进而访问的数据不同。A网页访问的百度,那么B网页不会显示百度。
- 我们得出结论,一个进程可以绑定多个端口号,但是一个端口号只能绑定一个进程。 因为端口号是用来标识唯一进程的,如果能绑定多个进程,端口号就失去了意义。
1,端口号范围划分
- 端口号有16位,最多有65536个端口号。
- 一般0到1023号端口为知名端口号,默认绑定了某些服务,如果HTTP,HTTPS,DNS,SSH,所以操作系统不允许用户进行绑定。
- 1024 - 65535: 操作系统动态分配的端口号. 客户端程序的端口号, 就是由操作系统从这个范围分配的.
一些知名的端口号:
- http:80
- https:443
- ssh : 22
- ftp : 21
- telnet : 23
相关指令
- 查看知名端口号
cat /etc/services
- 查看网络状态(*****)
netstat
n:拒绝显示别名,能显示数字的全部转化成数字。
l:列出所有在listen服务状态
p:显示建立相关链接的程序名
t:只显示tcp相关选项
u:只显示udp相关选项
a:显示所有连线中的Socket。
- 查看服务器的进程id
pidof 服务器的名字
UDP
用户数据报协议(udp)是工作在传输层的一个重要协议,UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。
UDP的协议格式
struct udphdr {
__be16 source;
__be16 dest;
__be16 len;
__sum16 check;
};
如何将报头和有效载荷分离?
- UDP的报头是定长报头,它的报头只有8字节,只需要读取8字节的长度就行。
16位源端口号和16位目的端口号:
- 所有的协议都要解决分用的问题。而UDP的解决方法就是通过端口号。目的端口号就标记了该报文应该交付的进程。而源端口号则用来回复数据。
16位UDP长度:
- 我们已经分离出了报头,但是我们如何判断有效载荷的长度呢?
- 16位UDP长度就是有效载荷+报头的字节数,我们只需要用该数据减去8,即得到有效载荷的长度。
- 因为这16位标记了一个UDP报文的长度,所以每次UDP报文最多能发送的有效载荷是2^16 - 8字节,这是一个很小的数字。
- 所以如何数据过大,我们就需要手动分包几次发送,然后在接收端拼接起来。
16位UDP校验和
- UDP是不可靠的协议,校验和一旦检测出数据有问题,立刻丢弃,其他的问题一概不管。
UDP的特点
- 无连接:获得对方的ip和端口号时直接传输数据,不需要建立连接。
- 不可靠:什么也不管,出错直接丢弃。
- 面向数据报:每次发送或者读取数据都有明显的边界。比如我发送100个字节,你要么不读,要读就读取100个字节,不存在只读取10个字节的情况。
其他:
- 相比于tcp,UDP是更快速简单的。
- udp是全双工,可以同时接收和发送数据。
- udp没有发送缓冲区,但是有接收缓冲区。
udp的缓冲区:
- udp没有发送缓冲区,因为它收到上层的数据报,直接加上报文就往下层传,不需要做任何多余的动作,因为它不考虑可靠性。
- udp有接收缓冲区,如果发送数据过多,接收缓冲区也会塞满。如果缓冲区塞满,多余的udp报文直接丢弃(没错,就是这么暴力。)。而且udp的接收缓冲区不保证udp报文的有序性。即你发送1,2,3,可能收上来就是3,2,1。
基于UDP的应用层协议
- NFS: 网络文件系统
- TFTP: 简单文件传输协议
- DHCP: 动态主机配置协议
- BOOTP: 启动协议(用于无盘设备启动)
- DNS: 域名解析协议
TCP
TCP全称为传输控制协议,主要功能是对数据的传输进行详细的控制。
TCP的协议格式
- tcp报头同样的,需要解决分用和解包的问题。
- 第一行的4个字节的源端口号,和目的端口号决定了将报文交付给哪一个进程。
- 而序号标识了数据的顺序。
- 确认序号是对数据的确认,一般是序号+1.(后面细谈)
- 数据偏移标识了tcp报头的长度,它的单位是4字节,也就是说如过数据偏移为x,那么报头长度为x*4。
6个标识位:
- 实际上在源码中是8个标识位,但是后两个跟IP有关,在次不谈。
- FIN(finish):终止连接的标识位,在4次挥手的报文中使用。
- SYN(synchronization, 同步) : 建立连接的标识位。
- RST:建立连接出现问题,重新建立连接。3次握手的时候,客户端收到服务器的ACK+SYN就认为连接建立,就会发送数据。此时如果服务器没有收到客户端的ACK,反而接收到了数据,服务器就会意识到ACK丢失,那么就是设置RST标识位。
- PSH:催促自己的输出缓冲区快将数据发送,催促对方的接收缓冲区快将数据递交上层。
- ACK:确认字符,用来确认信息收到。
- URG:当有紧急报文时,URG被设置,配合紧急指针来使用。
保留项:
- tcp报头不是定长的,因为在报头后面有个保留选项可选可不选。具体功能不做探讨。
16位窗口:
- tcp是有接收缓冲区的。但是缓冲区一定是有大小的。如果发送方不停的发送大量数据给服务器,那么缓冲区一定会被填满,此时再发送数据,就会产生丢包。
- 所以我们希望发送方获得接收方的接受能力,如果对方接受能力较弱,则发送慢一点。
- 16位窗口在发送tcp报文的时候会被填充,填充的是(发送方)自己的未被使用的接收缓冲区大小。注意的是,在tcp3次握手的时候,双方的窗口就会被“协商”。这叫做流量控制。
实际上,tcp是有超时重传机制的,你可能会问,既然有重传机制,重传不就好了,为什么还要流量控制呢?
- 使用超时重传处理丢包问题是不健康的!网络资源是有限的,你重传肯定会浪费资源,所以为了节省互联网的资源,进行流量控制。
紧急指针:
- 当有紧急报文来的时候,我们需要先处理紧急报文,而紧急报文会被放到tcp报文的最前面。紧急指针就指向紧急报文的最后一个字节+1,用来标识紧急数据的长度。
检验和:
- 如何判断数据在发送过程中是否丢失某些信息?
- 发送端填充, CRC校验. 接收端校验不通过, 则认为数据有问题. 此处的检验和不光包含TCP首部, 也包含TCP数据部分.
tcp有没有规定数据的长度?
struct tcphdr {
__be16 source;
__be16 dest;
__be32 seq;
__be32 ack_seq;
#if defined(__LITTLE_ENDIAN_BITFIELD)
__u16 res1:4,
doff:4,
fin:1,
syn:1,
rst:1,
psh:1,
ack:1,
urg:1,
ece:1,
cwr:1;
#elif defined(__BIG_ENDIAN_BITFIELD)
__u16 doff:4,
res1:4,
cwr:1,
ece:1,
urg:1,
ack:1,
psh:1,
rst:1,
syn:1,
fin:1;
#else
#error "Adjust your <asm/byteorder.h> defines"
#endif
__be16 window;
__sum16 check;
__be16 urg_ptr;
};
tcp确认应答机制
可靠性:只要收到了对方的应答,认为之前的数据对方已经收到!!
栗子:
- A主机给B主机发送一个hello_B,A主机怎样确认B主机接收到了?收到B主机的回复,我收到了。但是,我收到了这句话本身就是数据,B主机怎么保证我收到了这句话被A接收到呢?
- 你会发现一个问题:在互联网数据传输中,没有绝对的可靠性,因为你永远无法确定最后一条消息对方是否接收到。
- 所以我们只有相对可靠性,所以我们不需要对应答进行应答,即不需要对ACK进行ACK。
tcp基于确认应答机制:
- tcp进行的是可靠传输,传输的是字节流。因此,对于tcp协议需要做到以下两点:①确保接收端成功接收到数据②确保接收端接收到数据的有序性。
- 即使数据按照正确顺序发送,由于网络的错综复杂,接收方也不一定会按照顺序接收。所以需要进行排序。
确认应答机制:发送方每发送一个数据,接收方就要向发送方发送一个确认信号,表明自己已经收到该数据,从而确保接收端成功接收到数据。
那么tcp如何保证数据的按序到达呢?
- tcp给发送缓冲区的每一个字节的数据都标号,发送的时候将最后一个字节的序号填充到tcp报文的16位序号中,代表发送方发送的数据顺序。接收方只需要按照16位序号排序即可保证数据的有序。
- 而tcp还有一个16位确认序号, 每次接收数据,tcp需要给对方发送一个确认报文,这个确认报文的ack会被填充,而且16位的确认序号是数据的序号+1,表示你应该从确认序号开始发送数据,之前的数据已被接收。 通过序号和确认序号,tcp完美的实现了确认应答机制。
两个问题:
我们需要序号来保证接收的顺序。
因为tcp是全双工的,可以同时发送和接受数据。
超时重传
tcp协议在保证数据可靠传输时,还需要确保丢失的数据能够重新发送。如果数据在发送后一段时间内没有收到对方的ACK,就认为数据丢失,需要重传,这就是超时重传机制。 哪些情况需要超时重传呢?
由于网络阻塞或其他网络原因,数据直接丢失在网络中。
也有可能是主机B在回复ACK时,ACK丢失在网络中。
- 无论是数据直接丢失还是对方的ACK丢失,这对于A主机来说都是一样的,因为确认应答机制就规定A主机必须收到B主机的ACK才能确认数据被正常接收。
- 这里又有两个问题需要解决:
- 既然是超时重传,那么时间怎么设置?
- 重传后,极有可能导致数据的重复,怎么去重?
分析:
- 时间一定不能设置太短,否则就会大量传输重复数据,浪费网络资源。
- 时间也不能太长,因为太长导致传输效率下降。
- TCP为了保证无论在任何情况下都能比较高性能的通信,会动态计算这个时间。
- 在linux中超时时间以500ms为一个单位,每次判断超时重发的时间都是500ms的整数倍。
- 如果重发一次仍然得不到应答,会等待500*2ms后进行重传,
- 如果还是不能得到应答则在等待4*500ms重发,指数形式增长,以此类推。
- 当累计到一定次数,tcp会认为对端或者网络出现异常,直接强制断开连接。
去重:
- tcp在报文中有16位序列号,序列号的另外一个作用就是去重。相同的序列号的报文保留一份即可。
http发送的一个request,然后接收到一个response,这是否对应tcp的一次确认应答呢?
- 完全不是。上层的request和response和下层的确认应答机制没有一对一的关系。
- send仅仅是将上层数据拷贝到内核。具体怎么发送,分几次确认应答是通信细节,不是http关心的。
例子:
- 学校订购1万本书,然后出版商让顺丰快递来运这一万本书。顺丰可以选择一次运500本,或者一次运400本,最后到目的地,在整合到1万本发给学校。
连接管理机制
tcp需要3次握手建立连接,4次挥手断开连接。 如何理解连接?
三次握手:
- 一般是客户端主动发起建立连接的请求。
- 客户端向服务器发起建立连接的请求,即带有SYN的报文,并将自己设置为SYN_SENT的状态,阻塞等待。
- 服务器接收到SYN报文后,发送ACK+SYN报文给客户端,表示已经接收到服务器的SYN报文,并要求建立服务器到客户端的连接。并将自己设置为SYN_RCVD状态。
- 客户端接收到ACK+SYN后,立刻向服务器发送ACK报文,此时在客户端看来,连接建立完成,即ESTABLISHED。
- 当服务器接收到最后的ACK时,服务器端的连接也建立完成。
为什么建立连接是三次握手?一次两次不行吗?四次握手呢?
- 因为需要同步。tcp在建立连接的时候需要进行序号协商。假设A是客户端,B是服务器。A给B发送带有SYN的报文,在发送缓冲区随机选取一个序号seq = x,然后发送给服务器。tcp规定带有SYN的报文不允许携带数据,但是占有一个序号。B收到SYN报文后,将确认序号设为ack_seq = x + 1,然后设置SYN和ACK标识位。 tcp规定ACK可以携带数据,如果不携带,那么不消耗序号。在自己的缓冲区随机选取一个序号seq = y,发送给客户端。客户端收到后,将自己的ack_seq = y+1, 连接建立完成。
- 需要验证全双工。tcp3次握手就可以确认tcp双向通信的信道是可用的。服务器和客户端都进行了一次接收和发送过程。
- 如果是两次握手,就可能导致服务器的误判。 我们先来看看两次连接的正常情况:A发送SYN请求给B,B发送ACK,然后建立连接。如果SYN请求丢失,那么根据重传机制,A会再次发送SYN报文,此时连接建立完成,没有问题。
- 但是, 如果SYN没有丢失,是因为网络问题阻塞在了网络中。那么在客户端看来,服务器没有收到SYN,就会重传,此时连接建立完成,完成通信后,连接释放。但是,上一个被阻塞在网络中的连接也会被B接收,B就会建立连接,向A发送ACK。但是A根本就没有发起SYN,A就可以不理会这个ACK,导致服务器的资源被白白浪费。
- 一次握手根本就没有验证双全工的信道。而且2次通信容易受到SYN的洪水攻击,疯狂占用服务器资源。
- 那么为什么不是四次握手,五次握手,或者更多次呢? 因为在网络中,没有绝对可靠性,我们不需要对ACK进行ACK。3次握手是最小的代价,5次7次。。。握手跟3次效果一样,更多的握手只会造成资源的浪费。
三次握手一定成功吗?
- 不一定。但是不用担心。前两次握手的报文丢失是没有任何影响的。因为前两次报文的丢失,服务器和客户端没有一方建立好连接。没有资源的浪费。
- 即使第三次报文丢失,客户端已经建立好连接。客户端会进行数据发送,但是服务器没有建立好连接,服务器会发送一个带有RST的报文,告诉客户端,需要重新发送第二次连接。一旦超时重传好几次都没有建立完成,那么连接就被强转断开。
四次挥手
- tcp的连接释放需要进行4次挥手。我们假设A是服务器,B是客户端。
- A主动断开连接,向B发送连接释放报文,设置FIN为1,主动关闭TCP连接,不在发送数据。注意,这里的不发送数据是指应用层不再发送数据,不是指传输层等通信细节。其序号seq = u,u是A已传送过的数据+1。此时A进入FIN_WAIT_1。tcp规定FIN报文即使不携带数据,也消耗一个序号。
- B收到A的FIN报文后,就发出确认,确认序号ack_seq = u+1,序号为v,v等于B已发送的数据序号+1。B进入CLOSE_WAIT状态。此时tcp处于半关闭状态,A已经无法发送数据,但若B继续发送数据,A仍要接收。
- A收到B的确认后,就进入FIN_WAIT_2状态,等待B发出FIN报文。
- 若B没有要发送的数据,B就会发送FIN报文。将FIN标识位设置为1,将序号设置为w(为什么不是v?因为在B没有发送FIN期间,B还可以继续发送数据,单向通信。),同时B还必须重复发送上次的确认号ack_seq = u + 1。此时B进入LAST_ACK状态。
- A在收到B的FIN报文后,必须对此进行确认。确认报文中ACK标识位设为1,seq = u + 1(FIN消耗一个序号), ack_seq = w + 1。然后A进入TIME_WAIT状态。 注意,此时tcp连接现在还未释放掉!!!直到TIME_WAIT设置的时间2*MSL只后,A才进入CLOSED状态。
- B在收到ACK后,也进入CLOSED状态。
CLOSE_WAIT && TIME_WAIT
- CLOSE_WAIT: 当服务器接收到客户端的FIN,发送出ACK时,服务器就会变成CLOSE_WAIT状态。此时,如果服务器没有继续关闭对应的套接字(第三次挥手),那么连接就会卡在CLOSE_WAIT状态,浪费服务器资源。所以我们应该避免这种情况。
- TIME_WAIT: 主动断开连接的一方(没错,服务器也可能处于这个状态!),在最后发出ACK时,需要进行等待。
- 意义:1,最后一个ACK是可能在网络中丢失的,如果ACK丢失,那么B就无法释放连接。所以当B等待一段时间,没有等待ACK时,B就会进行超时重传FIN,此时若A已经CLOSED,那么无法接收ACK,导致服务器的资源一直被浪费。虽然,最终通过几次超时重传B也会被释放,但是这是一种级不健康的做法!!!所以,我们需要A等一等,这个时间就是2*MSL(最长报文段寿命),即发送一次数据被接收的最长时间。
- 2,等待历史数据在网络上进行消散。A断开连接后,B仍可发送数据给A。有可能B发送的数据阻塞在网络中,但是此时A已经销毁连接,无法接收B的数据。所以,需要A等数据消散在网络中。
- TIME_WAIT: 2*MSL,2倍的最大传送时间。
为什么是4次挥手?
TIME_WAIT状态的体现:
- 打开一个服务器,然后立刻关闭它。当我们再次通过同一个端口号连接它时,发现连接不上状态,因为此时服务器处于TIME_WAIT状态,底层还未断开。
- 但是服务器挂掉后需要立刻重启,否则损失惨重。我们需要解决这个问题。使用setsockopt函数进行端口复用。
抓包
tcpdump -i any -nn tcp port 8080
滑动窗口
- 用来弥补确认应答机制的缺点。确认应答机制要求我们必须收到ACK才能确定信息的可靠,但是上面我们一次只确认一条数据,这是串行的,这种方式虽然保证了数据很高的可靠性,但是效率有点低。
- 而滑动窗口就是牺牲一部分可靠性带来的效率极高的方法。
- 滑动窗口是发送缓冲区的一部分。可以将发送缓冲区想象成一个环形队列。
-
滑动窗口允许一次发送一批数据,而发送过程中不需要等待某一个数据的应答就可以直接发送。滑动窗口的大小就是发送的最大值(也可以发送小于滑动窗口的数据!)。 -
如上图,红框就是滑动窗口,28-31号数据已经被发送,而32-33号数据也可以直接发送,不用等待28-31号的ACK。已经发送但是未确认的数据不应该被清除,以防超时重传。 -
发送窗口里的序号就是允许发送的序号。滑动窗口越大,那么效率越高。但是滑动窗口的必须小于对方的窗口大小!滑动窗口还受限于拥塞窗口的大小,后面讲解。 -
滑动窗口后沿左边的数据可以清除,因为已经收到ACK。前沿右边的数据不可被发送,因为对方可能没有额外的空间来存储这部分数据。 -
滑动窗口的位置和大小都会改变!滑动窗口后沿 可以前移,当滑动窗口部分数据收到ACK。也可以不动,当没有收到ACK。不允许后移。当收到滑动窗口的某一个ACK时,就必须前移,因为tcp不会撤销确认。 比如28-31号数据,收到了30号的ACK,那么滑动窗口后沿就会前移3个字节而不管28-29号数据。滑动窗口的前沿通常是前移的,但也有可能不动。一是没有收到新的确认,对方的窗口大小也不变;二是收到了新的确认,对方的窗口变小了。 -
一个重要的结论:滑动窗口的前沿和后沿不是同步变化的!! -
前沿也可以后移,但是标准强烈不推荐!! 因为可能滑动窗口已经将数据发送出去,但是前沿后移了,将你已经发送的数据标识位不允许发送的数据,这就会造成错误。
描述一个滑动窗口:
- 我们只需要3个指针即可描述一个滑动窗口,即p1,p2,p3。
滑动窗口的丢包:
- 当数据包真正丢失了,是需要进行重传的,这也就要求滑动窗口中发送的ACK要确保他之前的数据都已经接收到。例如,接收端接收到的数据的顺序是1~ 1000、5001~ 6000、4001~ 5000,当接收到5001~ 6000的数据后不能立即发送6001的ACK,必须等到6001之前的数据全部到达才能发送。
- 当1001-2000的报文丢失后,接收端接下来的几个ACK报文都会将ack_seq设为1001,提醒客户端1001-2000号报文丢失。一但客户端收到3次同样的ack_seq,那么客户端就会重传这部分报文。
- 由于这种方式比超时重传更快,称为快重传。
- 这个时候接收端收到了 1001 之后 , 再次返回的 ACK 就是 7001 了 ( 因为 2001 - 7000) 接收端其实之前就已 经收到了, 被放到了接收端操作系统内核的接收缓冲区。
有了快重传,为什么还需要超时重传? 当你的滑动窗口小于3时,无法触发快重传。而且,如果所有的ACK都丢了,也无法触发快重传。所以需要超时重传兜底。这两种方式互相补充。
流量控制
-如果发送端一直向对方发送数据,那么就可能导致对方的接收缓冲区满了。此时如果再次发送,就可能丢包,所以需要根据接收端的能力进行流量控制。
- 在SYN建立连接的时候就会进行窗口协商,此时就已经进行流量控制了。
- 如果B已经无法接收数据,那么就会将自己的窗口设置为0,然后发给A。A收到后就会阻塞等待。
那么,如何知道对方的窗口已经有了空间? - A会时不时的发送一个不带有数据的窗口探测报文,用来获取对方的窗口大小。
- 但是也有可能你的窗口探测报文也会丢失,那么你就需要重传,如果窗口探测设置的时间是1s,但是你的缓冲区在0.1s以后就已经更新了窗口大小,那么发送方就还需要在等待0.9s在进行发送,会造成时间的浪费,所以接收方还应该设置一个窗口更新通知,当窗口更新完毕以后,主动的给发送方,这样也就节省了时间,提高了效率。
- 如果一直不取数据,那么就是应用层的bug。
拥塞控制
- 如果10000个报文有几个需要重传,那么就说明网络没问题。但是如果10000个报文,有9999个需要重传,那么tcp就会认为网络状态不好。
- 在不清楚当前网络状态下 , 贸然发送大量的数据 , 是很有可能引起雪上加霜的. TCP引入 慢启动 机制 , 先发少量的数据 , 探探路 , 摸清当前的网络拥堵状态 , 再决定按照多大的速度传输数据 ;
- 我们引入拥塞窗口的概念。而拥塞窗口就代表探路使用的少量数据的大小。开始发送数据的时候,拥塞窗口定义为1,每收到一个ACK拥塞窗口+1,每次发送数据的时候将拥塞窗口和主机端反馈的窗口大小做比较,取最小的作为实际发送的窗口大小。
慢启动:
- 为了避免网络更加阻塞,我们将阻塞窗口一开始设置的很小,然后指数增长的启动方式。慢启动并不是说阻塞窗口增长的慢,而是说我们一开始将阻塞窗口设置的非常小,这样比起一开始将大量数据放到网络上显得慢多了。
为了防止慢启动的指数增长过快,拥塞窗口增长到一定程度就会呈线性增长。
快重传和快恢复算法
- 因为拥塞控制的存在,又时候丢失少量报文,发送发也会重置拥塞窗口为1,这样就会损失效率。此时我们就需要快重传,快速的将丢失的报文告知发送方。
- 当发送方发现是少量报文丢失而非网络不好时,发送方就会执行快恢复算法,快速的扩大拥塞窗口的大小。
一个人的数据在网络看来是毛毛雨,那么为什么有拥塞控制?
- 拥塞控制是多人控制。
- 一个人的拥塞控制对网络影响不大, 但是现在几乎所有的进程都支持tcp服务,都有拥塞控制,所以所有的拥塞控制都发挥作用,就会让网络变好。
延迟应答
-
当服务器接收到tcp报文时,可以等一等再发送ACK,等一等。等待上层将自己的缓冲区的数据拿走,这样自己的窗口就会变得更大,然后ACK给客户端更大的窗口,这样能增加效率。 -
假设接收端缓冲区为1M. 一次收到了500K的数据; 如果立刻应答, 返回的窗口就是500K; -
但实际上可能处理端处理的速度很快, 10ms之内就把500K数据从缓冲区消费掉了; 在这种情况下, 接收端处理还远没有达到自己的极限, 即使窗口再放大一些, 也能处理过来; -
如果接收端稍微等一会再应答, 比如等待200ms再应答, 那么这个时候返回的窗口大小就是1M;
**窗口越大,网络吞吐量越大。**我们要在网络不拥塞的情况下,尽量提高效率。但是我们也不是每个包都会延迟,一般是隔几个包,或者隔一段时间延迟一个包。
捎带应答
当收到对方的数据时,我们往往会回复对方。此时给对方数据的ACK就可以搭顺风车,跟随我们的回复数据一起交付给对方。
tcp黏包问题
- A使用tcp协议发送给B好几个数据包,由于tcp是面向字节流的,那么B在交付上层的时候,有没有可能一次交付半个包,或者一次交付1个半呢?这样就导致数据包的不完整。我们的应用层协议应该具有主动将每个数据包分离的作用,例如http就有Content-Length:size键值对。
tcp异常情况
- A和B建立了tcp连接。
- A跟B正在通信,A主机的基于tcp进程挂了。由于文件生命周期随进程,所以socket会被关闭。此时底层的tcp连接就会发送FIN,跟正常关闭连接一样。
- 机器重启: 和进程终止的情况相同(在重启前,电脑会提示还有正在运行的进程是否确认关闭,也就是说重启前会先关闭进程).
- 机器掉电/网线断开: 接收端认为连接还在, 一旦接收端有写入操作, 接收端发现连接已经不在了, 就会进行reset.。即使没有写入操作, TCP自己也内置了一个保活定时器, 会定期询问对方是否还在。 如果对方不在, 也会把连接释放。另外, 应用层的某些协议, 也有一些这样的检测机制。例如HTTP长连接中, 也会定期检测对方的状态. 例如QQ, 在QQ 断线之后, 也会定期尝试重新连接。
Listen的第二个参数
- tcp需要建立连接,但是有可能网络资源已经满了。此时服务器无法立刻accept连接。那么就需要连接进行排队。而根据排队的连接状态,又可分为以下的2种:
- 全连接队列: 即服务器和客户端处于ESTABLISHED状态的连接。三次握手已经完成。
- 半连接队列: 即服务器和客户端处于SYN_SENT和SYN_RECV状态的连接,三次握手没有完成。
- 而Listen的第二个参数就是全连接队列的长度。即这些连接已经完成,但是无法立刻被accept。当全连接队列满了之后,新来的连接无法进入ESTABLISHED状态,只能进入半连接队列。
- 全连接队列的长度是Listen的第二个参数+1(因为在内核中使用的是大于号来判断是否满,而不是大于等于号)。
基于TCP的应用层协议
- HTTP
- HTTPS
- SSH
- Telnet
- FTP
- SMTP
(本章完)
|