AFSim学习-自定义Processor开发6-通过Socket进行python强化学习交互示意
步骤
新建并编译
复制之前的文件,或者新建,名字为:
WsfSimpleSocketProcessor.cpp
WsfSimpleSocketProcessor.hpp
加到WsfProcessorTypes.cpp里:
#include "WsfSimpleSocketProcessor.hpp"
AddCoreType("MY_SIMPLE_SOCKET_PROCESSOR", ut::make_unique<WsfSimpleSocketProcessor>(aScenario));
重新编译:
rm -rf buil
mkdir -p buil
cmake -S src -B buil
cmake --build buil --target wsf_util -j10
cmake --build buil --target wsf -j12
cmake --build buil --target wsf_parser -j12
cmake --build buil --target mission -j12
python文件逻辑
python文件用于强化学习训练,主函数在python文件,通过他启动afsim的进程,进行训练。
强化学环境习有三个主要函数:init,reset,step。
init
由于是示例,非真正强化学习训练,所以这部分没有定义什么,如果是真正强化学习训练,需要定义状态空间和动作空间。
reset
重置环境,开始一个新的 episode。
reset 的流程:
1. 关闭旧 episode;
2. 启动 Python socket 服务端;
3. 启动 AFSIM 进程;
4. 等待 AFSIM 连接;
5. 等待 AFSIM 发来第一帧 OBS;
6. 返回初始 obs。
关闭旧 episode
close()函数。
1、先关闭与 AFSIM 的 socket 连接;
如果self.conn非空,说明连接还在,需要关闭。
if self.conn is not None:
try:
self.conn.close()
except Exception:
pass
self.conn = None
2、关闭 Python socket 服务端
判断self.server_sock
# 关闭 Python socket 服务端。
if self.server_sock is not None:
try:
self.server_sock.close()
except Exception:
pass
self.server_sock = None
3、关闭AFSIM 进程;
如果self.proc还在,则先终止进程,再杀死进程,稍作等待。
# 关闭 AFSIM 进程。
if self.proc is not None:
if self.proc.poll() is None:
self.proc.terminate()
try:
self.proc.wait(timeout=2.0)
except subprocess.TimeoutExpired:
self.proc.kill()
self.proc.wait()
self.proc = None
4、关闭日志
# 关闭日志文件。
if self.log_fp is not None:
try:
self.log_fp.close()
except Exception:
pass
self.log_fp = None
启动 Python socket 服务端;
_start_server()#
1、创建 TCP socket
AF_INET 表示 IPv4,SOCK_STREAM 表示 TCP
self.server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
2、设置端口复用
正常情况下,如果程序刚刚退出,或者异常中断,端口 6000 可能不会立刻释放。操作系统会让这个端口短时间处于一种等待状态,比如 TIME_WAIT。
setsockopt设置 socket 选项
socket.SO_REUSEADDR:允许复用本地地址和端口
self.server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
3、绑定 IP 和端口
self.server_sock.bind((HOST, PORT))
4、开始监听连接
self.server_sock.listen(1)
启动 AFSIM 进程;
def _start_afsim_process(self):
"""
启动 AFSIM 进程。
Python 通过 subprocess.Popen 启动 AFSIM。
这相当于在终端中执行:
cd /home/ubuntu/afsim_ws/afsim-src/afsim/swdev
./buil/mission -sm /home/ubuntu/afsim_ws/project1/13-rl-platform/two_platform_logger.txt
每个 episode 会单独生成一个日志文件。
"""
# 每个 episode 单独保存一个日志文件。
log_path = os.path.join(BASE_DIR, f"afsim_episode_{self.episode_id}.log")
# 打开日志文件。
# AFSIM 的标准输出和错误输出都会写到这里。
self.log_fp = open(log_path, "w", encoding="utf-8")
# 启动 AFSIM 进程。
self.proc = subprocess.Popen(
[AFSIM_EXE, "-sm", MISSION_FILE],
cwd=AFSIM_WORKDIR,# 指定 AFSIM 的运行目录。
stdout=self.log_fp,# 把 AFSIM 的标准输出写到日志文件。
stderr=subprocess.STDOUT,# 把错误输出也合并写到日志文件。
text=True# 文本模式。
)
print(f"[Python] started AFSIM episode {self.episode_id}")
等待 AFSIM 连接 Python。
def _accept_connection(self):
"""
等待 AFSIM 连接 Python socket 服务端。
AFSIM 侧启动后,应该主动连接:
127.0.0.1:6000
如果 30 秒内没有连接成功,就会抛出超时异常。
"""
# 设置 accept 等待超时时间。
self.server_sock.settimeout(30.0)
# 阻塞等待 AFSIM 连接。
# 成功后返回:
# self.conn:与 AFSIM 通信的 socket
# addr:AFSIM 客户端地址
self.conn, addr = self.server_sock.accept()
print(f"[Python] AFSIM connected from {addr}")
等待 AFSIM 发来第一帧 OBS;
def _wait_obs(self):
"""
等待并读取一帧新的 observation。
流程:
1. 从 socket 中读一行;
2. 解析成 obs 字典;
3. 更新 current_obs 和 current_step_id;
4. 返回 obs。
"""
# 从 socket 中读取一条消息。
line = self._read_line(timeout=30.0)
# 把消息解析成 observation。
obs = self._parse_obs(line)
self.current_obs = obs# 保存当前 observation。
self.current_step_id = obs["step_id"]# 保存当前 step_id。
return obs
step
执行一个动作,并等待 AFSIM 返回下一帧 observation。
step 的流程:
1. 检查 socket 是否已经建立;
2. 对 action 做限幅;
3. 发送 ACT 消息给 AFSIM;
4. 等待 AFSIM 推进一步后返回 OBS;
5. 计算 reward 和 done;
6. 返回 obs, reward, done, info。
检查 socket 是否已经建立
if self.conn is None:# 如果还没有 reset,就不能 step。
raise RuntimeError("env is not reset")
对 action 做限幅
heading_delta_deg = float(action)# 把 action 转成 float。
heading_delta_deg = clamp(# 限制动作范围,避免一次转向过大。
heading_delta_deg,
-MAX_HEADING_DELTA_DEG,
MAX_HEADING_DELTA_DEG
)
step_id = self.current_step_id# 当前动作对应的 step_id。
发送 ACT 消息给 AFSIM
self._send_line(f"ACT {step_id} {heading_delta_deg}")
def _send_line(self, line):
"""
向 AFSIM 发送一行文本。
当前协议要求每条消息以 \\n 结尾。
如果传入的 line 没有换行符,这里会自动补上。
"""
if not line.endswith("\n"):# 确保消息以换行符结尾。
line += "\n"
# sendall 会尽量把整条消息完整发送出去。
self.conn.sendall(line.encode("utf-8"))
等待 AFSIM 推进一步后返回 OBS
# 等待 AFSIM 推进一步后发下一帧 OBS
try:
obs = self._wait_obs()
except Exception as e:
# AFSIM 自然结束或 socket 关闭
return None, 0.0, True, {"reason": str(e)}
def _wait_obs(self):
"""
等待并读取一帧新的 observation。
流程:
1. 从 socket 中读一行;
2. 解析成 obs 字典;
3. 更新 current_obs 和 current_step_id;
4. 返回 obs。
"""
# 从 socket 中读取一条消息。
line = self._read_line(timeout=30.0)
# 把消息解析成 observation。
obs = self._parse_obs(line)
self.current_obs = obs# 保存当前 observation。
self.current_step_id = obs["step_id"]# 保存当前 step_id。
return obs
def _read_line(self, timeout=30.0):
"""
从 socket 中读取一行文本。
当前协议约定:
每条消息以换行符 \\n 结尾。
所以这里不断 recv(1),每次读取 1 个字节,
直到读到换行符为止。
返回值:
去掉换行符后的字符串。
"""
# 设置读取超时时间。
self.conn.settimeout(timeout)
# 用于保存接收到的字节。
chunks = []
while True:
# 每次读取 1 个字节。
ch = self.conn.recv(1)
# 如果 recv 返回空,说明对端 socket 已关闭。
if not ch:
raise RuntimeError("socket closed by AFSIM")
# 如果读到换行符,说明一条消息结束。
if ch == b"\n":
break
# 否则继续累积数据。
chunks.append(ch)
# 把字节拼接成完整字符串,并去掉两端空白。
return b"".join(chunks).decode("utf-8").strip()
计算 reward 和 done
自行计算
返回 obs, reward, done, info
自行计算
afsim文件逻辑
.hpp
原来的必须有的函数不变。
加上函数:
std::string mSocketHost; // Python server IP,默认 127.0.0.1
int mSocketPort; // Python server 端口,默认 5000
int mSocketFd; // socket 文件描述符
bool mSocketConnected; // 是否已连接 Python
double mMaxHeadingDeltaDeg; // 限制动作范围,例如 [-10, 10]
double mSocketTimeoutSec; // 等待 Python 动作超时时间
int mRlStepIndex; // 当前 RL 步号
// 连接 Python socket server
bool ConnectToPython();
// 发送一行字符串,自动带 \n
bool SendLineToPython(const std::string& aLine);
// 接收一行字符串,直到读到 \n
bool ReceiveLineFromPython(std::string& aLine, double aTimeoutSec);
// 发送 observation,接收 action
bool RequestActionFromPython(
int aStepId,
double aSimTime,
const RelativeState& aState,
double& aHeadingDeltaDeg);
.cpp
afsim的主要步骤还是:ProcessInput和Update
ProcessInput
读取txt需要的参数,和之前一样。
Update
1、执行父类Update。
2、获取所属平台
3、获取目标平台
4、获得运动的对象mover,转成 WsfWaypointMover
5、Socket RL:发送 observation,等待 Python 返回 action
6、控制飞机转向,waypointMover->TurnToHeading
7、记录和打印。
这里边,新内容是5。
Socket RL:发送 observation,等待 Python 返回 action
// ==========================================================
// Socket RL:发送 observation,等待 Python 返回 action
// ==========================================================
// 当前 RL 步号
int stepId = mRlStepIndex;
double headingDeltaDeg = 0.0;
bool gotAction = RequestActionFromPython(
stepId,
aSimTime,
state,
headingDeltaDeg);
if (!gotAction)
{
std::cout << "[RL] Timeout waiting action for step "
<< stepId
<< ", use headingDeltaDeg = 0"
<< std::endl;
headingDeltaDeg = 0.0;
}
RequestActionFromPython函数:
1、判断是否与python连接
2、构造 observation 消息,并发送。
3、接收python消息。
4、解析,简单校验
判断是否与python连接
if (!ConnectToPython())
{
std::cout << "[Socket] Cannot connect to Python, use action = 0."
<< std::endl;
return false;
}
// 连接 Python函数,AFSim / C++ 这一侧作为 socket 客户端,主动去连接 Python 启动的 socket 服务端
bool WsfSimpleSocketProcessor::ConnectToPython()
{
// ------------------------------------------------------------
// 1. 如果已经连接过 Python,就不重复连接
// ------------------------------------------------------------
// mSocketConnected 表示逻辑上是否已经连接
// mSocketFd >= 0 表示 socket 文件描述符有效
//
// 两个条件都满足,说明当前已经有可用连接,
// 直接返回 true 即可。
if (mSocketConnected && mSocketFd >= 0)
{
return true;
}
// 在 Linux 里,socket 本质上也被当成一种“文件描述符”。// ------------------------------------------------------------
// 2. 创建 socket
// ------------------------------------------------------------
// 在 Linux 里,socket 本质上也被当成一种“文件描述符”。
//
// AF_INET :使用 IPv4
// SOCK_STREAM :使用 TCP
// 0 :使用默认协议,对于 SOCK_STREAM 来说就是 TCP
//
// 创建成功后,返回一个非负整数,例如 3、4、5。
// 创建失败时,返回 -1。
mSocketFd = ::socket(AF_INET, SOCK_STREAM, 0);
if (mSocketFd < 0)// 如果 socket 创建失败,打印错误原因并返回 false。
{
std::cout << "[Socket] socket() failed: "
<< std::strerror(errno) << std::endl;
return false;
}
// ------------------------------------------------------------
// 3. 准备 Python 服务端地址结构
// ------------------------------------------------------------
// sockaddr_in 是 IPv4 地址结构体。
// 后面 connect() 需要用这个结构体指定:
// 连接哪个 IP、哪个端口。
sockaddr_in serverAddr;
// 把 serverAddr 整体清零,避免里面有随机脏数据。
std::memset(&serverAddr, 0, sizeof(serverAddr));
// 指定地址族为 IPv4。
serverAddr.sin_family = AF_INET;
// 设置 Python 服务端端口。
//
// mSocketPort 是普通整数,例如 6000。
// htons() 的作用是把主机字节序转换成网络字节序。
//
// 网络通信中,端口号必须使用网络字节序。
serverAddr.sin_port = htons(static_cast<uint16_t>(mSocketPort));
// ------------------------------------------------------------
// 4. 把字符串 IP 转换成网络地址格式
// ------------------------------------------------------------
// mSocketHost 一般是 "127.0.0.1"。
//
// inet_pton 的作用:
// 把字符串形式的 IP 地址转换成二进制网络地址。
//
// 例如:
// "127.0.0.1"
// 转成 serverAddr.sin_addr 里面需要的二进制格式。
//
// 参数含义:
// AF_INET :IPv4
// mSocketHost.c_str() :字符串 IP
// &serverAddr.sin_addr :转换后的结果存放位置
int ok = ::inet_pton(AF_INET, mSocketHost.c_str(), &serverAddr.sin_addr);
// inet_pton 返回值:
// 1 :转换成功
// 0 :IP 字符串格式不对
// -1 :系统错误
//
// 所以 ok <= 0 表示转换失败。
if (ok <= 0)
{
std::cout << "[Socket] invalid socket_host: "
<< mSocketHost << std::endl;
// 创建 socket 成功了,但 IP 转换失败,
// 所以这里要关闭 socket,避免文件描述符泄漏。
::close(mSocketFd);
mSocketFd = -1;
return false;
}
// ------------------------------------------------------------
// 5. 连接 Python 服务端
// ------------------------------------------------------------
// connect() 用于客户端主动连接服务端。
//
// 当前 C++ / AFSim 是客户端。
// Python 是服务端。
//
// connect 成功后,mSocketFd 就可以用来 send / recv。
//
// 参数说明:
// mSocketFd:
// 前面 socket() 创建出来的 socket 文件描述符。
//
// reinterpret_cast<sockaddr*>(&serverAddr):
// connect() 需要的是通用 sockaddr* 类型,
// 但我们这里使用的是 IPv4 专用 sockaddr_in,
// 所以需要强制转换。
//
// sizeof(serverAddr):
// 地址结构体大小。
if (::connect(mSocketFd,
reinterpret_cast<sockaddr*>(&serverAddr),
sizeof(serverAddr)) < 0)
{
// connect 返回负数,说明连接失败。
//
// 常见原因:
// 1. Python 服务端没有启动;
// 2. Python 监听端口和这里的 mSocketPort 不一致;
// 3. IP 地址写错;
// 4. 端口被防火墙或系统限制;
// 5. Python 服务端已经退出;
// 6. Python 服务端还没执行到 listen()。
std::cout << "[Socket] connect() failed to "
<< mSocketHost << ":"
<< mSocketPort
<< " error="
<< std::strerror(errno)
<< std::endl;
// 连接失败后,要关闭 socket。
// 否则这个 socket 文件描述符会一直占用系统资源。
::close(mSocketFd);
mSocketFd = -1;// 重置 socket 文件描述符。
return false;
}
// ------------------------------------------------------------
// 6. 连接成功,更新状态
// ------------------------------------------------------------
// 标记当前 socket 已经成功连接 Python。
mSocketConnected = true;
std::cout << "[Socket] Connected to Python server "
<< mSocketHost << ":"
<< mSocketPort
<< std::endl;
return true;
}
构造 observation 消息,并发送
std::ostringstream obs;
obs << "OBS "
<< aStepId << " "
<< aSimTime << " "
<< aState.range << " "
<< aState.relativeBearingDeg << " "
<< aState.elevationDeg << " "
<< aState.closingSpeed << " "
<< aState.courseDeg;
// ------------------------------------------------------------
// 4. 发送 OBS 给 Python
// ------------------------------------------------------------
// SendLineToPython() 会把字符串发送给 Python。
//
// 注意:
// SendLineToPython() 内部通常会自动补 '\n'。
// 因为 Python 侧是按行读取的。
//
// 实际发送出去大概是:
//
// OBS 12 60.0 180000.0 -3.2 0.0 450.0 90.0\n
if (!SendLineToPython(obs.str()))
{
std::cout << "[Socket] Failed to send OBS, use action = 0."
<< std::endl;
return false;
}
// 发送一行文本给 Python
// 参数:
// aLine:要发送的字符串,例如:
// "OBS 1 5.0 200000.0 -2.5 0.0 450.0 90.0"
//
// 返回值:
// true :发送成功
// false :发送失败
bool WsfSimpleSocketProcessor::SendLineToPython(const std::string& aLine)
{
// ------------------------------------------------------------
// 1. 检查 socket 是否已经连接
// ------------------------------------------------------------
// mSocketConnected 表示逻辑上是否已经连接 Python
// mSocketFd 是 socket 文件描述符
//
// 如果还没连接,或者 socket 文件描述符无效,
// 那么不能发送数据,直接返回 false。
if (!mSocketConnected || mSocketFd < 0)
{
return false;
}
// ------------------------------------------------------------
// 2. 准备要发送的消息
// ------------------------------------------------------------
// 复制一份字符串,避免直接修改传入参数 aLine。
std::string msg = aLine;
// 当前通信协议约定:
// 每条消息以换行符 '\n' 作为结束标志。
//
// Python 侧的 _read_line() 会一直读取 socket,
// 直到读到 '\n' 才认为一条消息结束。
//
// 所以这里要确保 msg 最后一定有 '\n'。
if (msg.empty() || msg[msg.size() - 1] != '\n')
{
msg += "\n";
}
// ------------------------------------------------------------
// 3. 获取底层字符数据指针
// ------------------------------------------------------------
// c_str() 返回 C 风格字符串指针。
// send() 是 C 系统调用,需要 const char* 类型的数据指针。
const char* data = msg.c_str();
size_t totalSent = 0;// totalSent 表示当前已经成功发送了多少字节。
size_t totalSize = msg.size();// totalSize 表示这条消息总共有多少字节需要发送。
// ------------------------------------------------------------
// 4. 循环发送,直到整条消息全部发完
// ------------------------------------------------------------
// 注意:
// send() 不保证一次就把所有数据发送完。
//
// 例如 msg 有 100 个字节,
// send() 可能一次只发送出去 40 个字节。
//
// 所以这里要用 while 循环,
// 不断发送剩余部分,直到 totalSent == totalSize。
while (totalSent < totalSize)
{
// 发送剩余数据。
//
// 参数说明:
// mSocketFd:
// 已经连接到 Python 的 socket 文件描述符。
//
// data + totalSent:
// 从尚未发送的位置开始发送。
//
// totalSize - totalSent:
// 剩余还没有发送的字节数。
//
// 0:
// 默认发送方式,不使用特殊 flag。
ssize_t n = ::send(
mSocketFd,
data + totalSent,
totalSize - totalSent,
0);
// --------------------------------------------------------
// 5. 判断 send 是否失败
// --------------------------------------------------------
// send 返回值:
// n > 0 :本次成功发送了 n 个字节
// n = 0 :一般表示连接异常或没有发送出数据
// n < 0 :发送失败
//
// 当前代码把 n <= 0 都认为是发送失败。
if (n <= 0)
{
std::cout << "[Socket] send() failed: "
<< std::strerror(errno)
<< std::endl;
// 标记 socket 已经不可用。
// 后续逻辑可以根据 mSocketConnected 判断是否需要重连。
mSocketConnected = false;
return false;
}
// 本次成功发送了 n 个字节,
// 更新已经发送的总字节数。
totalSent += static_cast<size_t>(n);
}
return true;
}
接收python消息
// ReceiveLineFromPython() 会等待 Python 发来一行数据。
//
// mSocketTimeoutSec 是超时时间。
//
// Python 应该返回类似:
//
// ACT 12 5.0
//
// 含义是:
// ACT 消息类型,表示 action
// 12 这个动作对应 step_id = 12
// 5.0 航向调整量 heading_delta_deg
if (!ReceiveLineFromPython(line, mSocketTimeoutSec))
{
std::cout << "[Socket] Failed to receive ACT, use action = 0."
<< std::endl;
return false;
}
// ------------------------------------------------------------
// 6. 解析 Python 返回的 ACT 消息
// ------------------------------------------------------------
// 约定 Python 返回格式:
//
// ACT step heading_delta_deg
//
// 例如:
//
// ACT 12 5.0
//
// 这里用 istringstream 按空格拆分字符串。
std::istringstream iss(line);
std::string tag;// 消息类型。正常情况下应该是 "ACT"。
int actionStepId = -1;// Python 返回动作对应的 step_id。
double actionValue = 0.0;// Python 返回的动作值。
// 从 line 中解析三个字段。
//
// 如果 line = "ACT 12 5.0"
//
// 解析后:
// tag = "ACT"
// actionStepId = 12
// actionValue = 5.0
iss >> tag >> actionStepId >> actionValue;
// 从 Python 接收一行文本
//
// 参数:
// aLine:输出参数,用来保存从 Python 收到的一整行内容
// 例如收到:ACT 12 5.0
//
// aTimeoutSec:接收超时时间,单位是秒
// 例如 30.0 表示最多等待 30 秒
//
// 返回值:
// true :成功收到一整行
// false :接收失败、超时、连接断开
bool WsfSimpleSocketProcessor::ReceiveLineFromPython(
std::string& aLine,
double aTimeoutSec)
{
// ------------------------------------------------------------
// 1. 清空输出字符串
// ------------------------------------------------------------
// 每次接收前,先把 aLine 清空。
// 避免残留上一次接收到的数据。
aLine.clear();
// ------------------------------------------------------------
// 2. 检查 socket 是否已经连接
// ------------------------------------------------------------
// mSocketConnected 表示逻辑上是否已经连接 Python。
// mSocketFd 是 socket 文件描述符。
//
// 如果还没连接,或者 socket 文件描述符无效,
// 就无法接收数据,直接返回 false。
if (!mSocketConnected || mSocketFd < 0)
{
return false;
}
// ------------------------------------------------------------
// 3. 循环接收字符,直到遇到换行符 '\n'
// ------------------------------------------------------------
// 当前通信协议规定:
// Python 发来的每条消息以 '\n' 结尾。
//
// 所以这里每次 recv 1 个字符,
// 一直读到 '\n',才认为一整行接收完成。
while (true)
{
// --------------------------------------------------------
// 4. 使用 select 等待 socket 可读
// --------------------------------------------------------
// fd_set 是 select 使用的文件描述符集合。
// 可以理解为:
// “我要监听哪些 socket 是否有数据可读”。
fd_set readSet;
FD_ZERO(&readSet);// 清空 readSet。
// 把当前 socket 文件描述符加入 readSet。
// 表示接下来要监听 mSocketFd 是否可读。
FD_SET(mSocketFd, &readSet);
// --------------------------------------------------------
// 5. 设置 select 超时时间
// --------------------------------------------------------
// timeval 是 select 使用的超时时间结构体。
timeval timeout;
// 秒部分。
// 例如 aTimeoutSec = 30.5,
// timeout.tv_sec = 30。
timeout.tv_sec = static_cast<int>(aTimeoutSec);
// 微秒部分。
// 例如 aTimeoutSec = 30.5,
// aTimeoutSec - timeout.tv_sec = 0.5,
// timeout.tv_usec = 500000。
timeout.tv_usec = static_cast<int>(
(aTimeoutSec - timeout.tv_sec) * 1000000.0);
// --------------------------------------------------------
// 6. 调用 select 等待数据到来
// --------------------------------------------------------
// select 的作用:
// 在真正 recv 之前,先判断 socket 是否有数据可读。
//
// 这样做的好处:
// 1. 不会一直卡死在 recv;
// 2. 可以设置超时时间;
// 3. 如果 Python 长时间不发动作,AFSim 可以检测到超时。
int ret = ::select(mSocketFd + 1, &readSet, nullptr, nullptr, &timeout);
// ret > 0 :有 socket 可读
// ret = 0 :超时,没有数据
// ret < 0 :select 调用失败
// --------------------------------------------------------
// 7. select 失败
// --------------------------------------------------------
if (ret < 0)
{
std::cout << "[Socket] select() failed: "
<< std::strerror(errno)
<< std::endl;
// select 失败后,认为连接不可用了。
mSocketConnected = false;
return false;
}
if (ret == 0)
{
std::cout << "[Socket] receive timeout" << std::endl;
return false;
}
// --------------------------------------------------------
// 9. socket 可读,开始 recv 一个字符
// --------------------------------------------------------
// 用来保存本次接收到的 1 个字符。
char ch = '\0';
// 从 socket 中读取 1 个字节。
//
// 参数说明:
// mSocketFd:
// 已连接的 socket 文件描述符。
//
// &ch:
// 接收到的数据存放到 ch 里。
//
// 1:
// 本次只读取 1 个字节。
//
// 0:
// 默认接收方式。
ssize_t n = ::recv(mSocketFd, &ch, 1, 0);
// --------------------------------------------------------
// 10. 判断 recv 是否失败或连接关闭
// --------------------------------------------------------
// recv 返回值:
// n > 0 :成功读取 n 个字节
// n = 0 :对端正常关闭连接
// n < 0 :读取失败
//
// 当前每次只读 1 个字节,
// 所以正常情况下 n 应该是 1。
if (n <= 0)
{
std::cout << "[Socket] recv() failed or connection closed."
<< std::endl;
mSocketConnected = false;
return false;
}
// --------------------------------------------------------
// 11. 如果读到换行符,说明一整行结束
// --------------------------------------------------------
// Python 发送时会在每条消息后面加 '\n'。
// 所以 C++ 读到 '\n' 时,就认为一条完整消息接收完成。
if (ch == '\n')
{
return true;
}
// --------------------------------------------------------
// 12. 普通字符加入 aLine
// --------------------------------------------------------
// 如果不是换行符,就把这个字符追加到 aLine 后面。
//
// 例如 Python 发送:
// ACT 12 5.0\n
//
// C++ 会依次收到:
// A
// C
// T
// 空格
// 1
// 2
// 空格
// 5
// .
// 0
//
// 最后 aLine 变成:
// "ACT 12 5.0"
aLine.push_back(ch);
}
}
编译运行
1、对应修改一下txt,
2、重新编译并运行。
更多推荐
所有评论(0)