Mecanismos de serialização de dados são necessários quando desejamos traduzir o conteúdo de estruturas de dados para algum formato padronizado, de forma que esse conteúdo possa ser armazenado (em um arquivo, por exemplo) ou transmitido através de um protocolo de comunicação. Esse conteúdo poderá, então, ser posteriormente reconstruído (deserializado) para uso em algum sistema ou aplicação.
Existem diversos padrões de formatos de serialização de dados; um exemplo bastante conhecido e utilizado é XML. Um outro formato de serialização, que utilizaremos em nosso trabalho, é o mecanismo utilizado pela Google, denominado Protocol Buffers.
O mecanismo de serialização Protocol Buffers define a codificação
de estruturas de dados (mensagens) que podem conter diversos tipos de dados,
como, por exemplo, inteiros com e sem sinal, valores de
ponto flutuante, strings e mensagens aninhadas.
Em nosso trabalho, porém, serializaremos apenas estruturas de dados simples
(sem aninhamento),
contendo campos de dois únicos tipos: inteiros de 32 bits com sinal
(o tipo sint32
) e strings contendo caracteres ASCII.
Além disso,
utilizaremos uma forma mais simples para descrever as estruturas de dados
a serem serializadas (ao invés de utilizar arquivos .proto
).
No nosso trabalho, teremos apenas dois tipos de dados (wire types): 0 (um inteiro sint32) e 2 (uma string). Além disso, nossas estruturas terão no máximo 7 campos (o primeiro campo corresponde ao número de sequência 1, o segundo 2, e assim por diante).
Valores de inteiros com sinal são codificados como varints, um método de serialização de inteiros que usa um número variável de bytes. Porém, ao invés de codificar valores inteiros com sinal em complemento a 2, a codificação adotada pelo mecanismo Protocol Buffers é a ZigZag, que mapeia inteiros com sinal para inteiros sem sinal zigzag-ando valores positivos e negativos de forma que números com um valor absoluto pequeno (como -1) tenham um valor codificado pequeno também. Dessa forma, -1 é codificado como 1, 1 é codificado como 2, -2 é codificado como 3, e assim por diante.
Valores de strings são representados em uma mensagem como um tamanho (um inteiro codificado como um varint) seguido pelo número de bytes (que representam os caracteres) especificado por esse tamanho. No nosso trabalho, usaremos apenas caracteres ASCII (os 128 caracteres associados aos códigos de 0 a 0x7F em ASCII tem a mesma representação em UTF-8).
A codificação de chaves de campos, de inteiros como varints e o método ZigZag são descritos, com exemplos, na página de documentação Protocol Buffers:Encoding.
Leia com atenção o enunciado do trabalho e as instruções para a entrega. Em caso de dúvidas, não invente. Pergunte!
O objetivo do trabalho é implementar, na linguagem C,
uma função codifica
,
que escreve em arquivo binário
o conteúdo de uma estrutura de dados formatada conforme
a codificação adotada pelo mecanismo Protocol Buffers.
O protótipo dessa função é
int codifica (void* valores, char *campos, FILE *arquivo);
A função codifica
recebe como argumentos:
valores
: um ponteiro para a struct a ser codificada
campos
: uma descrição dos campos dessa struct
arquivo
: um arquivo aberto para escrita, em modo binário
A string campos
representa, na ordem, o tipo de cada campo
da struct, de acordo com o código a seguir:
'i' - inteiro com sinal 's' - uma string (cadeia de caracteres ASCII, terminada com zero)
Como exemplo, dada a declaração:
struct s { int i1; int i2; char s1[21]; int i3; }; struct s est1;a string
campos
correspondente é "iisi"
.
Assumindo que o descritor do arquivo de saída esteja armazenado em uma variável
arq
, do tipo FILE *
, a chamada
para a gravação do conteúdo dessa struct codificado como
uma mensagem Protocol Buffer seria:
res = codifica(&est1, "iisi", arq);
Observações importantes:
codifica
deve assumir que sua entrada está
correta, isto é:
valores
contém o endereço
de uma estrutura armazenada na memória;
campos
;
arquivo
contém o descritor
de um arquivo aberto, com sucesso, pela função que chamou codifica.
codifica
deve levar em consideração
as regras de alinhamento especificadas para o ambiente onde
a função será executada (SO Linux, em uma máquina IA-32).
A estrutura est1
mostrada acima, por exemplo,
possui bytes de padding...
Você deve criar um arquivo fonte chamado
codifica.c
contendo a função descrita acima (codifica
)
e funções auxiliares, se for o caso.
Esse arquivo não
deve conter uma função main
!
Crie também um arquivo
codifica.h
,
que deve conter apenas
o protótipo da função codifica
.
Mantenha o protótipo especificado no enunciado do trabalho,
sem alterações!
Para testar seu programa, crie um outro arquivo,
teste.c
,
contendo uma função main
.
Crie seu programa executável teste
com a linha:
gcc -Wall -m32 -o teste codifica.c teste.c
Tanto o arquivo codifica.c
como
teste.c
devem conter a linha:
#include "codifica.h"
typedef struct { int i1; int i2; } S1; S1 e1 = {1, -1};o arquivo binário resultante da codificação deve ter este conteúdo.
typedef struct { int i1; int i2; char s1[3]; int i3; } S2; S2 e2 = {150, -150, "oi", 65632};o arquivo binário resultante da codificação deve ter este outro conteúdo.
Devem ser entregues três arquivos:
Coloque no início dos arquivos fonte, como comentário, os nomes dos integrantes do grupo, da seguinte forma:
/* Nome_do_Aluno1 Matricula Turma */ /* Nome_do_Aluno2 Matricula Turma */Coloque também no relatório o nome dos integrantes do grupo.
O trabalho deve ser enviado por email para o professor da turma, com
subject/assunto "Trabalho 1 - Software Basico".
Indique claramente os integrantes do grupo no corpo do email e inclua,
como anexo, os três arquivos pedidos (codifica.c, teste.c e relatorio).
O trabalho deve ser entregue até a meia-noite do dia 07/04