最近要实现两个机器之间基于 TCP 的 socket 通讯(个人使用 Python 实现),尝试了官方的 demo 代码后总是被拒绝连接,仔细研究了一下并成功建立两台局域网内机器之间的通讯,通过两次请求的方式解决了粘包问题。
在 Python 语言中要实现 socket 通讯需要使用内置的 socket 包来实现,具体使用可以参考官方文档。
一般,通过下述代码建立一个 socket 通讯,其中第一个参数表示使用 ipv4 地址,第二个参数表示使用 TCP 协议族,该段调用会创建一个 Socket 对象,后续的绑定、连接监听都是基于这个 Socket 对象的。
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
首先,需要在服务端开启通讯监听,等待连接,这里为了演示方便,服务端返回的信息固定一个字符串。
import socket
import TCP_utils
# socket.AF_INET使用ipv4协议族
# socket.SOCK_STREAM使用tcp通讯
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定端口和地址
addr = ("172.18.129.245", 8990) # 改为自己的服务器ip或者本地环回
sock.bind(addr)
# 监听接入访问的socket
sock.listen(10)
while True:
print("server is waiting")
# 接收消息
conn, addr = sock.accept() # 接受客户端连接
data_head = TCP_utils.parse_header(conn)
data_len = data_head['data_len']
data = conn.recv(data_len)
print("服务端收到消息:{}".format(data.decode("utf-8")))
# 发送反馈
msg = 'finish connect'
head_len, head_bytes = TCP_utils.create_header('server', msg)
conn.send(head_len)
conn.send(head_bytes)
conn.sendall(msg.encode('utf-8'))
conn.close()
开启服务端的 socket 监听后就可以与其建立会话了,具体代码如下。
import socket
import TCP_utils
# 发送内容到服务器
while True:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
addr = ("192.168.43.27", 8990)
sock.connect(addr)
msg = input("发送消息:")
head_len, head_info = TCP_utils.create_header("client1", msg)
sock.send(head_len)
sock.send(head_info)
sock.sendall(msg.encode("utf-8"))
# 接受反馈
data_head = TCP_utils.parse_header(sock)
data_len = data_head['data_len']
data = sock.recv(data_len)
print('收到消息', data.decode("utf-8"))
# 关闭链接通路
sock.close()
该问题指的是是客户端和服务端进行数据传输时,接收的一方不知道消息之间的界限(即不能准确判断不一次性提取多少字节的数据)。该问题本质上是由于 TCP 协议本身的缺陷,为了提高效率,TCP 发送消息时必须等到发送方收集到足够多的消息,若每次信息都很少会合成一个 TCP 段进行发送。
该问题解决也不难,先发送一个报文头部信息来指明后面的数据的字节数,该头部信息包含了发送方 id 和数据段长度(字节数),接收方通过数据段长度接受指定字节数信息。为了控制发送的成功,将头部信息通过 struct 模块的打包方法压缩为固定 4 字节。
具体的解析和封装头部信息的代码封装为 TCP_utils 模块,代码如下。
import struct
import json
def create_header(id, data):
"""
客户端编号
:param id:
:param data:
:return:
"""
data_len = len(data)
head_info = {'id': id, 'data_length': data_len}
head_json = json.dumps(head_info)
head_bytes = head_json.encode('utf-8')
head_bytes_len = len(head_bytes)
head_length = struct.pack('i', head_bytes_len)
return head_length, head_bytes
def parse_header(conn):
"""
建立的连接
:param conn:
:return:
"""
head_info = conn.recv(4)
head_length = struct.unpack('i', head_info)[0] # 元组格式
data_head = conn.recv(head_length).decode('utf-8')
data_head = json.loads(data_head)
return data_head
开启服务端监听,随后,通过客户端建立一个连接 ,进行会话,演示如下图。