C与Python之间的tcp通信

当一个程序需要用不同的语言执行时, 需要建立通信接口来传播数据/指令. 在此使用Socket建立端口, 可以通过TCP/UDP通信. 不同语言数据的转换使用Protocal Buffer, 来对传输内容进行encode/ decode. 当然,也有类似pip通信之类的方式.

1. Socket

关于Python Socket 的一个不错的介绍
关于C Socket的用法
比较详细的Linux socket介绍
另一个C Socket的讲解
G4G源码
WSAData

1.1 Python Socket

    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()

1.2 C++服务端/Python客户端

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()

2. Protocal buffer

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);

2.1 将对于输入输出文件转化成.proto形式,并编译

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;
}

2.2 将信息导入并生成序列 (encode)

以下函数可以用来检查是否生成成功

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);

2.3 将序列解码

  • cpp接收的时候, 注意得到接收到的长度
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;
    }

}
  • Python编码和解码就简单多了
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之间的数据通信。

你可能感兴趣的:(网络)