一、背景
软件行业飞速迭代,如今微服务
架构已经成为构建大规模、复杂应用系统的主流选择。通过将大型复杂的应用程序拆分成一系列小型、独立的服务,每个服务都可以独立部署、运行和维护。每个服务专注于特定的业务功能,有自己的数据库、业务逻辑和接口。它们之间通过轻量级“通信机制”
相互协作,实现整个应用程序的功能。通过微服务架构,可以提高应用程序的可伸缩性、灵活性、可维护性和可测试性,同时降低开发和部署的复杂度。
虽然这种架构带来了高度的灵活性和可扩展性,但也引入了新的挑战,特别是在服务之间的通信方面。因此,对于我们测试人员来说,我们需要特别关注微服务之间的通信
。
二、常见的通信协议
1. 应用层协议
Http、Https、Socket、WebSocket、RPC
2. 传输层协议
TCP、UDP
3. 连接方式
- 短连接:
Http、TCP - 长连接:
Http(v1.1有长连接的实现方式)、Socket(TCP的套接字)、WebSocket、RPC - 无连接:
Socket(UDP的套接字)
三、通信协议底层传输的原理
以Socket为例
在 Socket 编程中,常见的两种类型是TCP Socket
和UDP Socket
。
- TCP Socket:TCP(传输控制协议)是一种面向连接的协议,它提供可靠的、有序的数据传输。TCP Socket 基于
TCP
协议,使用三次握手建立连接,确保数据的可靠性和顺序性。- UDP Socket:UDP(用户数据报协议)是一种无连接的协议,它提供了简单的数据传输服务,但不保证数据的可靠性和顺序性。UDP Socket 基于
UDP
协议,无需建立连接,适用于一些实时性要求高、允许一定数据丢失的应用场景。
下面是TCP/IP三次握手大致的流程图
在 TCP 协议中,建立连接通常需要进行三次握手
。而创建 Socket 对象通常是在握手之前的准备工作之一。用于准备建立连接所需的资源和信息。
创建一个套接字,收集一些计算机的资源,将一些资源绑定在套接字里面,以及接受和发送数据的函数等等,这些功能结合在一起构成了Socket的编程。
- Socket服务端的工作原理
创建Socket对象:服务端首先会创建一个
Socket对象
,用于监听客户端的连接请求。绑定地址和端口:服务端会将
Socket对象
绑定到一个特定的地址和端口上,以便客户端能够连接到该地址和端口。监听连接:服务端通过调用
Socket对象
的listen()
方法来开始监听客户端的连接请求。一旦调用了listen
方法,服务端就处于等待客户端连接的状态。接受连接:当客户端发起连接请求时,服务端会调用
accept()
方法来接受连接。在接受连接之后,服务端会创建一个新的Socket对象
,用于与该客户端进行通信。接收和处理数据:一旦连接建立成功,服务端就可以通过新创建的
Socket对象
来接收客户端发送的数据,并进行相应的处理。服务端会等待客户端发送数据,并在接收到数据后进行解析和处理。发送数据:服务端也可以通过
Socket对象
的send()
方法向客户端发送数据。这通常发生在服务端需要向客户端发送响应或其它信息时。关闭连接:通信结束后,服务端会调用
Socket对象
的close()
方法来关闭连接,并释放资源。
- Socket客户端的工作原理
创建Socket对象:客户端首先会创建一个
Socket 对象
,用于与服务端建立连接。连接服务器:客户端通过调用
Socket对象
的connect()
方法来连接服务端。在连接过程中,客户端会发送连接请求给服务端。发送数据:一旦连接建立成功,客户端就可以通过
Socket对象
的send()
方法向服务端发送数据。发送的数据通常包括请求信息、参数等。接收数据:客户端通过
Socket对象
的recv()
方法来接收服务端发送的响应数据。客户端会等待服务端的响应,并在接收到响应后进行相应的处理。关闭连接:通信结束后,客户端会调用
Socket对象
的close()
方法来关闭连接,并释放资源。
客户端和服务端的
Socket
工作原理都涉及创建Socket对象、建立连接、发送和接收数据以及关闭连接
等步骤,但具体的操作和流程略有不同。客户端主要负责发起连接和发送请求,而服务端主要负责监听连接、接受连接和处理请求。以下分别是服务端和客户端的Python示例
# 服务端
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # SOCK_STREAM TCP传输
server_address = ('127.0.0.1', 9527)
server_socket.bind(server_address) # 绑定IP和端口
server_socket.listen(1) # 开始监听并配置最大的连接数
while True:
channel, client_addr = server_socket.accept() # 获取客户端的连接通道
print(f'channel:{channel}')
print(f'client_addr:{client_addr}')
data = channel.recv(1024).decode('utf-8')
print(f'recv_data:{data}')
if data == '周杰伦':
msg = '七里香'
elif data == '蔡徐坤':
msg = '鸡你太美'
else:
msg = '未知歌曲'
channel.sendall(msg.encode('utf-8'))
channel.close()
# 客户端
import socket
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_addr = '127.0.0.1', 9527
client_addr = '127.0.0.1', 9528
# 绑定到客户端地址和端口
client_socket.bind(client_addr)
# 连接到服务端地址和端口
client_socket.connect(server_addr)
# 发送消息给服务端
client_socket.sendall('蔡徐坤'.encode('utf-8'))
# 接收服务端返回的数据,并将其从字节字符串解码为UTF-8文本字符串
data = client_socket.recv(1024).decode('utf-8')
# 输出接收到的数据
print(data)
四、需要关注哪些测试点
接口验证:测试Socket接口是否正确处理连接请求、数据接收和发送。包括连接的建立、维持和断开,以及消息的发送和接收。
数据传输准确性:验证数据从一个服务发送到另一个服务时,数据的格式、内容是否准确无误。需要测试正常情况下的数据传输,以及数据在处理过程中是否出现丢失、重复或篡改。
异步通信:验证服务是否能正确处理异步消息,包括消息的发送顺序和接收顺序是否一致。
网络延迟:测量从发送消息到接收到响应的时间间隔,确保系统在不同网络条件下都能保持较低的延迟。
吞吐量:测试系统在高并发连接和大数据量传输情况下的最大处理能力。
可靠性测试:模拟网络中断、服务重启等情况,测试服务能否自动恢复连接,数据是否能继续传输。需要验证重连的时机和数据一致性。
异常处理:测试在传输非法数据、意外断开连接等异常情况下,系统能否正确处理并保持稳定。如接收端如何处理异常包,发送端是否能正确检测并报告错误。
数据加密:验证Socket通信中的数据是否被加密传输,确保敏感数据不被窃取或篡改。测试包括对称加密和非对称加密的实现和效果。
鉴权:验证是否对连接请求进行身份验证,防止未授权的服务或用户访问。包括测试认证协议(如SSL/TLS)的实现。
五、小结一下
通过学习Socket通信的原理,尽可能多地模拟各种服务通信间的异常场景,能够较为全面地评估系统的稳定性和健壮性。通过这些场景进行测试,可以发现系统底层潜在的问题。提单给到开发人员进行修复,能够有效地增强错误处理逻辑以及改善系统的容错能力。
在项目前期针对微服务通信的测试方法不仅能够提升系统的稳定性和可靠性,还能为后续的优化和改进提供宝贵的数据支持,从而确保系统在实际生产环境中能够稳定、高效地运行,满足用户的需求。