TCP协议简介

传输控制协议TCP,Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。

计算机之间的通信需要使用Socket套接字,Python标准库中已内置Socket模块。

TCP客户端和服务器通信流程如下:

对于TCP服务端建立:

  1. 创建TCP套接字
  2. 关联本地端口与插座
    • 绑定端口后客户端才能访问
  3. 设置套接字监听模式
    • 设置为被动模式,不能主动发起请求,只能被动等待连接
  4. 接受来自客户端的新连接
    • 三次握手建立连接
  5. 接收和发送数据
  6. 关闭客户机/服务器连接
  7. 返回到步骤4

对于TCP客户端建立:

  1. 创建TCP套接字
  2. 连接到TCP服务器
  3. 发送数据/接收数据
  4. 关闭连接
    • 发起关闭连接通知
    • 四次挥手后断开连接
TCP工作原理
TCP三次握手

搭建TCP客户端

接下来就来利用Python搭建一个简单的TCP客户端,实现局域网内通信。

流程如下:

  1. 创建客户端套接字对象 socket()
  2. 和服务器套接字建立连接 connect()
  3. 发送数据 send()
  4. 接受数据 recv()
  5. 关闭客户端套接字 close()

至于TCP协议和Socket的实现原理可自行查阅相关资料。

首先,导入Socket模块

import socket

创建套接字对象

  • 使用socket.socket(AddressFamily, Type)返回一个socket对象
    • AddressFamily: 表示IP地址类型,分为IPv4IPv6
      • socket.AF_INET: IPv4
    • Type: 表示传输协议类型
      • SOCK_STREAM: TCP传输协议
      • SOCK_DGRAM: UDP传输协议
  • 默认参数即为socket.AF_INET, socket.SOCK_STREAM
tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

和服务端建立连接

  • connet()方法仅接受一个元组参数,元组中包含IP端口(服务端的IP和端口)
tcp_client_socket.connet(('192.168.26.70', 7777))
print('连接成功')
# 代码执行到此表示连接建立成功

准备发送的数据

  • 服务端为Windows用gbk,Mac和Linux为utf-8
send_data = '你好服务端,我是客户端'.encode('utf-8')

发送数据

  • 发送和接受数据都是以二进制字节流的形式进行
tcp_client_socket.send(send_data)

接收数据

  • recv(bytes) 方法接收服务端发来的数据,返回二进制数据
  • bytes参数指定接收的数据最大字节数为1024
recv_data = tcp_client_socket.recv(1024)

对数据进行解码,由于数据传输形式是二进制,需解码才能正常显示

recv_content = recv_data.decode('utf-8')
print(recv_content)

断开连接

tcp_client_socket.close()

以下是完整代码

'''
1. 创建客户端套接字对象 socket()
2. 和服务器套接字建立连接 connect()
3. 发送数据 send()
4. 接受数据 recv()
5. 关闭客户端套接字 close()
'''

import socket
# 导入socket模块

if __name__ == '__main__':
    # 创建tcp客户端套接字
    # 1. AF_INET: 表示IPv4
    # 2. SOCK_STREAM: TCP传输协议
    # 3. SOCK_DGRAM: UDP传输协议
    # 默认参数 socket.AF_INET, socket.SOCK_STREAM
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 和服务端应用程序建立连接
    tcp_client_socket.connect(('192.168.26.70', 7777))
    print('连接成功')
    # 代码执行到此,说明连接建立成功

    # 准备发送的数据
    # 服务端为Windows用gbk,Mac和Linux为utf-8
    send_data = '你好服务端,我是客户端'.encode('utf-8')

    # 发送数据
    tcp_client_socket.send(send_data)

    # 接受数据,指定接受的数据最大字节数是1024,最大4096
    recv_data = tcp_client_socket.recv(1024)

    # 返回的直接是服务端程序发送的二进制数据
    # print(recv_data)

    # 对数据进行解码
    recv_content = recv_data.decode('utf-8')
    print(recv_content)

    # 断开连接
    tcp_client_socket.close()

搭建TCP服务端

搭建完客户端,来搭建一个客户端尝试连接

  1. 创建服务端套接字对象 socket()
  2. 绑定端口号 bind()
  3. 设置监听 listen()
  4. 等待接受客户端的连接请求 accept()
  5. 接收数据 recv()
  6. 发送数据 send()
  7. 关闭套接字 close()

首先导入socket模块

import socket

创建tcp服务端套接字

tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

绑定ip和端口

  • 如果在绑定IP时,没有给定IP,默认绑定的是本地IP地址
  • bind()方法仅接受一个元组参数,元组中包含IP端口(服务端的IP和端口)
tcp_server_socket.bind(('', 7777))

设置监听

  • 使用listen()方法设置监听
  • 设置监听后,服务端进入被动模式,只能被动连接,连接前会一直等待
tcp_server_socket.listen(128)
print('监听中···')

等待客户端连接

接受连接

  • accpet()会返回两个数据,一个是客户端的Socket对象,另一个是客户端的地址信息
client_socket, ip_port = tcp_server_socket.accept()
print(f'客户端{ip_port[0]} 使用端口{ip_port[1]} 连接成功')

接受客户端的数据

  • 如果接收的数据长度为0说明客户端发送了断开连接请求
data_recv = client_socket.recv(1024).decode('utf-8')
if len(data_recv):
    print(f'客户端 {ip_port[0]} 发送的数据是 {data_recv}')
else:
    print(f'客户端{ip_port[0]} 断开连接')

给客户端发送数据

data_send = '已收到'.encode('utf-8')
client_socket.send(data_send)

关闭客户端

client_socket.close()

关闭服务端

tcp_server_socket.close()

完整代码如下

'''
1. 创建服务端套接字对象 socket()
2. 绑定端口号 bind()
3. 设置监听 listen()
4. 等待接受客户端的连接请求 accept()
5. 接收数据 recv()
6. 发送数据 send()
7. 关闭套接字 close()
'''


import socket


if __name__ == '__main__':
    # 创建tcp服务端套接字
    # 如果马上重启服务器时会出现错误,因为地址和端口没有被释放
    # OSError: [Errno 48] Address already in use
    # 如果想马上释放,要设置socket选项
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    
    # 设置socket选项
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True)
    
    # 绑定IP和端口
    # 如果在绑定ip时,没有给定ip,默认绑定的时本地ip地址
    tcp_server_socket.bind(('', 7777))
    
    # 设置监听
    # 设置监听后服务端进入被动模式,只能被动接受连接
    tcp_server_socket.listen(128)
    print('监听中···')
    
    # 等待客户端连接
    # 如果客户连接上来后,该函数会返回两个数据,一个是客户端的Socket对象,另一个是客户端的地址信息
    client_socket, ip_port = tcp_server_socket.accept()
    print(f'客户端{ip_port[0]} 使用端口{ip_port[1]} 连接成功')

    # 接受客户端的数据
    data_recv = client_socket.recv(1024).decode('utf-8')
    if len(data_recv):
        print(f'客户端 {ip_port[0]} 发送的数据是 {data_recv}')
    else:
        print(f'客户端{ip_port[0]} 断开连接')
    
    # 给客户端发送数据
    data_send = '已收到'.encode('utf-8')
    client_socket.send(data_send)
    
    # 关闭客户端
    client_socket.close()
    
    # 关闭服务端
    tcp_server_socket.close()
    
    # 设置端口号复用,让程序退出端口号立即释放