TCP协议

TCP类似于“打电话”,是面向连接的,通信双方必须先建立连接才能进行数据的传输。完成数据交换后,双方必须断开此链接,以释放系统资源。

TCP通过以下方式来保证数据传输的可靠:

  1. 采用发送应答机制,TCP发送的每个报文段都必须得到接收方的应答才认为是传输成功
  2. 超时重传
  3. 发送端发送一个报文后就启动一个定时器,如果在定时时间段内没有收到应答就重新发送这个报文段
  4. TCP为了保证不丢包,就给每个包一个序号,同时序号也保证了传送到接收端实体的包的按序接收,然后接收端实体对已成功收到的包发挥一个相应的确认(ACK);如果发送端实体在合理的往返时延(RTT)内未搜到确认,对应的数据包就会被认为丢失,进行重传
  5. 错误校验,TCP用一个校验和函数来检验数据是否有错误,在发送和接收时都要计算校验和
  6. 流量控制和阻塞管理,流量控制用来避免主机发送的过快而接收方来不及收下

相关函数

- socket.connect()   链接服务器
- socket.send()      发送数据
- socket.recv()      接收数据
- socket.listen()    启动一个服务器用于接收连接
- socket.accept()    接受一个连接

socket.connect(address)

  • address: 地址

连接到指定地址的远程连接中。address的格式取决于选择的地址族的规定格式。

socket.send(bytes[,flags])

  • bytes: 数据; flags: ;

发送数据给套接字。返回发送数据的字节数。

socket.recv(bufsize[,flags])

  • bufsize: 一次接收的最大数据量; flags: ;

从套接字中接收数据。执行此函数后程序将堵塞直到接收到数据。返回的是一个字节对象,表示接收到的数据。

socket.listen([backlog])

  • backlog: 最大可连接数;

启动一个服务器用于接受连接。本质上是将套接字创建成服务端,只能被动接受连接而不能主动连接。

可选参数backlog指定允许连接的客户端数量,超出将拒绝新的连接。默认不指定,系统自动设置一个合理的值。

socket.accept()

接收一个连接,此socket必须绑定到一个地址上 (bind()) 并且监听连接 (listen())。返回一个二元元组 (conn, address)。其中 conn 是连接的客户端套接字对象,可在此连接上收发数据,address是连接的客户端套接字的地址及端口。

TCP注意点

  1. 服务端必须绑定地址和端口号,否则客户端无法找到服务器。
  2. 客户端不一定要绑定地址和端口号,如果没有绑定系统会自动分配一个空闲的端口号。
  3. 客户端想要向服务端发送数据,必须先连接connect,建立连接成功后再进行通信。

示例:一个同一时间仅能服务一个客户端的服务端

test.py    (客户端)

import socket

# 创建套接字
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 绑定自己的地址
tcp_client_socket.bind(('', 7778))
# 连接服务端
tcp_client_socket.connect(('127.0.0.1', 7777))

while True:
    data = input('请输入发送内容:')
    if data == '':
        break
    tcp_client_socket.send(data.encode('utf-8'))
    recv_data = tcp_client_socket.recv(1024)
    print('服务端返回的数据:' + recv_data.decode('utf-8'))

# 关闭客户端
tcp_client_socket.close()


test2.py    (服务端)

import socket

# 创建套接字
tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址
tcp_server_socket.bind(('', 7777))
# 将套接字创建为服务端
tcp_server_socket.listen()

print('等待客户端连接')
while True:
    # 等待客户端连接,如果连接则返回一个元组,第一个元素是客户端套接字,第二个元素是客户端地址
    client_socket, client_address = tcp_server_socket.accept()
    print('连接到客户端:' + str(client_address))
    while True:
        # 接收数据并回信
        client_data = client_socket.recv(1024)
        if not client_data:
            break
        print('收到数据:' + client_data.decode('utf-8'))
        client_socket.send('已收到数据'.encode('utf-8'))
    # 关闭客户端连接
    client_socket.close()
    print('断开连接,等待下一个客户端')

# 关闭服务端
tcp_server_socket.close()

下面是测试的结果gif:


在测试时发现一个小小的细节。就是在客户端结束运行,服务端继续运行,重新运行客户端会报错,报端口号已被占用。这时之前占用端口的程序已经结束运行了,讲道理说不应该会出现端口号被占用的情况。这时等上两分钟,什么都不操作,重新运行客户端就OK了。可能是端口结束占用要延迟一下。

其实客户端可以不用绑定地址,系统在连接服务端时会自动分配一个空闲的端口号给客户端,这样就不会出现以上的问题了。