一、什么是粘包
TCP 是流式协议,数据向水流一样粘在一起,没有任何边界区分。如果接收数据没收干净,数据会有残留,就会和下一次的结果混淆在一起,就会出现粘包问题。粘包 指的是发送方发送的若干包数据到达接收方时粘成了一包,从接收缓冲区来看,后一包数据的头紧接着前一包数据的尾,出现粘包的原因是多方面的,可能是来自发送方,也可能是来自接收方。
TCP 默认使用 Nagle 算法(主要作用:减少网络中报文段的数量),而 Nagle 算法主要做两件事:①只有上一个分组得到确认,才会发送下一个分组;②收集多个小分组,在一个确认到来时一起发送;因此,Nagle 算法造成了发送方可能会出现粘包问题。
tcp_server.py 内容如下
from socket import socket
from socket import AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)
tcp_server = socket(AF_INET, SOCK_STREAM) # 创建服务器套接字
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 解决端口占用问题
tcp_server.bind(ADDRESS) # 套接字与地址绑定
tcp_server.listen(5) # 监听连接,最大挂起连接数为5
print("waiting for connection...")
connection, address = tcp_server.accept() # 接收客户端连接
print("...connection from: ", address)
data = connection.recv(1024) # 服务端接收消息,单次最大接收为1024个字节
print("收到客户端数据:%s" % data.decode("utf-8"))
connection.close() # 关闭连接
tcp_server.close() # 关闭服务器套接字
tcp_client.py 内容如下:
from socket import socket
from socket import AF_INET, SOCK_STREAM
HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)
tcp_client = socket(AF_INET, SOCK_STREAM) # 创建客户端套接字
tcp_client.connect(ADDRESS) # 尝试连接服务器
tcp_client.send("hello ".encode("utf-8")) # 客户端发送数据
tcp_client.send("world!".encode("utf-8")) # 客户端发送数据
tcp_client.close() # 关闭客户端套接字
二、TCP服务器
import subprocess
import struct
import json
from socket import socket
from socket import AF_INET, SOCK_STREAM, SOL_SOCKET, SO_REUSEADDR
HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)
tcp_server = socket(AF_INET, SOCK_STREAM) # 创建服务器套接字
tcp_server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 解决端口占用问题
tcp_server.bind(ADDRESS) # 套接字与地址绑定
tcp_server.listen(5) # 监听连接,最大挂起连接数为5
while True: # 监听连接
print("waiting for connection...")
connection, address = tcp_server.accept() # 接收客户端连接
print("...connection from: ", address)
while True: # 通信循环
try:
# 1、接收客户端发送的命令
cmd = connection.recv(1024) # 服务端接收命令,单次最大接收为1024个字节
# 在Linux系统中,一旦data收到空,意味着是一种异常的行为:客户端非法断开连接
if not cmd: # 适用于Linux系统
break
# 2、执行系统命令,拿到结果
obj = subprocess.Popen(cmd.decode("utf-8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout = obj.stdout.read() # 命令正确的返回结果
stderr = obj.stderr.read() # 命令错误的返回结果
# 3、把命令的结果返回给客户端
# 第一步:制作固定长度的包头
header_dict = {
"total_size": len(stdout) + len(stderr)
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode("utf-8")
# 第二步:先发送报头的长度
connection.send(struct.pack("i",len(header_bytes)))
# 第三步:把报头(固定长度)发送给客户端
connection.send(header_bytes)
# 第四步:在发送真实的数据
connection.send(stdout)
connection.send(stderr)
except ConnectionResetError: # 适用于Windows系统
break
connection.close() # 连接
tcp_server.close() # 关闭服务器套接字
三、TCP客户端
import struct
import json
from socket import socket
from socket import AF_INET, SOCK_STREAM
HOST = "127.0.0.1"
PORT = 8080
ADDRESS = (HOST, PORT)
tcp_client = socket(AF_INET, SOCK_STREAM) # 创建客户端套接字
tcp_client.connect(ADDRESS) # 尝试连接服务器
while True: # 通信循环
# 1、向服务器发送命令
data = input(">>>: ").strip()
if not data: # 按空格、回车等键结束连接
break
tcp_client.send(data.encode("utf-8")) # 客户端发送数据
# 2、拿到命令的结果,并打印
# 第一步:先接收报头的长度
obj = tcp_client.recv(4)
header_size = struct.unpack("i", obj)[0]
# 第二步:再接收报头
header_bytes = tcp_client.recv(header_size)
# 第三步:从报头中解析出对真实数据的描述信息(数据的长度)
header_json = header_bytes.decode("utf-8")
header_dict = json.loads(header_json)
total_size = header_dict["total_size"]
# 第四步:接收真实的数据
recv_size = 0
recv_data = b""
while recv_size < total_size:
data = tcp_client.recv(1024)
recv_data += data
recv_size += len(data)
# Windows默认的编码格式为gbk,Linux和MacOS默认的编码格式为utf-8
print(recv_data.decode("gbk"))
tcp_client.close() # 关闭客户端套接字
四、执行TCP服务器和客户端
如果先运行客户端,那么将无法进行任何连接,因为没有服务器等待接受请求。服务器可以视为一个被动伙伴,因为必须首先建立自己,然后被动的等待连接。另一方面,客户端是一个主动的合作伙伴,因为它主动发起一个连接。换句话说,首先启动服务器(在任何客户端试图连接之前)。
在开发中,创建这种 “友好的” 退出方式的一种方法就是,将服务器的 while 循环放在一个 try-except 语句中的 except 子句中,并监控 EOFError 或 KeyboardInterrupt 异常,这样你就可以在 except 或 finally 子句中关闭服务器的套接字。
标签:socket,04,tcp,粘包,问题,header,connection,server,客户端 From: https://www.cnblogs.com/kurome/p/17796171.html