一, TCP 简介
1,TCP 介绍
TCP 协议, 传输控制协议 (英语: Transmission Control Protocol, 缩写为 TCP) 是一种面向连接的, 可靠的, 基于字节流的传输层通信协议.
TCP 通信需要经过创建连接, 数据传送, 终止连接三个步骤.
TCP 通信模型中, 在通信开始之前, 一定要先建立相关的链接, 才能发送数据, 类似于生活中,"打电话".
2,TCP 面向连接
通信双方必须先建立连接才能进行数据的传输, 双方都必须为该连接分配必要的系统内核资源, 以管理连接的状态和连接上的传输.
双方间的数据传输都可以通过这一个连接进行.
完成数据交换后, 双方必须断开此连接, 以释放系统资源.
这种连接是一对一的, 因此 TCP 不适用于广播的应用程序, 基于广播的应用程序请使用 UDP 协议.
3,TCP 可靠传输
1)TCP 采用发送应答机制
TCP 发送的每个报文段都必须得到接收方的应答才认为这个 TCP 报文段传输成功
2)超时重传
发送端发出一个报文段之后就启动定时器, 如果在定时时间内没有收到应答就重新发送这个报文段. TCP 为了保证不发生丢包, 就给每个包一个序号, 同时序号也保证了传送到接收端实体的包的按序接收. 然后接收端实体对已成功收到的包发回一个相应的确认 (ACK); 如果发送端实体在合理的往返时延(RTT) 内未收到确认, 那么对应的数据包就被假设为已丢失将会被进行重传.
3)错误校验
TCP 用一个校验和函数来检验数据是否有错误; 在发送和接收时都要计算校验和.
4) 流量控制和阻塞管理
流量控制用来避免主机发送得过快而使接收方来不及完全收下.
4,TCP 与 UDP 的不同点
面向连接(确认有创建三方交握, 连接已创建才作传输.)
有序数据传输
重发丢失的数据包
舍弃重复的数据包
无差错的数据传输
阻塞 / 流量控制
二, TCP 数据包格式
所谓三次握手(Three-way Handshake), 是指建立一个 TCP 连接时, 需要客户端和服务器总共发送 3 个数据包.
那么我们就先来看一下 TCP 数据包的格式:
在 TCP 层, 有个 FLAGS 字段, 这个字段有以下几个标识: SYN, FIN, ACK, PSH, RST, URG.
URG - 为 1 表示高优先级数据包, 紧急指针字段有效.
ACK - 为 1 表示确认号字段有效
PSH - 为 1 表示是带有 PUSH 标志的数据, 指示接收方应该尽快将这个报文段交给应用层而不用等待缓冲区装满.
RST - 为 1 表示出现严重差错. 可能需要重现创建 TCP 连接. 还可以用于拒绝非法的报文段和拒绝连接请求.
SYN - 为 1 表示这是连接请求或是连接接受请求, 用于创建连接和使顺序号同步
FIN - 为 1 表示发送方没有数据要传输了, 要求释放连接,
Seq--- 序号, 这是为了连接以后传送数据用的,
Ack--- 确认号对收到的数据包的确认, 值是等待接收的数据包的序列号 + 1.
三, TCP 的三次握手
三次握手的目的是连接服务器指定端口, 建立 TCP 连接, 并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息. 在 socket 编程中, 客户端执行 connect()时. 将触发三次握手.
三次握手示意图
第一次握手:(Client 向 Server 发送联机请求)
SYN=1(Client 向 Server 发送联机请求)
Client 想要与 Server 进行 TCP 通信, 首先他需要向 Server 发送一个 SYN=1 的同步序列编号 (syncsynchronized squsequence number) 用来表示建立连接, 并且随机产生一个数 Seq number = X 的数据包到 Server,Server 由于 SYN=1 知道, Client 要求建立联机, 到这里第一次握手就结束了
第二次握手:(Server 向 Client 回复联机并确认联机信息)
- SYN=1(Server 接受 Client 的联机请求)
- ACK=1(确认信息)
这是对第一次握手信息的确认, 表示 Server 收到了 Client 的第一次握手信息
Ack=X+1(确认回复)
同时 Server 回复 Client 一个确认码 Ack 表示你的联机请求我已经收到, 而且数据没有丢失, 怎么验证数据没有丢失呢? 即 Ack 的值等于 Client 发过来 Seq 的值加 1, 即 Ack = X+1. 因为我都知道你发过来的 Seq 的值, 所以这个数据包没有丢失.
Seq = Y(第二次握手的数据包序列号)
Server 给 Client 的数据包序列号, 为了数据包在到达 Client 之后的验证, 所以这次从 Server 到 Client 的数据包中同样也会随机产生一个 Seq number = Y,
第三次握手
ACK=1(对第二次握手的确认)
首先 Client 会打开 Server 发送过来的 Ack 验证一下是否正确为 Seq+1, 即第一次发送的 seq number+1, 确认无误后, Client 仍然需要给 Server 再次回复确认即 ACK=1
Seq=Z(第三次握手的数据包序列号) Ack=Y+1
Client 告诉 Server, 你给我回复的信息我也收到了, 怎么确定我收到了你的信息呢? 就是通过 Ack 等于第二次握手传递过来的 Seq 值 + 1. 到此为止三次握手结束进入 ESTABLISHED 状态, 开始进行数据传输.
四, TCP 四次挥手
第一次挥手发送 FIN 请求, 第一次挥手结束.
第二次挥手开始, 被动方向主动方发送 ACK 确认码, 到这里第二次挥手结束.
第三次握手开始被动方向主动方发送 FIN 号结束.
第四次挥手开始主动方向被动方发送 ACK 确认, 等待 2MSL 后断开 TCP 连接.
五, TCP 的十种状态
这十种状态分别是三次握手和四次挥手中的状态, 在上面两个图中都给大家标记出来了, 这里再给大家一个简单的图表示
六, TCP 的 2MSL 问题
在四次挥手中我们提到了时间等待状态, 等待的时间是 2MSL.
2MSL 即两倍的 MSL,TCP 的 TIME_WAIT 状态也称为 2MSL 等待状态,
当 TCP 的一端发起主动关闭, 在发出最后一个 ACK 包后, 即第 3 次挥手完成后发送了第四次挥手的 ACK 包后就进入了 TIME_WAIT 状态, 必须在此状态上停留两倍的 MSL 时间, 等待 2MSL 时间主要目的是怕最后一个 ACK 包对方没收到, 那么对方在超时后将重发第三次挥手的 FIN 包, 主动关闭端接到重发的 FIN 包后可以再发一个 ACK 应答包. 在 TIME_WAIT 状态时两端的端口不能使用, 要等到 2MSL 时间结束才可继续使用. 当连接处于 2MSL 等待阶段时任何迟到的报文段都将被丢弃. 不过在实际应用中可以通过设置 SO_REUSEADDR 选项达到不必等待 2MSL 时间结束再使用此端口.
七, TCP 长连接和短连接
TCP 在真正的读写操作之前, server 与 client 之间必须建立一个连接,
当读写操作完成后, 双方不再需要这个连接时它们可以释放这个连接,
连接的建立通过三次握手, 释放则需要四次握手,
所以说每个连接的建立都是需要资源消耗和时间消耗的.
1. TCP 短连接
模拟一种 TCP 短连接的情况:
client 向 server 发起连接请求
server 接到请求, 双方建立连接
client 向 server 发送消息
server 回应 client
一次读写完成, 此时双方任何一个都可以发起 close 操作
在第 步骤 5 中, 一般都是 client 先发起 close 操作. 当然也不排除有特殊的情况. 从上面的描述看, 短连接一般只会在 client/server 间传递一次读写操作!
2. TCP 长连接
再模拟一种长连接的情况:
client 向 server 发起连接
server 接到请求, 双方建立连接
client 向 server 发送消息
server 回应 client
一次读写完成, 连接不关闭
后续读写操作...
长时间操作之后 client 发起关闭请求
3. TCP 长 / 短连接操作过程
(1)短连接的操作步骤是: 建立连接 -- 数据传输 -- 关闭连接... 建立连接 -- 数据传输 -- 关闭连接
(2) 长连接的操作步骤是: 建立连接 -- 数据传输...(保持连接)... 数据传输 -- 关闭连接
4. TCP 长 / 短连接的优点和缺点
长连接可以省去较多的 TCP 建立和关闭的操作, 减少浪费, 节约时间. 对于频繁请求资源的客户来说, 较适用长连接.
client 与 server 之间的连接如果一直不关闭的话, 会存在一个问题, 随着客户端连接越来越多, server 早晚有扛不住的时候, 这时候 server 端需要采取一些策略, 如关闭一些长时间没有读写事件发生的连接, 这样可以避免一些恶意连接导致 server 端服务受损; 如果条件再允许就可以以客户端机器为颗粒度, 限制每个客户端的最大长连接数, 这样可以完全避免某个蛋疼的客户端连累后端服务.
短连接对于服务器来说管理较为简单, 存在的连接都是有用的连接, 不需要额外的控制手段. 但如果客户请求频繁, 将在 TCP 的建立和关闭操作上浪费时间和带宽.
5. TCP 长 / 短连接的应用场景
长连接多用于操作频繁, 点对点的通讯, 而且连接数不能太多情况. 每个 TCP 连接都需要三次握手, 这需要时间, 如果每个操作都是先连接, 再操作的话那么处理速度会降低很多, 所以每个操作完后都不断开, 再次处理时直接发送数据包就 OK 了, 不用建立 TCP 连接.
例如: 数据库的连接用长连接, 如果用短连接频繁的通信会造成 socket 错误, 而且频繁的 socket 创建也是对资源的浪费.
而像 web 网站的 http 服务一般都用短链接, 因为长连接对于服务端来说会耗费一定的资源, 而像 Web 网站这么频繁的成千上万甚至上亿客户端的连接用短连接会更省一些资源, 如果用长连接, 而且同时有成千上万的用户, 如果每个用户都占用一个连接的话, 那可想而知吧. 所以并发量大, 但每个用户无需频繁操作情况下需用短连好.
八, TCP 的通信模型
tcp 通信模型中, 在通信开始之前, 一定要先建立相关的链接, 才能发送数据, 类似于生活中,"打电话"
生活中的电话机, 如果想让别人能更够打通咱们的电话获取相应服务的话, 需要做一下几件事情:
买个手机
插上手机卡
设计手机为正常接听状态(即能够响铃)
静静的等着别人拨打
tcp 服务器如同上面的电话机过程一样, 在程序中, 如果想要完成一个 tcp 服务器的功能, 需要的流程如下:
创建一个 socket 套接字
bind 绑定 ip 和 port
listen 使套接字变为可以被动链接
accept 等待客户端的链接
recv/send 接收发送数据
九, TCP 服务器代码实现
#coding = utf-8 from socket import * #1, 创建 socket 套接字 tcpServerSocket = socket(AF_INET,SOCK_STREAM) #2, 绑定本地信息 address = ("",7788) tcpServerSocket.bind(address) #3, 使用 socket 创建的套接字默认的属性是主动的, 使用 listen 将其变为被动, 这样就可以等着别人链接了 tcpServerSocket.listen(5) """ 如果有新的客户端来链接服务器, 那么就产生一个新的套接字专门为这个客户端服务器 newSocket 用来为这个客户端服务 tcpServerSocket 就可以省下来专门等待其他的客户端的链接 """ newSocket,clientAddress = tcpServerSocket.accept() #4, 接收对象发送过来的数据, 最大接收 1024 个字节 reveiveData = newSocket.recv(1024) print("接收的数据为:%s"%reveiveData.decode()) #5, 发送数据到客户端 newSocket.send("haha".encode()) #6, 关闭为这个客户端服务的套接字 newSocket.close() #7, 关闭监听套接字 tcpServerSocket.close()
运行流程
1,TCP 服务器
2, 网络调试助手:
十, TCP 客户端代码实现
所谓的服务器端: 就是提供服务的一方, 而客户端, 就是需要被服务的一方
tcp 的客户端要比服务器端简单很多, 如果说服务器端是需要自己买手机, 查手机卡, 设置铃声, 等待别人打电话流程的话, 那么客户端就只需要找一个电话亭, 拿起电话拨打即可, 流程要少很多
#coding = utf-8 from socket import * #1, 创建 socket tcpClientSocket = socket(AF_INET,SOCK_STREAM) #2, 链接服务器 serverAddress = ("192.168.100.106",7788) tcpClientSocket.connect(serverAddress) #3, 向服务器发送数据 tcpClientSocket.send("哈哈".encode("gb2312")) #4, 接收对方发送过来的数据, 最大接收 1024 个字节 receiveData = tcpClientSocket.recv(1024) print("接收到的数据为 %s"%receiveData.decode("gb2312")) #5, 关闭套接字 tcpClientSocket.close()
运行流程:
1,tcp 客户端
2, 网络调试助手:
来源: https://www.cnblogs.com/Se7eN-HOU/p/10903528.html