Socket Client Java e Server em C++

Pessoa,

Eu sou cliente(java) de um server(socket) feito em C++.
O servert está me enviando uma estrutura(Struct C++), recebo um byte[], gostaria de saber se existe uma forma de eu transformar em um objeto por exemplo.

public class Struct implements Serializable
{
    public int tipo;
    public int valorInt;
    public double valorDouble;
    
    /**
	 * 
	 */
	private static final long	serialVersionUID	= 1L;
}

Como é a definição do struct C++?

Segue

/* envelopamento de resposta e evento */
typedef struct _tagPACOTE_RESPOSTA {
	_int32 tipo;
	_int64 valorInt;
	double valorDouble;
} PACOTE_RESPOSTA;

Mais duas coisas.

  1. Como é em C, então a ordem dos bytes respeita a ordem natural dos bytes no processador. Você está rodando seu código em um processador AMD ou Intel, ou então MIPS ou Sparc ou ARM ou sei lá o quê?

  2. Qual é o “alignment” ou “packing” dessa estrutura de dados?
    Se você não especificou isso (talvez com algum pragma como “#pragma pack” (Microsoft Visual C++, gcc) ou #pragma align (IBM XL C++) ou com alguma opção dada na linha de comando do compilador. Isso depende do compilador e do processador

Então, por favor, indique qual é o compilador C++ usado e qual a arquitetura alvo (por exemplo, x86, x64, Sparc, MIPS, ARM, PowerPC etc.)

Dica número 1: se estiver muito complicado, sugiro rodar o seguinte programa (corrija eventuais erros de compilação que ocorrerem, só testei com MS Visual Studio 2005)

#include <stdio.h>
#include <string.h>
#include <stddef.h>

typedef struct _tagPACOTE_RESPOSTA {  
    _int32 tipo;  
    _int64 valorInt;  
    double valorDouble;  
} PACOTE_RESPOSTA; 

typedef union _UNION {
    PACOTE_RESPOSTA pr;
    unsigned char by [32];
} UNION;

void dump (unsigned char by[], int len) {
    int i;
    for (i = 0; i < len; ++i) { printf ("%02X ", by[i] & 0xFF); }
}

int main (int argc, char *argv[]) {
    UNION u;
    memset (u.by, 0, sizeof (u.by));
    printf ("Offset Of tipo = %d\n", offsetof(UNION, pr.tipo));
    u.pr.tipo = 0x12345678;
    printf ("Offset Of valorInt = %d\n", offsetof(UNION, pr.valorInt));
    u.pr.valorInt = 0x0123456789ABCDEFLL;
    printf ("Offset Of valorDouble = %d\n", offsetof(UNION, pr.valorDouble));
    u.pr.valorDouble = 3.1415926535897932;
    printf ("Sizeof PACOTE_RESPOSTA = %d\n", sizeof(PACOTE_RESPOSTA));
    dump (u.by, sizeof(u.by));
 }

Rodando esse programa usando o Microsoft Visual Studio 2005 gerando código para Win32, ele gerou a seguinte resposta:

Offset Of tipo = 0
Offset Of valorInt = 8
Offset Of valorDouble = 16
Sizeof PACOTE_RESPOSTA = 24
78 56 34 12 00 00 00 00 EF CD AB 89 67 45 23 01 18 2D 44 54 FB 21 09 40 00 00 00 00 00 00 00 00

O que ele me diz?

  1. Mesmo um _int32 ocupando 4 bytes, ele acabou ocupando 8 bytes na struct, porque há um conceito de “alignment” ou “packing” que faz com que uma variável ‘int’ ou ‘__int64’ ocupe um endereço múltiplo de 8.
  2. Os bytes são armazenados ao contrário (em máquinas Intel ou AMD). Você viu que o número 0x12345678 foi armazenado como 78 56 34 12 e o númer 0x0123456789ABCDEF foi armazenado como EF CD AB 89 67 45 23 01
  3. Embora não seja trivial enxergar isso, um double ocupa 8 bytes e também é armazenado ao contrário.

Portanto, você precisaria pegar os bytes (usando um DataInputStream acoplado a um SocketInputStream, quem sabe) e então “desinverter” os bytes para podermos ter os dados desejados.

Para converter 8 bytes em um double, é necessário converter esses 8 bytes para um long, e então usar o método Double.longBitsToDouble.

Em outras arquiteturas, o programa pode compilar e dar resultados diferentes. Diga o que ocorre para seu compilador e sistema operacional (talvez precise acertar algumas coisas, como _int32 ou __int32, _int64 ou __int64.)

Com o gcc sob Cygwin o resultado foi:

#include <stdio.h>
#include <string.h>
#include <stddef.h>
#include <unistd.h>

typedef struct _tagPACOTE_RESPOSTA {  
    int tipo;  
    long long valorInt;  
    double valorDouble;  
} PACOTE_RESPOSTA; 

typedef union _UNION {
    PACOTE_RESPOSTA pr;
    unsigned char by [32];
} UNION;

void dump (unsigned char by[], int len) {
    int i;
    for (i = 0; i < len; ++i) { printf ("%02X ", by[i] & 0xFF); }
}

int main (int argc, char *argv[]) {
    UNION u;
    memset (u.by, 0, sizeof (u.by));
    printf ("Offset Of tipo = %d\n", offsetof(UNION, pr.tipo));
    u.pr.tipo = 0x12345678;
    printf ("Offset Of valorInt = %d\n", offsetof(UNION, pr.valorInt));
    u.pr.valorInt = 0x0123456789ABCDEFLL;
    printf ("Offset Of valorDouble = %d\n", offsetof(UNION, pr.valorDouble));
    u.pr.valorDouble = 3.1415926535897932;
    printf ("Sizeof PACOTE_RESPOSTA = %d\n", sizeof(PACOTE_RESPOSTA));
    dump (u.by, sizeof(u.by));
 }
Offset Of tipo = 0
Offset Of valorInt = 8
Offset Of valorDouble = 16
Sizeof PACOTE_RESPOSTA = 24
78 56 34 12 00 00 00 00 EF CD AB 89 67 45 23 01 18 2D 44 54 FB 21 09 40 00 00 00 00 00 00 00 00

Ou seja, o comportamento foi semelhante.

Mas eu consigo fazer isso que eu quero? está bem dificil de conseguir aqui

Pelo menos você poderia dizer em que sistema operacional e tipo de processador você está rodando o seu programa em C++?
Por exemplo, se for em uma máquina Sparc rodando Solaris 10 o resultado é diferente de se for em uma máquina Intel ou AMD rodando Windows 7 - 64bits.

Processador I5 Intel com Windows 7.

Pois bem, então o layout provavelmente é o que mostrei nesses dois exemplos acima. Você entendeu mais ou menos o que deve ser feito nesses casos?

Você pode usar o Wireshark para ver exatamente o que está trafegando pela rede: http://www.wireshark.org/

Dê preferencia a estruturas como o DataInputStream, ou o ByteBuffer.
Se estiver usando tipos unsigned, veja também essa classe:

No lado do Java, não adianta ler um byte[] direto. Afinal, será impossível fazer um cast disso direto em cima de uma classe, como C++ faz.
Você terá que ler campo-a-campo, tomando o cuidado de fazer as conversões necessárias. O ByteBuffer já é capaz de lidar com endianess, por isso, recomendo que você o utilize no lugar dos streams.

E, como o entanglement está frizando: Não adianta imaginar o que vem pela rede. É necessário conhecer exatamente o que é transmitido.
Inclusive, saber se o essa transmissão deve ser em little ou big endian.

Consegue sim, tranquilamente. Só tem que saber exatamente o que cada lado está enviando.

Pessoal obrigado pela ajuda.

Minha estrutura é composta por int32, int64 e double.
Em uma conversão simples:
Um int32 em "C" é equivalente a um int em java.
Um int64 em "C" é equivalente a um long em java.
Um double em "C" é equivalente a um double em java.

Bom seguindo este raciocinio eu estou recebendo um array de 24 bytes, onde os 8 primeiros são os bytes do int32, os 8 seguintes são os bytes do int64 e os outros 8 bytes pertencem ao double.

criei 3 métodos conversores de byte array para estes valores.

Fiz alguns testes apresentaram sempre os mesmo valores, será que pode dar alguma coisa errada que eu não estou vendo?

Muito obrigado pela ajuda de vocês!!!

        public static long toLong(byte[] value)
	{
		long aux;
		
		aux = 0;
		
		for (int i = 0; i &lt; value.length; i++)
		{
		   aux += ((long) value[i] & 0xffL) &lt;&lt; (8 * i);
		}
		
		return aux;
	}
        public static int toInt(byte[] value)
	{
		int aux;
		
		aux = 0;
		
		for (int i = 0; i &lt; value.length; i++)
		{
		   aux += ((int) value[i] & 0xffL) &lt;&lt; (8 * i);
		}
		
		return aux;
	}
	public static double toDouble(byte[] value)
	{
		int start = 0;
		int i = 0;
		int len = 8;
		int cnt = 0;
		byte[] tmp = new byte[len];
		
		for (i = start; i &lt; (start + len); i++)
		{
			tmp[cnt] = value[i];
			cnt++;
		}
		
		long accum = 0;
		i = 0;
		
		for (int shiftBy = 0; shiftBy &lt; 64; shiftBy += 8)
		{
			accum |= ((long) (tmp[i] & 0xff)) &lt;&lt; shiftBy;
			i++;
		}

		return Double.longBitsToDouble(accum);
	}

[quote=brunobuild]Pessoal obrigado pela ajuda.

Minha estrutura é composta por int32, int64 e double.
Em uma conversão simples:
Um int32 em "C" é equivalente a um int em java.
Um int64 em "C" é equivalente a um long em java.
Um double em "C" é equivalente a um double em java.
[/quote]

Isso se a arquiteturas do lado do C for big endian. Isso pode ser garantido chamando funções do C para converter o dado para o “formato da rede” (como por exemplo, htons).
E se você só usar tipos com sinal (afinal, o java não suporta tipos sem sinal).

Se quiser garantir mesmo, não jogue uma struct na rede simplesmente do lado do C, envie campo-a-campo. Embora isso seja possível, você vai não só ter problemas com endianess, mas também com alinhamento de memória, como o Entanglement frisou.

Cuidado também com tipos do tipo String. O C, por padrão, termina Strings com \0, enquanto o Java não. Você deve decidir se em seu protocolo irá incluir o \0, ou deixar sem \0 e incluir antes do texto o tamanho da String.
Eu voto na segunda, é mais fácil de implementar tanto no Java quanto no C++, e facilita na hora de tratar o dado na rede.

Eu recomendo fortemente que você use classes adequadas, como o DataInputStream e DataOutputStream ou o ByteBuffer, no lugar de ficar fazendo shifts manualmente.
Isso só deixa o código desnecessariamente rebuscado. como também propenso a erros. As classes já fazem isso automaticamente, não tem porque reinventar a roda.