传输控制协议(TCP/IP)是互联网的重要组成部分。
互联网由一整套的协议组成,TCP只是其中的一层。
-
最底层的以太网协议(Ethernet)规定了电子信号如何组成数据包(packet),解决了子网内部的点对点通信。但是,以太网协议不能解决多个局域网如何互通,这由 IP 协议解决。
-
IP 协议定义了一套自己的地址规则,称为 IP 地址。它实现了路由功能,允许某个局域网的 A 主机,向另一个局域网的 B 主机发送消息。
IP 协议只是一个地址协议,并不保证数据包的完整。如果路由器丢包(比如缓存满了,新进来的数据包就会丢失),就需要发现丢了哪一个包,以及如何重新发送这个包。这就要依靠 TCP 协议。
-
简单说,TCP 协议的作用是,保证数据通信的完整性和可靠性,防止丢包。传输文档和文件的协议几乎都是使用TCP的。包括通过浏览器浏览网页、文件传输以及用与电子邮件传输的所有主要机制。
一、TCP工作原理
我们都知道UDP是不可靠的协议,所以UDP传输可能会出现诸如丢包、冗余、乱序等错误;而TCP传输,数据包就被隐藏在协议层之下了。应用程序只需要向目标机器发送流数据,TCP会将丢失的信息重传,保证信息能够成功到达目的机器。
TCP传输基本原理:
- 每个TCP数据包都有一个序列号,接收方通过该序列号将响应数据包正确排序。也可以通过该序列号发现传输序列中丢失的数据包,并请求重传。
- TCP并不使用顺序的整数(1,2,3…)作为数据包的序列号,而是通过一个计数器来记录发送的字节数。例如,如果一个包含1024字节的数据包的序列号为7200,那么下一个数据包的序列号就是8224。
- 在一个优秀的TCP实现中,初始序列号是随机选择的。
- TCP并不通过锁步的方式进行通信。相反,TCP无须等待响应就能一口气发送多个数据包。我们把在某一时刻发送方希望同时传输的数据量成为TCP窗口大小。
- 接收方的TCP实现可以通过控制发送方的窗口大小来减缓或暂停,叫做流量控制。
- 最后,若TCP认为数据包被丢弃了,他会假定网络正在变得拥挤,然后减少每秒发送的数据量。
- 注意一点:TCP四次挥手?其实也可以只发三次:FIN、FIN-ACK、ACK,这样会比较快速,四次就是每个方向上都发一对FIN、ACK。
二、TCP套接字
和UDP一样,TCP也使用端口号来区分同一IP地址上运行的不同应用程序。
上一节有说道,UDP其实也是可以调用connect()函数的,但是和TCP还是有些区别:
- TCP中调用connect会引起三次握手,client与server建立连结。
UDP中调用connect内核仅仅在OS内部把对端ip&port记录下来。 - UDP中可以多次调用 connect,TCP只能调用一次 connect。
- TCP的connect调用是有可能失败的。远程主机可能不作出应答,也可能拒绝连接,协议错误…原因就是TCP刘涉及两台主机间持续连接的建立。另一方的主机需要处于正在监听的状态,并做好接收连接请求的准备。
- TCP的S(服务器)端不进行connect()调用,而是接收C端connect()调用的初始SYN数据包。且在Python中S端在接受连接请求的同时还新建了一个套接字。
TCP接口实际上包含了两种套接字类型:“被动”监听套接字和主动“连接”套接字。
(1)被动套接字(监听套接字):服务器通过该套接字来接受连接请求,但不能用于发送或接收任何数据,也不表示任何实际的网络会话。而是由服务器指示被动套接字通知OS首先使用哪个特定的TCP端口号来接受连接请求。
由IP地址和正在监听的端口号唯一标识,因此任何其他程序都不能再使用相同的IP地址和端口。
(2)主动套接字(连接套接字):将一个特定的IP地址和端口号和某个与其进行远程会话的主机绑定,只用于与该特定主机进行通信,可以通过该套接字发送或接收数据。
唯一标识主动套接字的是一个四元组:
(local_ip, local_port, remote_ip, remote_port)
且多个主动套接字可以共享一个本地套接字名(IP&port) 。
三、一个简单的C/S程序
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter03/tcp_sixteen.py
# Simple TCP client and server that send and receive 16 octets
import argparse, socket
#保证一个循环内接收定长数据
def recvall(sock, length):
data = b''
while len(data) < length:
more = sock.recv(length - len(data))
if not more:
raise EOFError('was expecting %d bytes but only received'
' %d bytes before the socket closed'
% (length, len(data)))
data += more
return data
def server(interface, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #设置套接字选项
sock.bind((interface, port)) #绑定本地接口
sock.listen(1) #监听
print('Listening at', sock.getsockname())
while True:
print('Waiting to accept a new connection')
sc, sockname = sock.accept() #接受一个客户端的连接请求并返回一个新的套接字
print('We have accepted a connection from', sockname) #所连接的套接字名
print(' Socket name:', sc.getsockname()) #自己的套接字名
print(' Socket peer:', sc.getpeername()) #所连接的套接字名
message = recvall(sc, 16) #接收16字节的数据
print(' Incoming sixteen-octet message:', repr(message)) #将消息转为字符串
sc.sendall(b'Farewell, client') #发送客户端
sc.close() #关闭套接字
print(' Reply sent, socket closed')
def client(host, port):
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) #创建套接字
sock.connect((host, port)) #请求连接
print('Client has been assigned socket name', sock.getsockname()