当一个程序需要用不同的语言执行时, 需要建立通信接口来传播数据/指令. 在此使用Socket建立端口, 可以通过TCP/UDP通信. 不同语言数据的转换使用Protocal Buffer, 来对传输内容进行encode/ decode. 当然,也有类似pip通信之类的方式.
关于Python Socket 的一个不错的介绍
关于C Socket的用法
比较详细的Linux socket介绍
另一个C Socket的讲解
G4G源码
WSAData
import socket
def tcpServer():
host = "127.0.0.1"
port = 5000
s = socket.socket()
s.bind((host, port))
s.listen(1) # 只能同时连接一个
c, address = s.accept()
print("connection from ", str(address))
while True:
data = c.recv(1024) # 接收buffer的大小
if not data:
break
print("from connected user ", str(data))
data = str(data).upper()
print("sending data ", data)
c.send(data)
c.close()
def tcpClient():
host = "127.0.0.1"
port = 5000
s = socket.socket()
s.connect((host, port))
message = input("->")
while message != 'q':
s.send(message)
data = s.recv(1024)
print("Received from server "+ str(data))
message = input("->")
s.close()
def udpServer():
host = "127.0.0.1"
port = 5001
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.bind((host, port))
print("Server started")
whiel True:
data, addr = s.recvfrom(1024)
print("message from {}, is {}".format(addr), str(data))
s.sendto(data, addr)
s.close()
def udpClient():
host = "127.0.0.1"
port = 5001
server = (host, 5000)
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# http socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((host, port))
message = input("->")
while message != 'q':
s.sendto(message, server)
data, addr = s.recvfrom(1024)
print("received from server: {}, {}".format(str(data), addr))
s.close()
class Comm {
public:
WSADATA wsaData;
SOCKET sockServer;
SOCKADDR_IN addrServer;
SOCKET sockClient;
SOCKADDR_IN addrClient;
// socket, bind, listen
Comm(int port);
~Comm();
void _accept();
// 将环境发给Python
void _send(char* buf, int len);
// 从Python得到指令
char* _recv();
};
Comm::Comm(int port)
{
WSAStartup(MAKEWORD(2, 2), &wsaData);
// AR_INET ipv4
sockServer = socket(AF_INET, SOCK_STREAM, 0);
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY);//INADDR_ANY为通配地址其值为0
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(port);//端口
bind(sockServer, (SOCKADDR*)&addrServer, sizeof(SOCKADDR));//绑定
listen(sockServer, 5);//5为等待连接数目/连接队列的长度
printf("服务器已启动:\n监听中...\n");
}
Comm::~Comm()
{
::closesocket(sockServer);
}
void Comm::_accept()
{
//会阻塞进程,直到有客户端连接上来为止
int len = sizeof(SOCKADDR);
sockClient = accept(sockServer, (SOCKADDR*)&addrClient, &len);
}
void Comm::_send(char* buf, int len)
{
send(sockClient, buf, len, 0);
}
char* Comm::_recv()
{
//接收并打印客户端数据
char recvBuf[1024];//接受客户端返回的字符串
recv(sockClient, recvBuf, 100, 0);
return recvBuf;
}
Python 客户端
import socket
import time
def tcpClient():
host = "127.0.0.1"
port = 5000
s = socket.socket()
s.connect((host, port))
message = input("->").encode()
while message != 'q':
t = time.time()
s.send(message)
data = s.recv(1024)
print("RTT: ", time.time() - t)
print("Received from server " + str(data))
message = input("->").encode()
s.close()
if __name__ == "__main__":
tcpClient()
Protobuf是将各种语言的数据类型转化为serialization data, 以方便数据传输。
Python自带,C++需要自己安装
用法见Python官方介绍
我目前需要将仿真环境放在cpp中, AI控制放到python之中,因此,大概分为3步, 整体流程大概如下
AIIO::GameState gs = comm->_encode();
std::string out = gs.SerializeAsString();
comm->_send(out);
std::string r = comm->_recv();
comm->_decode(r);
proto文件1, 用于Cpp输出的编码
syntax = "proto2";
package AIIO;
message Player{
required int32 number = 1;
repeated float position = 2; // x, y, z, attention
}
message Obstacle{
required int32 number = 1;
repeated float position = 2; // x, y, z
}
message Self{
required int32 id = 1;
required int32 health = 2;
repeated float position = 3;
repeated float orientation = 4;
}
message Enviroment{
required Player players = 1;
required Obstacle obs = 2;
required Self my = 3;
}
message GameState{
required int32 totalPlayer = 1;
repeated Enviroment env = 2;
}
proto文件2, 用于python的编码
syntax = "proto2";
package AIEngine;
message Command{
message NextStep{
required int32 id = 1;
required int32 action1 = 2 [default = 0];
optional int32 action2 = 3;
}
repeated NextStep actions = 1;
}
以下函数可以用来检查是否生成成功
bool IsInitialized()
string DebugString()
如何解决嵌套的encode问题? 以之前的env为例
AIIO::GameState gs;
for (auto i=act.begin(); i != act.end(); i++) {
AIIO::Enviroment* cur_env = gs.add_env();
AIIO::Player *seen_plyer = cur_env->mutable_players();
AIIO::Obstacle *cur_obs = cur_env->mutable_obs();
AIIO::Self *cur_slf = cur_env->mutable_my();
seen_plyer->set_number(i->pmemory->opponents.size());
for (auto j = i->pmemory->opponents.begin(); j != i->pmemory->opponents.end(); j++) {
seen_plyer->add_position((*j).first->getGlobalPose().p.x);
seen_plyer->add_position((*j).first->getGlobalPose().p.y);
seen_plyer->add_position((*j).first->getGlobalPose().p.z);
seen_plyer->add_position((*j).second); // attention
}
cur_obs->set_number(i->pmemory->shelters.size());
for (auto j = i->pmemory->shelters.begin(); j != i->pmemory->shelters.end(); j++) {
cur_obs->add_position((*j)->getGlobalPose().p.x);
cur_obs->add_position((*j)->getGlobalPose().p.y);
cur_obs->add_position((*j)->getGlobalPose().p.z);
}
cur_slf->set_id(i->id);
cur_slf->set_health(i->pshoot->health);
std::string Comm::_recv()
{
char recvBuf[8096];
int length = recv(sockClient, recvBuf, 8096, 0);
std::string ret(recvBuf, length);
return ret;
}
void Comm::_decode(std::string r)
{
AIEngine::Command commands;
commands.ParseFromString(r);
for (int i = 0; i < commands.actions_size(); i++) {
AIEngine::Command_NextStep command = commands.actions(i);
std::cout << command.id() << std::endl;
std::cout << command.GetTypeName() << std::endl;
}
}
def _encode(rule):
command = command_pb2.Command()
for id, no in rule:
curStep = command.actions.add()
curStep.id = id
curStep.action1 = no
return command.SerializeToString()
def _decode(input_string):
GameState = env_pb2.GameState()
GameState.ParseFromString(input_string)
ENV = defaultdict(dict)
for env in GameState.env:
temp_dict = defaultdict(dict)
temp_dict["Player"] = env.players.position
temp_dict["Obstacle"] = env.obs.position
temp_dict["Self"] = {"id":env.my.id, "health":env.my.health,
"position":env.my.position,
"orientation":env.my.orientation}
ENV[temp_dict["Self"]["id"]] = temp_dict.copy()
return ENV
以上,就完成了cpp和python之间的数据通信。