Python <-> Communication interprocessus entre les langages C
En raison de diverses circonstances, j'ai décidé d'écrire un programme en langage C, mais je pensais qu'il serait très difficile d'écrire la partie interface dans le même C, j'ai donc créé l'interface en Python et connecté les programmes avec IPC, etc. J'y ai pensé. Au début, je pensais que ce serait bien d'emballer / décompresser la structure du langage C moi-même, mais comme mes prédécesseurs en ont fait certains, puis-je utiliser ** Protocol Buffer **? J'ai donc décidé de faire des recherches et de l'essayer.
Protocol Buffer(protobuf) La prise en charge officielle du tampon de protocole est la suivante: (Au 11/11/2018)
proto2
proto3 (+ ci-dessus)
proto2 et proto3 ne sont pas compatibles. Qiita a également un article qui explique en détail les grandes lignes de proto2, proto3, protobuf, etc., veuillez donc le lire.
「Proto2 vs Proto3」:https://qiita.com/ksato9700/items/0eb025b1e2521c1cab79
Générer un fichier proto
sample.proto
message DataStructs{
optional uint32 id = 1;
optional uint32 ip_address = 2;
optional uint32 port_num = 3;
}
message IpcMessage {
enum Errors {
SUCCESS = 200;
ERROR_BAD_REQUEST = 400;
ERROR_NOT_FOUND = 404;
ERROR_SERVER_ERROR = 500;
ERROR_SERVICE_UNAVAILABLE = 503;
}
// error_code fait référence aux erreurs ci-dessus.
optional Errors error_code = 1;
//Vous pouvez imbriquer des messages.
optional DataStructs data = 2;
}
Ce qui précède est la description de proto2. Je pense que même les gens qui le voient pour la première fois peuvent le comprendre d'une manière ou d'une autre.
C'est pratique car vous pouvez ajouter des commentaires dans le fichier .proto
.
proto3 n'est pas compatible en raison de la syntaxe différente.
protobuf-c
Donc, cette fois, je voulais exécuter protobuf en langage C. Il n'y a pas de support officiel, mais quand je le recherche, il semble qu'il y ait un projet tiers, donc je vais l'utiliser. protobuf-c : https://github.com/protobuf-c/protobuf-c
Il semble que protobuf-c ne supporte pas proto3, je vais donc l'essayer avec proto2 après cela.
Si vous pensez devoir compiler, il semble que les distributions modernes aient des packages. Il peut être installé à partir d'un référentiel standard sur CentOS et Ubuntu.
CentOS-7.5-yum
$ yum search protobuf-c | grep x86_64
protobuf-c.x86_64 : C bindings for Google's Protocol Buffers
protobuf-c-compiler.x86_64 : Protocol Buffers C compiler
protobuf-c-devel.x86_64 : Protocol Buffers C headers and libraries
...
Ubuntu-18.04-apt
$ apt-cache search protobuf-c | grep '(protobuf-c)'
libprotobuf-c1 - Protocol Buffers C shared library (protobuf-c)
libprotobuf-c-dev - Protocol Buffers C static library and headers (protobuf-c)
libprotobuf-c1-dbg - Protocol Buffers C shared library debug symbols (protobuf-c)
protobuf-c-compiler - Protocol Buffers C compiler (protobuf-c)
Cette fois, il sera réalisé dans l'environnement CentOS.
$ cat /etc/redhat-release && uname -r && rpm -aq | egrep '(protobuf)|(gcc)' && python -V
CentOS Linux release 7.5.1804 (Core)
3.10.0-862.14.4.el7.x86_64
gcc-4.8.5-28.el7_5.1.x86_64
protobuf-2.5.0-8.el7.x86_64
protobuf-c-devel-1.0.2-3.el7.x86_64
protobuf-c-1.0.2-3.el7.x86_64
protobuf-compiler-2.5.0-8.el7.x86_64
libgcc-4.8.5-28.el7_5.1.x86_64
protobuf-python-2.5.0-8.el7.x86_64
protobuf-c-compiler-1.0.2-3.el7.x86_64
Python 2.7.5
Compilez le fichier .proto
avec protoc-c
, qui est un compilateur protobuf pour protobuf-c, et générez .pb-ch '' et
.pb-cc ''. Je vais l'utiliser à partir de maintenant.
$ protoc-c sample.proto --c_out=./ && ls sample.*
sample.pb-c.c sample.pb-c.h sample.proto
Ceci est un exemple de lecture de protobuf en langage C
Avant d'écrire un programme pour désérialiser en langage C, préparez un script à sérialiser en sortie standard en Python.
Tout d'abord, créez un fichier Proto pour Python.
$ protoc sample.proto --python_out=. ; ls *.py
sample_pb2.py
Un script qui affiche le protobuf sérialisé python sur la sortie standard. Cette fois, nous parlons de protobuf-c, je vais donc omettre les détails.
serialize_sample.py
#!/usr/bin/python
# -*- encoding:utf-8 -*-
import sample_pb2
import sys
message = sample_pb2.IpcMessage()
message.error_code = sample_pb2.IpcMessage.ERROR_NOT_FOUND
message.data.id=123
message.data.ip_address=(192<<24)+(168<<16)+(0<<8)+5
message.data.port_num=5060
data=message.SerializeToString()
#Sortie sans code de saut de ligne
sys.stdout.write(data)
En langage C, essayez de désérialiser à partir de l'entrée standard.
deserialize_sample.c
#include <stdio.h>
#include "sample.pb-c.h"
int main(){
char buffer[1024];
int len=0;
FILE *fp;
//Entrée en mode binaire à partir de l'entrée standard
fp=freopen(NULL, "rb", stdin);
len=fread(buffer, sizeof(char), sizeof(buffer), fp);
//Suivez les définitions définies dans le fichier Proto
IpcMessage *message;
//La longueur exacte des données sérialisées est requise lors du déballage. NG s'il est trop court ou trop long
message=ipc_message__unpack(NULL, len, buffer);
// has_*Vérifiez si l'élément facultatif a une valeur dans.
if(message->has_error_code)
printf("error_code : %d\n", message->error_code);
//Les messages imbriqués sont également générés dynamiquement lorsqu'ils sont compressés.
if(message->data->has_id)
printf("data.id : %d\n", message->data->id);
if(message->data->has_ip_address)
printf("data.ip_address : %d.%d.%d.%d\n", (message->data->ip_address)>>24 & 0xff,
(message->data->ip_address)>>16 & 0xff,
(message->data->ip_address)>>8 & 0xff,
(message->data->ip_address) & 0xff);
if(message->data->has_port_num)
printf("data.port_num : %d\n", message->data->port_num);
//Libérer les objets déballés
//Il semble que les messages imbriqués soient également publiés
ipc_message__free_unpacked(message, NULL);
close(fp);
return 0;
}
Compiler
$ gcc -l protobuf-c deserialize_sample.c sample.pb-c.c -o deserialize_sample
$ ./serialize_sample.py | ./deserialize_sample
error_code : 404
data.id : 123
data.ip_address : 192.168.0.5
data.port_num : 5060
J'ai pu récupérer avec succès le message protobuf sérialisé en Python en langage C.
Ensuite, un exemple de lecture de Protobuf généré en langage C en Python
serialize_sample.c
#include <stdio.h>
#include <stdlib.h>
#include "sample.pb-c.h"
int main(){
void *buffer;
int len=0;
FILE *fp;
//Entrée en mode binaire à partir de l'entrée standard.
fp=freopen(NULL, "wb", stdout);
//Il existe un exemple d'utilisation de la macro INIT, mais ici, elle est allouée dynamiquement par malloc.
//Après malloc__Doit être initialisé avec init
IpcMessage *message;
message=(IpcMessage *)malloc(sizeof(IpcMessage));
ipc_message__init(message);
//Contrairement à la compression, init ne sécurise pas la zone de message imbriquée.
//Réservez de l'espace pour les messages imbriqués séparément et init. Nécessite une initialisation
message->data=(DataStructs *)malloc(sizeof(DataStructs));
data_structs__init(message->data);
// .L'élément spécifié comme facultatif dans le fichier proto est a_*Drapeau à vrai.
//S'il n'est pas défini sur true, il sera ignoré lors de la sérialisation
message->has_error_code=1;
message->error_code=IPC_MESSAGE__ERRORS__ERROR_SERVICE_UNAVAILABLE;//503
message->data->has_id=1;
message->data->id=1192;
message->data->has_ip_address=1;
message->data->ip_address=(192<<24)+(168<<16)+(0<<8)+234;
message->data->has_port_num=1;
message->data->port_num=8080;
//Sérialiser le processus, obtenir la taille et le malloc&Processus de sérialisation
len=ipc_message__get_packed_size(message);
buffer=malloc(len);
ipc_message__pack(message, buffer);
//Sortie en binaire vers sortie standard
fwrite(buffer, sizeof(void), len, fp);
//Libérer la zone mallocée
free(buffer);
free(message->data);
free(message);
close(fp);
return 0;
}
Compiler
$ gcc -l protobuf-c serialize_sample.c sample.pb-c.c -o serialize_sample
Il s'agit d'un script qui désérialise et affiche l'entrée sérialisée par le protobuf d'entrée standard.
deserialize_sample.py
#!/usr/bin/python
# -*- encoding:utf-8 -*-
import sample_pb2
import sys
data = sys.stdin.read()
message = sample_pb2.IpcMessage()
message.ParseFromString(data)
if message.HasField("error_code"):
print("error_code : {}".format(message.error_code))
if message.data.HasField("id"):
print("data.id : {}".format(message.data.id))
if message.data.HasField("ip_address"):
print("data.ip_address : {}.{}.{}.{}".format((message.data.ip_address>>24)&0xff,
(message.data.ip_address>>16)&0xff,
(message.data.ip_address>> 8)&0xff,
(message.data.ip_address>> 0)&0xff))
if message.data.HasField("port_num"):
print("data.port_num : {}".format(message.data.port_num))
$ ./serialize_sample | ./deserialize_sample.py
error_code : 503
data.id : 1192
data.ip_address : 192.168.0.234
data.port_num : 8080
J'ai réussi à récupérer le message protobuf sérialisé en langage C avec Python.
C'est facile car je n'avais pas beaucoup d'informations sur `` protobuf-c '' en japonais, mais j'ai résumé la méthode de sérialisation et la méthode de désérialisation. C'est un exemple de code qui n'est pas très conscient de la gestion des erreurs, etc., et en réalité ce n'est pas une simple entrée / sortie standard lorsqu'il est implémenté avec IPC, il est donc nécessaire de réassembler avec socket etc., mais je pense que vous pouvez comprendre l'essence. pense···.
Jusqu'à présent, je ne comprenais pas vraiment la valeur du `` tampon de protocole '', et pour être honnête, je pensais seulement que je devrais le vider avec json. Certes, s'il est nécessaire de définir sous une forme facile à comprendre, comme REST / API, il est bon de le définir en json ou yaml, mais qu'en est-il du format des données entre différents processus, différents langages de programmation, etc. Je l'ai trouvé très utile comme solution au problème. De plus, s'il s'agit d'un langage de bas niveau (par rapport aux langages récents) comme le langage C, json et yaml ne sont pas pris en charge en standard, et la vitesse de calcul n'est pas gaspillée autant que possible. Je comprends bien.
Recommended Posts