从上一章开始,我们正式进入传输层的学习,该层两个重要的协议TCP协议和UDP协议。上一章我们主要学习了UDP,认识了其报头和各个字段。TCP协议因为要维持可靠性传输,所以要做很多工作,同时也很难理解,本章我们将重讲TCP协议。
整个网络协议栈就叫做TCP/IP协议栈,可想而知TCP/IP协议的重要性!!
整个网络协议栈叫做TCP/IP协议,用两个协议把整个网络协议栈命名了,但是可不止这两个协议,说明这两个协议非常重要。
TCP传输控制协议,主要解决网络通信过程中,可靠性的问题。
同时也帮我们]解决了效率的问题。
我们之前用TCP协议写过一个聊天程序,并以守护进程的方式布置在了服务器上: TCP的服务端(守护进程) + 客户端
应用层传输到传输层的时候(从上往下交付时),和UDP一样,也是要填写TCP报文的格式的。
报头长度:
16位源端口和目的端口:
4位首部长度:
"Data Offset"
字段,用于指示TCP报头的长度。1111(二进制)
也就是15(十进制)
。101(20Byte)
(固定的报头字段),是最长是1111(60Byte)
(报头选项字段大小不固定)。范围:5 ~ 15 < == > 101 ~ 1111
16位校验和:
6个保留位:
"RESERVED"
,并且必须设置为0以确保与未来的版本兼容。TCP协议如何将报头和有效载荷如何分离的问题(如何分用):
TCP报头是否关心数据(重点):
关于流的概念:
在平时生活中,假设有两个人隔着一扇门对话,那么我们是如何确认对方听到了我说的话呢?一定是我收到了回复!!
例如:A对B说:吃了吗?
此时,B回复A说:吃过了,那你呢?
A收到了之前对B提问的应答,就可以确认B收到了A的提问。
那么B如何确认自己的提问被A收到了呢?那么一定是B也收到了A的应答!!
我们发现在上述过程中,只有得到了对方的应答,才能确认对方是否收到我发出去的消息,并且始终是由一条消息是没有得到应答的!
同样的在网络通信中,我们如果收到了对方的应答,我们确认是没有丢包的,否则就是不确定!
如果发出去的报文有很多,那么如何确认这个确认应答是对哪个报文的确认呢?
序号字段和确认序号
。小试牛刀:
1,2,3,5,6,7
。解释:
1,2,3,5,6,7
这几个报文中,少了序号为4的报文,没有全都收到。TCP必须保证客户端到服务端的可靠性,也必须保证从服务端到客户端的可靠性!
在大部分情况下客户端和服务端在通信时,报头中序号填的是自己的,确认序号确认的是对方的,对于客户端和服务端都是如此。
没有百分之百的可靠协议,但是有局部百分之百的可靠协议,虽然最新的消息没有被应答,但是之前的消息可以保证被应答。
为什么tcp报文有两组序号?也就是说为什么既有序号也有确认序号呢?
客户端用自己的序号和服务端的确认序号构成从左向右的可靠性,服务端用自己的序号和客户端的确认序号构成从右向左的可靠性。
所以就可以用序号机制,保证双向方向上的全双工的确认应答机制,TCP通信的时候是全双工的!!
序号这个数字是怎么来的呢?
注意:
小结:
在长距离交互的时候,永远有一条最新的数据是没有应答的!发送方无法确定这条消息对方是否收到。但是,只要发送的消息有对应的应答,我们就认为我们发送的消息,对方是收到的!!
TCP是既有发送缓冲区,又有接收缓冲区的。
我们之前学的接口都是将数据从用户拷贝到内核,再从内核拷贝到用户。
数据什么时候发,发多少,出错了怎么办,要不要添加提高效率的策略都是由OS内的TCP自主决定的。不是应用层程序员决定的,是由操作系统内的自主决定的。
所以叫做传输控制协议!TCP对于数据发送是具有传输控制的权力和能力的!
那么凭什么呢?
如果有两台主机要通信,本质是两个TCP协议在互相通信:
read
或者recvfrom
接口会阻塞。那么问题来了,只要是缓冲区就会有大小,所以发送和接收缓冲区是有上限的,如何知道对方的接收缓冲区剩余空间大小呢?
如果发送报文,丟包了再重传,可以但是不可行,因为传输的路上消耗了大量的资源。
如果发送的太快了导致服务端来不及接受,怎么办?等一系列问题都需要TCP来解决。
所以只要是客户端知道了服务端的接收能力,客户端就可以根据服务端的接收能力,来动态的调整自己的发送策略。
双方基于两个缓冲区,基于得知对方接受能力的前提条件下,进行数据通信,就可以保证给对方发送消息时,对方也给我不断地应答,每次应答都更新对方窗口大小,根据窗口大小发送合适的数据大小!!
如果我们是第一次发消息,我怎么确定对方的接受能力呢?
选项字段的用途:
2^16 - 1
,也就是64KB
。发送和接收缓冲区:
1 ~ 1000
时拿最大下标充当报文的序号就发出去。ACK
时,收到的报文的确认序号是1001
,那么下次再去发就拿着数组的下标定位到1001
,就可以再发数据了。TCP通信的完整结构:
如果两个主机想通过tcp进行正常通信,必须先要进行三次握手。通常由客户端主动发起,发起第一次握手,客户端发起完毕之后,服务端进行响应,只有第三次成功之后,才完成三次握手。
客户端发送完毕了,双方要断开连接,要经历四次挥手。每次通信必须保证连接建立成功,然后才是正常通信,然后当我们在通信完毕再进行四次挥手,这才是TCP通信的完整的基本结构。
服务器的视角,它收到的TCP报文,有的是用来建立连接的,有的是正常的数据报文,有的是用来断开连接的,所以报文也是有类别的。
服务器可能在一瞬间内有多个服务器向它发送大量的报文,不同的报文有不同的诉求。
那么作为服务端,要不要区分收到的TCP报文属子哪个类别呢?答案是肯定的,必须得区分!!
不同的报文是有不同的类别的!!这也就是在回答为什么会有若千个标记位的原因。
还剩两个后面讲…
带有ACK的报文可能也会携带服务端给客户端发送的消息,在确认的同时也发数据:
对PSH标志位的理解:
如果有一天无法阻塞式的调用read接口,想要立即让上层开始读取数据,就可以携带PSH标记位,此时数据被服务端收到后放在接收缓冲区里,通知上层来读取。
假如服务端接收缓冲区满了,客户端再发消息时被服务端响应并且tcp报头中ACK是0,于是客户端就再给服务端发送tcp报头是就带上PSH标志位。
为什么TCP在正常通信时要有序号呢?
序号是确认应答的基石,但是序号不仅仅只有确认应答的功能。
1 ~ 10
一共10个报文,那么接收方接收到的数据就一定是按照1 ~ 10
接收到呢吗?(假设不丢包的情况下)URG紧急指针的全称是"Urgent Pointer"。
如何知道紧急数据在哪呢?
只要URG标志位为1,就代表了有效载荷里携带了紧急数据,由16为紧急指针以偏移量的方式找到数据,并且该数据只有一个字节。
什么时候用紧急指针呢?
当服务端接收到紧急指针后,也以紧急指针的方式返回给客户端(客户端服务端提前约定好紧急指针指向的数值的含义)。
因为每个数据都要有应答,当把数据发出去,如果在一段时间之内没有收到直接或间接的确认应答的话,对方就会认为要收到的报文就是丢了,至于是不是真的丢了就不管了。
丢了之后就重新发送,这就叫做 —— 超时重传机制。
潜台词:
B收到A发来的报文后给A的应答没有被A收到,和A发给B的报文丢了没有收到B的应答,在A的视角看来都是一样的,A都会认为发给B的报文丢了。
如果B给A的应答给丢了怎么办?
如何去重呢?TCP序号的第三个作用去重:
超时重传的时间是多少呢,也就是说超出多少时间才重传呢?
TCP为了保证无论在任何环境下都能比较高性能的通信,因此会动态计算这个最大超时时间:
2 * 500ms
后再进行重传。4 * 500ms
进行重传,逐渐递增。如果对方主机没有出异常,单纯的只是网络出问题了,服务端强制关闭了连接:
RST
,那么连接直接被重置了。SYN
之后,只要发出去,客户端的状态就叫做SYN_SENT
(同步发送)。SYN
,紧接着发送完SYN + ACK
之后,立马就变成了同步收到,状态就变成了SYN_ RCVD
(同步收到)。SYN + ACK
之后,再进行ACK
发给服务端,服务端此时叫做ESTABUSHED
(连接建立)。建立连接就是通过三次握手来完成的,所以对应的TCP连接维护,双方是基于状态变化的。
客户端并不是收到了服务端的
ACK
,状态才变成了SYN_SENT
,而是只要将SYN
发出去,状态就变成了SYN_SENT
。图中线是斜着的,说明数据包在流动的时候是需要花费时间的。
TCP面向连接的,如何理解连接呢?
虽然TCP是保证可靠性的,但是并不代表三次握手一定是成功的:
在我们熟悉了三次握手过程之后,不禁会有以下问题:
为什么要是三次握手?一次握手可以吗?两次握手可以吗?接下来我们来挨个分析一下。
一次握手可以吗?(有安全问题)
SYN
,双方连接就建立好了,就可以继续通信了。SYN
请求(或者构造假的SYN
请求),再加上一次握手就能成功建立连接。SYN
洪水,把服务端机器搞挂掉。两次握手可以吗?(有安全问题)
SYN
,服务端必须响应ACK
,一来回两次握手。ACK
,那么服务端也认为连接已经建立好了。SYN
,服务器收到之后,只要将ACK
报文给客户端发过去,服务端也认为连接建立好了。三次握手可以吗?(可以的)
ACK
才认为连接建立好了。只要服务端有了维护连接的结构体,客户端也必定有结构体维护!!
SYN
请求连接,服务端也会把这台机器拉下水。如果三次握手最后一次带有ACK报文丢了,会怎么样?
ACK
就认为连接建立好了。ACK
,不管服务端是否收到这个ACK
,客户端一定要维护连接!!同时三次握手也验证了客户端和服务端输入输出是否正常,俗称验证全双工。
更多次握手可以吗?(没意义)
三次握手时不仅仅是只进行三次握手,除此之外双方还有很多协商工作:
如果在TCP三次握手的最后一次握手中,服务器端的确认包(ACK) 丢了,客户端将不会收到服务器端的响应。在这种情况下,客户端会尝试重新发送第三次握手的请求包。
客户端会等待一个合理的时间来接收服务器端的确认包,这个时间通常由操作系统设定。如果超过了这个时间,客户端将认为连接建立失败,并终止连接请求。
当三次握手,最后一次握手,客户端发送完带有ACK
的报文就认为连接已经建立好,就开始向服务端发送消息。
ACK
,服务端就会立马给客户端进行ACK
(为0)响应,将响应报文的RST
标志位置为1。TCP中的RST全称为"Reset"。
RST
是告知客户端将连接进行重置,只要收到了TCP报文中带有RST
标志位设置为1,就代表要关闭连接,进行重新连接。
三次握手和四次挥手图用的是上面同一个图。
四次挥手(一个close对应两次挥手):
FIN
。服务端再响应返回ACK
,一来一回就是两次挥手。FIN
就代表要和对方断开连接,四次挥手分别由客户端和服务端各自要主动触发一次。FIN
,此时客户端就无法再向服务端发送正常数据了。ACK
报文可以发,但一般的数据就没法再发了。FIN
,客户端再ACK
,双方连接就断开了。为什么是四次挥手呢?
FIN
就够了。ACK
。三次挥手可以吗?(可以的)
FIN + ACK
。所有的FIN
的发送都是由上层调用了close
来触发的。无论是三次握手还是四次挥手,上层接口也就一个函数,而三次握手四次挥手是tcp协议自主自动完成的。
双方状态变化:
CLOSE_WAIT
状态是指在一个连接被关闭时,等待关闭的一方的状态,半关闭状态,说白了就是没关。CLOSE_WAIT
状态。CLOSE_WAIT
状态下,主机A不能再向主机B发送数据,但是仍然可以接收主机B发送的数据。ACK
后,没有继续发送数据或发送了FIN
(用于完全关闭连接)。CLOSED
状态,表示连接已经完全关闭。FIN
,表示它要关闭连接,那么主机B会进入LAST_ACK状态。LAST_ACK
状态下,主机B等待来自主机A的确认(ACK),确认收到FIN
。CLOSED
状态,表示连接已经完全关闭。TIME_WAIT
的状态,谁先断开谁就进入TIME_WAIT
。FIN
,一旦主机A收到了主机B的ACK
,它会进入TIME_WAIT
状态。TIME_WAIT
特点是发出去最后一个ACK
之后,理论上客户端可以断开连接了。CLOSED
状态。为什么要编码时要注意close:
CLOSE_WAIT
状态。accept
。close
,就处于CLOSE_WAIT
状态。TIME_WAIT
,就一定意味着,主动断开连接的一方就认为,自己把四次挥手的动作做完了。CLOSE_WAIT
这样的连接,大概率是没有调用close
,半关闭状态也消耗资源,服务器会变卡。TCP维护连接双方都要维护,而且TCP是全双工通信的,指的是客户端和服务端都是全双工的:
当四次挥手都结束了,为什么还停留在TIME_WAIT
呢?
ACK
是否被对方收到,我们是不确定的,如果这个ACK
丢了呢?ACK
发出去,它认为自己四次挥手完成了。ACK
,那么对于服务端来讲四次挥手就没有完成,就要持续一段时间的连接保持的情况。ACK
丢了,就必定要以异常情况关闭连接(这样不太好)。TIME_WAIT
变成CLOSED
最后关闭连接的状态。ACK
被对方收到。三次握手不是百分之百成功的,同样的四次挥手也不是百分之百成功的,所以最后一个
ACK
被对方收到没有,并不确定。当ACK
发出去了,无论有没有丢失,只要发送方等一会,也就相当于是一种间接的方式得到ACK
是否被对方收到。
如果在TIME_WAIT
这个特定时间内,如果没有收到来自对方的超时重传FIN
,那么就认为发送出去的ACK
已经被对方收到了。
MSL最大传送时间,TIME_ WAIT
等待的时间基本上就是二倍的MSL,至少要报文保证一来一回的时间。
也就至少能保证一个FIN
,一个ACK
,也就能保证从左向右或从右向左两个方向上的数据能够尽可能消散。
查看MSL的值:
TCP协议规定,主动关闭连接的一方要处于TIME_WAIT
状态,等待两个MSL(maximum segment lifetime)
的时间后才能到CLOSED
状态。
Ctrl+C
终止了server,所以server是主动关闭连接的一方。TIME_WAIT
期间不能再次监听同样的server端口。在TIME_WAIT
期间,如果服务端主动关闭了,重启的话是无法立即重启的,连接依旧存在,被绑定的ip和端口依旧被占用!!
Ctrl+C
了。FIN
。ACK
,完成前两次挥手,服务端一关闭,先进入TIME_WAIT
状态。比如一个公司的服务器崩了,它首先要做的最重要的事情不是要查出崩了的原因,而是要立刻将服务器重启。
查看TIME_WAIT状态:
我们看到在服务端Ctrl C
之后服务端还处于TIME_WAIT
的状态。
就这一个函数就能完成服务器崩溃时,就能立马重启的功能:
SOL_SOCKET
。SO_REUSEADDR
。使用setsockopt()
设置socket
描述符的选项SO_REUSEADDR
为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。
我们看到服务可以立刻重启:
accept不参与三次握手,即便是不调用accept,底层也会三次握手成功。
我们用之前写的TCP服务端演示一下:
将来服务器可能比较忙,一瞬间又来了成百上千个连接,服务端来不及accept
,底层的链接就要在操作系统的底层进行排队。
listen
的第二个参数,叫做底层的全连接队列的长度,用户传的值然后加,表示在不accept
的情况下,服务器最多能够维护多少个链接。
当listen第二个参数是2时,最多能维护三个连接:
listen
的第二个参数,叫做底层的全连接队列的长度,算法是:n + 1
,表示在不accept的情况下,最多能够维护多少个链接。
listen的第二个参数不要太大,根据不同的场景来设定。
为什么第二个参数不要太大呢?
A给B发送一个消息,那么B在收到一个报文之后要给A发送ACK
确认应答。整个数据发送的过程,是发一个消息给一个应答,是串行的。
ACK
再发第二个,不是这样的。既然一发一回的效率太低了,那么就可以一次给主机B塞满大量的报文,此时效率就提高了。以前是串行的,现在是并行的。
接收缓冲区的内容是从客户端拷贝下来的,再由TCP来决定什么时候发,怎么发,发多少,出错了怎么办之类的问题,所以TCP叫传输控制协议。
当A把数据发出去的时候,因为有可能会丢包,可能会再进行超时重传,所以A的发送中就不能直接将发出去的数据给清掉,得暂时保存在特定的内存区域当中,以支持超时重传。
那么这些临时被保存的数据被保存在哪里了呢?(以前接收窗口谈论的是接收缓冲区,现在谈的是发送缓冲区),所以就引入了 —— 滑动窗口!!
为了能够更好的支持,高性能的进行数据发送,一次批量化发送大量的数据。当数据发送不成功时,要支持各种超时重传,各种延迟应答和其他的一些后续策略。所以TCP有一种策略叫做 —— 滑动窗口。
在一大块发送缓冲区当中截取一段区域,来表征可以立即发送暂时不要应答的数据的容量。
操作系统内核为了维护这个滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答。只有确认应答过的数据,才能从缓冲区删掉,窗口越大,则网络的吞吐率就越高。
滑动窗口将发送缓冲区划分成了三个部分:
放在滑动窗口里的数据:
ACK
的,立马可以直接给对方发送的数据的集合就放在滑动窗口里。那么滑动窗口的大小由谁决定?
发送缓冲区:
发送缓冲区可以看做是一个大的数组,而滑动窗口可以理解为,该数组中的一段区域,两个整数(类比指针)维护起始位置和结束位置。
窗口滑动的过程本质就是:
TCP滑动窗口看起来是线性的其实是被设计成:其实是被设计成为环状结构的!!
学习滑动窗口我们不禁会有疑问,滑动窗口一定是向右滑动吗?滑动窗口可以变大吗?可以变小吗?
滑动窗口的大小由谁决定?是由对方的接受的能力决定!我收到的TCP数据报头中的窗口大小!!
假如滑动窗口的大小是4KB,对方的接收缓冲区当中剩余空间大小也是4KB:
ACK
为是0。ACK(为1)
确认左指针向右移动,收到一个ACK(为1)
确认左指针向右移动。所以滑动窗口不一定会向右滑动。
发送缓冲区的滑动窗口,衡量的是对方的接收能力的话,如果对方的接收能力越来越小,那么只有左侧的
start_index
不断地确认,而end_index
一直没有后移。
后续:
end_index
就向后加上8KB,直接就又扩展出来了一个滑动窗口。如果在通信的时候,如果发送方发送了4KB报文,而对方返回的窗口是16KB。那么此时的滑动窗口就变成了,可以变大,也可以变小。
滑动窗口减小:
start_index
向后移动,end_index
指针不动。滑动窗口变大:
正常向右滑动,指的是对方的接收能力比较稳定,发的时候,对方也一直在取。
滑动窗口不会跳过一些没有确认的报文然后向后滑动,因为确认序号规定了,没有办法跳过一些没有确认的序号就向后滑动。
如何理解发送缓冲区发送完毕数据?
滑动窗口的最大意义在于:
如果出现了丢包,如何进行重传?这里分两种情况讨论:
这种情况下,部分ACK
丢了并不要紧,因为可以通过后续的ACK
进行确认。
应答当中哪些ACK
丢失不影响,发送过去的报文丢失了也不影响,主要还是看响应报文的确认序号!有对应的超时重传机制,如果超时重传收不到ACK
,那么就是对方主机的问题了。
如第二个图所示:
1001 ~ 2000
报文丢失了。1001 ~ 2000
然后再进行ACK。3001 ~ 4000
,4001 ~ 5000
主机B都收到了。7001
,于是主机A就继续从7001 ~ 8000
进行发送。这种机制被称为 —— 高速重发控制(也叫快重传)。
然而,如果发送方连续多次重发同一个数据包(通常是3次),仍然没有收到确认,就会认为网络非常拥塞或者接收方已失去响应能力。在这种情况下,发送方将放弃高速重发控制机制,转而使用"超时重传"的方式。
如何进行流量控制:
要学TCP必学三大机制,分别是:滑动窗口,流量控制,拥塞控制。
发送方考虑了对方接受能力的问题,发送报文丢包的问题,对方乱序的问题,对方效率的问题,对方重复报文去重的问题。有了上述考虑,对方主机可以以很舒服的状态来接收数据了。
但是漏掉了一个非常重要的角色,发送数据给对方主机,但不是直接将数据发送给对方主机,而是现将数据交给了网络。
之前的所有策略都是在考虑对方主机的感受,那网络的感受谁来管呢?所以TCP不仅考虑了对方主机的问题,还考虑了中间路上的问题(网络的问题)。
假设发送方发送一千个报文,但是有九百多个报文都丢了,对方只收到了几个报文,那么此时肯定不是发送和接收端的问题(因为之前有那么多的机制来保证可靠性)。
那么此时就只能是路上的问题了,如果发送报文丢了一两个,网络是没有问题的,如果是丢了大量的报文,那就是要怀疑是网络的问题了。
如何确认网络出现问题,方法是什么呢?
此时TCP为了能够更好的进行这方面的处理,就引入了 —— 拥塞控制。
丢了大量的包,可以重传吗?答案是,不能!!
如果网络出问题的话,所有主机都能识别到,如果TCP协议规则设计成丢包无论如何都要超时重传,那么就会更加加重网络瘫痪程度,进而谁也发送不了。
正确做法:
TCP引入慢启动机制,先发少量的数据,探探路,摸清当前的网络拥堵状态,再决定按照多大的速度传输数据。
当网络拥塞了,其他所有的客户端主机都能识别得到,不是一个主机在进行上述过程,而是所有的主机都有共识,都在慢慢发。
网络一旦拥塞了,网络的入口流量瞬间减少,就给了网络以充足的时间来进行数据推送,数据路由,推送到指定的服务器,这样就会让网络缓一口气,网络就慢慢的缓过来了。这种做法叫做慢启动机制。
ACK
应答,像上面这样的拥塞窗口增长速度是指数级别的。此处插播一条滑动窗口的知识点:
响应报文的16位窗口
为主。拥塞窗口
的大小为数据量发送。所有的主机都要遵守这样的规则:
拥塞窗口没有体现在TCP报头当中,相当于TCP连接当中的属性字段。
拥塞窗口大小信息在TCP发送方的发送缓冲区中存储,通常包含于TCP协议栈内部的某个数据结构中。
这个窗口代表的含义是:这个窗口发送数据量以内不会拥塞,超过拥塞窗口可能会导致网络拥塞问题。
因为指数增长前期增长特别慢,所以叫做慢启动。但是随着重传次数增多发送的报文就会变得越来越多,势必导致下一次的网络拥塞。
难道网络是就是在不断地恢复和拥塞之中进行通信吗,那这个网络也太震荡了吧~
网络出现拥塞,最想做什么?尽快恢复!
为了不增长的那么快,因此不能使拥塞窗口单纯的加倍!!
拥塞控制算法:
指数增长前期慢,意味着前期都可以发送少量的数据,过了一个临界值,就会增长速度变快。我们要尽快恢复网络通信的正常速度,增长到一定程度,就让它正常的线性增长。
如果网络趋于稳定,拥塞窗口还要增大吗?
不考虑网络拥塞的情况下,一次能够向对方发送多少数据是由滑动窗决定的。如果滑动窗口越大,那么一次向对方发送的数据量也就越大,通信效率也就越高。
本质的说对方主机接收能力越强,发送效率就越高。网络整体的发送效率的问题,最根上就变成了,上层能不能尽快取走数据的问题。
延迟应答:
ACK
,等一会。窗口越大,吞吐量就越大,传输效率就越高,保证在网络不拥堵的情况下,尽快提高效率:
当然在发送数据时也要考虑网络拥塞的问题,无论有还是没有延迟应答,都要考虑网络拥塞的问题。
只不过有了延迟应答,在一定的概率上,就可以让我们在正常通信时,一次可以让对方可以向我要么不发,要发送就发送更大块的数据。
网络也是IO:
如果一次IO就能将大块的数据传过来,那么就是可以肉眼可见的效率提升。
延迟多久时间呢?
那么所有的包都可以延迟应答么?肯定也不是!
延迟应答策略:
(一般N取2)
。(超时时间取200ms)
。一般用的是数量的方式,更简单一些。
为了提高效率还有一种做法 —— 捎带应答。
ACK
的同时,该响应报文也可以携带其他数据。如果发送的字节数太长,会被拆分成多个TCP的数据包发出:
接收数据的时候,数据也是从网卡驱动程序到达内核的接收缓冲区:
那网卡收到了其他主机发来的数据,网卡是怎么让操作系统知道网卡里是有数据了呢?从而让操作系统把数据从网卡硬件通过网卡驱动程序拿到操作系统的TCP接收缓冲区呢?
TCP面向字节流该如何理解?
write
,调用一次或者调用100次向缓冲区里拷贝,这就叫做写入的时候与写入的格式没有关系。网络通信基于TCP通信时,发送怎么发(调用write函数几次),接收怎么收(调用read函数几次),二者毫无关系。你写你的我读我的,这就叫做面向字节流。
TCP不关心数据报文和报文之间的边界,但是应用层必须关心!!
读取报文的完整性: