在互联网通信中,主要通过确定对方计算机的IP地址和对应开放的端口号来进行对接;IP地址用来标识网络上一台主机,端口号用来标识主机上联网的进程,IP地址和端口号的组合称为套接字(Socket);双方通过某种约定好的传输协议进行通信,可以理解为某种数据传输格式。本文主要介绍利用Python实现TCP协议通信
TCP协议通信
基本函数
- 双方导入套接字包:
import socket - 双方创建套接字对象:
s=socket(family, type),参数family=socket.AF_INET表示使用IPv4地址,family=socket.AF_INET6时表示使用IPv6地址;参数type=socket.SOCK_STREAM表示使用TCP协议,type=socket.SOCK_DGRAM表示使用UDP协议。套接字对象支持with关键字with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s - 服务器端绑定套接字:
s.bind(address),把套接字绑定到本地地址,address为服务器地址,形式为元组(host, port) - 服务器端监听套接字:
s.listen(backlog),声明自己为服务器端的监听套接字或被动套接字,开始监听并准备好接收客户端的连接请求,参数backlog表示能够同时连接的客户端数量,默认为1 - 客户端连接套接字:
s.connect(address),address为服务器端地址,形式为(host, port) - 服务器端接收套接字:
conn, addr=s.accept()接收一个客户端的连接,返回元组(conn, addr),其中conn是可以实际用于收发数据的新套接字,addr是对方套接字地址,形式为(host, port) - 发送字节串:
s.send(data):向已连接的远程套接字发送字节串data,返回实际发送的字节串长度s.sendto(data, address):向参数address指定的套接字发送字节串,返回实际发送的字节串长度,其中参数address的格式为(hostaddr, port)s.sendall(data):向已连接的远程套接字发送字节串data,自动重复调用send()方法,确保data全部发送完成
- 读取字节串:
recv(buffersize),从套接字中读取并返回最多buffersize个字节,如果对方已关闭并且已读取完缓冲区内所有数据,返回空字节串recvfrom(buffersize):从套接字接收并返回形式为(data, address)的元组,其中data为实际接收的完整报文的字节串数据,address为发送端的地址,格式为(hostaddr, port)recv_into(buffer, nbytes):从套接字读取最多nbytes个字节直接写入缓冲区,返回实际接收并写入缓冲区的字节串长度
- 关闭套接字:
s.close(),把输出缓冲区中剩余的数据传输完成之后,关闭套接字
JSON
说到字节串,常用方式是将所需传输对象转换成json格式
JSON 是一种轻量级的文本数据交换格式,指的是 JavaScript 对象表示法,通过导入json库进行使用
json.dumps(data).encode()将data的数据转换成json字符串再编码为二进制格式
json.loads(data.decode())将所得二进制数据解码成json字符串再转换为python对象
基本流程
服务器端(server)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # 创建套接字
s.bind((host, port)) # 绑定本地套接字(IP地址+端口号)
s.listen() # 开始监听客户端的连接请求
conn, addr = s.accept() # 监听到客户端请求时接受连接
with conn:
print('Connected by', addr) # 可以输出一下客户端的地址
while True: # 保持一直执行
json_data = json.dumps(data).encode() # 数据转码
data_len = len(json_data) # 计算长度
conn.sendall(str(data_len).zfill(10).encode()) # 先发送数据长度,便于对方判断接收
conn.sendall(json_data) # 发送数据
客户端(client)
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s: # 创建套接字
s.connect((host, port)) # 连接服务器套接字
while True: # 保持一直执行
length_data = s.recv(10) # 先接收代表数据长度的数据
if not length_data:
break
data_length = int(length_data.decode()) # 转码为整数,得到字符数
data = b'' # 准备保存数据
while len(data) < data_length: # 因为读取数据不一定能成功读取到所设字符数
received_data = s.recv(data_length - len(data)) # 所以设置循环直到全部读完
if not received_data:
break
data += received_data # 组装数据
data = json.loads(data.decode()) # 数据解码
其他函数
socket.inet_aton(string):把圆点分隔数字形式的IPv4地址字符串转换为二进制形式
socket.inet_ntoa(packed_ip):把32位二进制格式的IPv4地址转换为圆点分隔数字的字符串格式
socket.setblocking(flag):设置套接字为阻塞模式(flag=True)或非阻塞模式(flag=False)
socket.getblocking():查看套接字是否处于阻塞模式,对于非阻塞模式的套接字,调用accept()和recv()方法时如果没有数据会抛出异常。新创建的套接字默认为阻塞模式
socket.getpeername():返回套接字对象连接的另一端地址,可以用于获取远程套接字的IP地址和端口号
socket.getsockname():返回套接字对象的本地地址,可以用于获取系统随机分配给本地套接字的端口号
socket.gethostname():返回计算机名
socket.gethostbyname(host):根据计算机名获取并返回对应的IP地址
socket.gethostbyaddr(host):根据IP地址或计算机名,返回包含计算机名、别名和IP地址列表的元组