TCP三次握手和四次挥手
背景描述
通过OSI七层网络模型中IP层的介绍,我们知道网络层,可以实现两个主机之间的通信。其实并不然,因为真正的实体是在主机的进程,是一个主机中的一个进程与另一个主机的进程在交换数据。
IP协议只是把数据发送到目的主机,但是并没有交给主机的具体应用程序。而端到端的通信才是应用程序之间的通信。
UDP:在传送数据时不需要进行连接,远地的主机在收到UDP报文后也不需要进行确认,虽然UDP不提供数据的可靠交付,但是这样会节省好多资源,开销,使得它的速度比较快,比如一些实时性要求较高的服务,经常使用UDP。对应的应用层的协议主要有DNS,TFTP,DHCP,SNMP,NFS等。
TCP: 提供面向连接的服务,在传输数据之前,必须先建立连接,传输结束后释放连接。因此TCP是一种可靠的数据运输服务,但是这样就会增加不可避免的花销。对应的应用层的协议有,SMTP,TELNET,HTTP,FTP等。
常用的熟知端口号
应用程序 | 熟知端口 | 传输层协议 |
---|---|---|
FTP | 21,20 | TCP |
TFTP | 69 | UDP |
TELNET | 23 | TCP |
SMTP | 25 | TCP |
DNS | 53 | UDP |
HTTP | 80 | TCP |
SSH | 22 | TCP |
TCP概述
TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种端点我们叫做套接字(socket),它的定义为端口号拼接到IP地址即构成套接字,例如,若IP为192.3.4.26,而端口号为80,
那么得到的套接字为192.3.4.26:80
TCP连接的建立(三次握手)
- 最开始的时候,客户端和服务端都是处于CLOSED关闭状态。主动打开连接的为客户端,被动打开的连接是服务器。如果要进入来连接,会经过如下步骤:
- TCP服务器进程先创建传输控制块TCP,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
- 客户端先创建传输控制块TCP,然后向服务器发连接请求报文,报文里包括SYN报头;
- 服务端接收到客户端传入的报文,如果同意连接,就会发出确认报文,此时报文中包括SYN和ACK报头;
- 客户端接收到服务端传来的报文,如果确认连接,就会返回给ACK报头的报文给服务端,此时客户端进入连接状态;
- 服务端接收到客户端传入的确认连接报文后,也进去连接状态,这样双发就建立了连接,可以进行通信了。
TCP四次挥手
- 数据传输完毕后,双方就可以释放连接。最开始的时候,客户端和服务端都处于ESTABLISHED(连接)状态,然后客户端主动关闭,服务端被动关闭。
- 客户端进程发出连接释放报文,并且停止发送数据。报文包括FIN报头;
- 服务端接收到包括FIN报头的释放报文,发出确认报文,ACK报头,但是此时服务端可能还会传输数据给客户端;
- 客户端接受到服务器返回的包含ACK报头的报文后,等待把最后的数据传输完毕;
- 数据传输完毕后,服务端就会像客户端发送报文,包含FIN,ACK报头,等待客户端确认;
- 客户端接收到服务端发送的释放连接报文请求后,如果要释放连接,就会返回给服务端一个ACK报头的报文,客户端进入等待状态。此时客户端的连接还没有释放,必须经过2MSL的时间后,才会释放连接;
- 服务端接收到客户端发送的包含ACK报头的报文,直接释放连接,进入CLOSDED(关闭)状态。
如果已建立连接,客户端突然断开,会怎么办呢?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
基于TCP协议的套接字编程
什么是Socket
socket是应用层与TCP/IP协议通信的中间软件抽象层,他是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议隐藏在Socket接口后面。
套接字发展史及分类
套接字起源于 20 世纪 70 年代加利福尼亚大学伯克利分校版本的 Unix,即人们所说的 BSD Unix。 因此,有时人们也把套接字称为“伯克利套接字”或“BSD 套接字”。一开始,套接字被设计用在同 一台主机上多个应用程序之间的通讯。这也被称进程间通讯,或 IPC。
套接字有两种(或者称为有两个种族),分别是基于文件型的和基于网络型的。
基于文件类型的套接字家族
套接字家族名字:AF_UNIX
unix一切皆文件,基于文件的套接字调用就是底层的文件系统来取数据,两个套接字进程运行在同一个机器上,可以通过访问一个文件系统间接完成通信
基于网络类型的套接字家族
套接字家族名字:AF_INET
套接字工作流程
服务端:初始化Socket---->然后端口绑定(bind)---->对端口进行监听(listen)---->调用accept接受
客户端:初始化Socket---->连接服务器(connect)
如果客户端和服务端连接成功,则可以进行连接。客户端发送信息,服务端处理请求,然后再把数据传给客户端。
import socket
# socket_family:可以为AF_UNIX或AF_INET
# socket_type:可以是SOCK_STREAM或SOCK_DGRAM
# protocol:一般不填,默认为0
socket.socket(socket_family, socket_type, protocal=0)
# 获取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 获取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
服务端套接字函数
方法 | 用途 |
---|---|
bind() | 绑定(主机,端口号)到套接字 |
listen() | 开始TCP监听 |
accept() | 被动接受TCP客户的连接,(阻塞式)等待连接的到来 |
客户端套接字函数
方法 | 用途 |
---|---|
connect() | 主动初始化TCP服务器连接 |
connect_ex() | connect()函数的扩展版本,出错时返回出错码,而不是抛出异常 |
公共用途的套接字函数
方法 | 用途 |
---|---|
recv() | 接收TCP数据 |
send() | 发送TCP数据(send在待发送数据量大于己端缓存区剩余空间时,数据丢失,不会发完) |
sendall() | 发送完整的TCP数据(本质就是循环调用send,sendall在待发送数据量大于己端缓存区剩余空间时,数据不丢失,循环调用send直到发完) |
recvfrom() | 接收UDP数据 |
sendto() | 发送UDP数据 |
getpeername() | 连接到当前套接字的远端的地址 |
getsockname() | 当前套接字的地址 |
getsockopt() | 返回指定套接字的参数 |
setsockopt() | 设置指定套接字的参数 |
close() | 关闭套接字 |
面向锁的套接字函数
方法 | 用途 |
---|---|
setblocking() | 设置套接字的阻塞与非阻塞模式 |
settimeout() | 设置阻塞套接字操作的超时时间 |
gettimeout() | 得到阻塞套接字操作的超时时间 |
面向文件的套接字函数
方法 | 用途 |
---|---|
fileno() | 套接字的文件描述符 |
makefile() | 创建一个与该套接字相关的文件 |
基于TCP协议的套接字编程
服务端
# 服务端.py
import socket
# 1、初始化服务端
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 2、绑定端口
server.bind(('127.0.0.1', 8000))
# 3、建立监听
server.listen(5)
# 4、等待客户端连接
print('start...')
conn, client_addr = server.accept()
# 5、通信
data = conn.recv(1024) # 接受信息的最大字节数,默认为1024个字节
print(data.decode('utf-8')) # 数据传输的是二进制
conn.send(data.upper())
# 6、断开连接
conn.close()
# 7、关闭服务器
server.close()
客户端
# 客户端.py
from socket import *
# 1、初始化客户端
client = socket(AF_INET, SOCK_STREAM)
# 2、连接服务器
client.connect(('127.0.0.1', 8000))
# 3、通信
msg = input('请输入你想传输的信息>>>')
client.send(msg.encode('utf-8')) # 以二进制形式发送数据
data = client.recv(1024)
print(data)
# 4、关闭连接
client.close()