服务端:
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#基于inet网络通信,基于TCP协议,stream代表TCP基于流式通信 买手机 phone.bind(('192.168.31.218',8000)) #ip地址加端口,唯一标识找到服务端程序 绑定手机卡,IP在哪程序就要在哪 phone.listen(5) #开机 代表有几个电话可hold着 print('等电话》》》') conn,addr=phone.accept() #等电话 ,拿到电话连接,拿到手机号 msg=conn.recv(1024) #收电话,收了1024字节的信息 print('客户端发来的消息是',msg) conn.send(msg.upper()) #发消息 conn.close() #挂电话 phone.close()#关机
客户端:
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) print('拨电话《《《') phone.connect(('192.168.31.218',8000))#播电话 客户端需要同服务端IP一致 phone.send('hello'.encode('utf-8'))#发消息 data=phone.recv((1024)) print('收到服务端的消息',data)
三次握手:1,2 客户端到服务端 双向连接 3 最后客户端同意服务端的请求,同意建立连接请求
四次挥手:1 客户端发关闭请求 2 服务端收到确认反馈 3 服务端传送最后的数据 4客户端收到最后的数据并反馈确认
前言
先说一下IP协议和TCP协议,IP协议是无连接的通信协议,IP不会占用两个设备之间通信的线路,IP实际上主要负责将每个数据包路由至目的地,但是IP协议并没有能够确保数据包是否到达,传过去的数据包是否按照顺序排列,所以IP数据包是不可靠的。而解决数据不可靠的问题就是由TCP协议来完成,接下来就介绍TCP协议,是如何让这些数据可靠的。
TCP概念
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,前面的博客有介绍,在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内另一个重要的传输协议。数据传输时,应用程序向TCP层发送数据流,TCP就会将接受到的数据流切分成报文段(会根据当前网络环境来调整报文段的大小),然后经过下面的层层传递,最终传递给目标节点的TCP层。为了防止丢包,TCP协议会在数据包上标有序号,对方收到则发送ACK确认,未收到则重传。这个步骤就是我们通常所说的TCP建立连接的三次握手。同时TCP会通过奇偶校验和的方式来校验数据传输过程中是否出现错误。下面来详细介绍三次握手的过程。
TCP报文中的6个标志位
先来简单介绍一下TCP报文
在这里主要介绍一下那6个标志位
URG:紧急指针标志,当为1时表示紧急指针有效,为0则忽略紧急指针
ACK:确认序号标志,为1表示确认号有效,为0表示报文中不含有确认信息,确认号无效
PSH:push标志,当为1时就是让接收方收到该TCP报文的时候不进入缓冲区排队而是快速发给应用程序
RST:重置连接标志,当连接出现错误时可以重置,或者用于拒绝非法的报文段和连接请求
SYN:同步序号,用于建立连接过程
FIN:finish标志,用于释放连接
TCP建立连接的三次握手
下面是示意图:
(注:seq代表序号,ack代表确认号)
第一次握手:当客户端需要去建立连接时,客户端就会发送SYN包(seq=x)到服务器,然后客户端进入SYN_SEND的状态,代表已经发SYN包过去,并且在等待服务器确认。此时ACK=0,SYN=1.,这时候由于才第一次握手,所以没有ACK标志
第二次握手:服务器收到SYN包,就会进行确认,由上面的标志位介绍我们可以知道SYN是表示同步序号,这时候会使得确认号=序号+1,即ack就会等于x+1,然后服务器也会像客户端发送一个SYN包(seq=y),这时候也就是服务器会发送SYN+ACK包,来表示服务器确认到了客户端的一次握手并且二次握手建立,此时服务器进入SYN_RECV状态。此时SYN=1,ACK=1,这时候由于是第二次握手,所以就会有一个服务器给客户端的确认标志。
第三次握手:客户端收到服务器的SYN+ACK包,然后就会像服务器发送确认包ACK(ack=k+1)和SYN(seq=x+1),等到这个包发送完毕之后客户端和服务器就会进入ESTABLISHED状态,完成三次握手,而后就可以在服务端和客户端之间传输数据。此时SYN标志位已经不需要,因为当我们发送ACK标志位的时候代表三次握手成功,已经建立完连接了,接下来可以传送数据过去了。
既然都有SYN包那为什么还要ACK来确认呢?SYN是同步序号,当 SYN=1 而ACK=0 时,表明这是一个连接请求报文。对方若同意建立连接,则应在响应报文中使 SYN=1 和 ACK=1。因此SYN置1就表示这是一个连接请求或连接接受报文。而ACK状态是用来确认是否同意连接。也就是传了 SYN,证明发送方到接收方的通道没有问题,但是接收方到发送方的通道还需要 ACK 信号来进行验证。
当TCP三次握手完之后,就代表连接已经建立完成,那么连接断开又是怎么样的呢?
TCP四次握手
当在传送完数据之后,客户端会和服务端之间有四次握手
第一次握手:客户端发送一个FIN和序号过去(seq=u),用来表示客户端和服务端之间有关闭的请求,同时关闭客户端到服务端的数据传送,客户端就进入FIN_WAIT_1的状态。
第二次握手:服务端收到FIN=1的标志位时,就会发送一个ACK标志位代表确认,然后确认序号就变成了收到的序号加1,即ack=u+1(FIN和SYN在这点上相同,但是作用不一样)这时候服务端进入CLOSE_WAIT状态,这是一个半关闭状态。只能服务端给客户端发送数据而客户端不能给服务端发送数据。
第三次握手:这次握手还是由服务端发起,这是服务端在传完最后的数据(没有就不传)就会发送一个FIN=1和ACK=1,且序号seq会改变(没有传数据则不变),而ack不变。这时候服务端就会进入LAST_ACK状态,表示最后确认一次。
第四次握手:客户端在接收到FIN之后,就会进入TIME_WAIT状态,接着就发送一个ACK和seq=u+1,ack=w+1给服务端,这时候服务端就会进入CLOSED状态。而客户端进入TIME_WAIT状态的时候必须要等待2MSL的时间才会关闭
为什么会有TIME_WAIT状态呢?(MSL:网络中数据报文存在的最大时间)
1、TIME_WAIT状态可以确保有足够的时间让对方接收到ACK包,如果ACK没有到达,在传输的过程丢失了或者一些其他原因,这样就可以让客户端重发ACK包。如果客户端直接关闭了,那么就有可能导致服务端在一些情况下没有接收到ACK包而无法与客户端断开连接。这样客户端发送ACK包到服务端,服务端请求重发,一来一回就正好是2MSL
2、保证迟来的TCP报文段有足够的时间被识别并丢弃,linux 中一个TCPport不能打开两次或两次以上。当client处于time_wait状态时我们将无法使用此port建立新连接,假设不存在time_wait状态,新连接可能会收到旧连接的数据。
服务器出现大量COLSE_WAIT状态的原因?这时候很可能是因为程序中在收到FIN=1的TCP报文后,由于忙于读或者写,没有去及时发送FIN回来,也就是无法及时关闭连接。这时候需要着重检查释放资源的代码和处理请求的线程配置,有可能是代码中有资源没有被释放掉,也有可能是线程池中的线程数不合理等等。
三次握手过程理解
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
四次挥手过程理解
1)客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN-WAIT-1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
2)服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE-WAIT状态持续的时间。
3)客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4)服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5)客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6)服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。
常见面试题
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。(因为数据发完了 才能发起断开请求,所以4次挥手。3次握手是因为没有数据的概念,只是建立一个连接通道,所以中间第二和 第三是合并的)
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
---------------------
作者:青柚_
来源:CSDN
原文:https://blog.csdn.net/qq_38950316/article/details/81087809
版权声明:本文为博主原创文章,转载请附上博文链接!