Ver Mensaje Individual
  #4 (permalink)  
Antiguo 09/03/2013, 08:24
vosk
 
Fecha de Ingreso: agosto-2012
Mensajes: 601
Antigüedad: 12 años, 3 meses
Puntos: 83
Respuesta: Medir cadena con un /0 por medio

'strlen' es una funcion que sirve para contar caracteres hasta que encuentra el caracter nulo, está dirigida al trabajo con texto plano y no puedes cambiarlo, y tal como has comprovado no te sirve.

Para trabajar con datagrams tienes que serializar el envio, de lo contrario nunca podras separar los datos en caso que haya datos combinados como el que planteas:

Código:
numerico de 2 bytes
texto de x bytes

buffer[byte 1 y byte 2] = entero de 2 bytes
buffer[byte 3 ... byte x] = texto de x bytes

longitud a enviar = 2 bytes + x bytes
Ten en cuenta que para el envio el caracter de final de cadena \0 que pones al final de 'hola mundo' no es necesario, solo es cuestion de saber que cuando lo recibes debes guardarlo en un buffer del tamaño de texto recibido +1, pero esto no es importante.

Si el texto a enviar contiene \0 como una lista de textos, deberas implementar tu propia funcion para calcular la longitud exacta de todo el bloque; normalmente en estos casos se usa el doble nulo como indicador de final de bloque; un ejemplo de como harias la funcion para serializar el envio de ese caso concreto:

Código:
/**
la funcion espera un codigo y un texto
*/
void envia(short codi, char *texte) {
	char buffer[1024];
	int lon_texte, tamany_buffer, rb;
	short num_rec;
	unsigned short nbo;
	
        //conviertes a network byte order y lo clavas en el buffer
	nbo = htons(codi);
	memcpy(buffer, &nbo, sizeof(unsigned short));
	
        //calculas la longitud del texto
	rb = 0;
	lon_texte = 0;
	while(strlen(texte + rb) > 0) {
		lon_texte += strlen(texte + rb) + 1;
		rb += (strlen(texte + rb) + 1);
	}
        
        //concatenas el texto
	memcpy(buffer+sizeof(unsigned short), texte, lon_texte);

        //calculas tamaño de buffer de salida
        tamany_buffer = sizeof(unsigned short) + lon_texte;

        //finalmente lo envias con sendto o como sea
}

//haces la llamada a la funcion
envia(2, "una\0prova\0de\0texte\0");

//otra llamada de ejemplo para comprovar que tambien funciona con texto plano
envia(2, "esto funciona\0");
Tal como ves el texto puede contener cuantos nulos quieras, el truco está en crear tu propio sistema de trabajo: en este caso requiero que las cadenas de texto siempre finalicen con un nulo extra, y eso me permite insertar aun mas nulos dentro del texto (esto es habitual en listas de textos). Si no necesitas enviar nulos dentro del texto con el strlen tienes suficiente para calcular la longitud del texto para el buffer de salida.

Ahora lo que te interesa: como separas lo que recibes? Esta es la descripcion del valor de retorno de la funcion (en ingles, pero se entiende)

Código:
Return Values

If no error occurs, recvfrom returns the number of bytes received. If the connection has been gracefully closed, 
the return value is zero. Otherwise, a value of SOCKET_ERROR is returned, and a specific error code can be 
retrieved by calling WSAGetLastError.
Eso significa que si recibes 0 es que todo està dentro del buffer de entrada y la conexion ha finalizado correctamente; si recibes -1 (SOCKET_ERROR) es que se produjo un error; y si recibes el nº bytes igual al tamaño del buffer de entrada es que como minimo aun queda otro paquete por recibir, en ese caso guardas en otro buffer y concatenas los siguientes paquetes hasta recibir el final de conexion. Lo que necesitas es llegar a un retorno de 0 (puede ser en la primera lectura o en la 14, da igual)

Nota: es importante que antes de cada recvfrom reinicies todo el buffer de entrada a nulo (y no vale buffer[0] = 0), de lo contrario te quedaran datos basura que no podras distinguir de los datos recibidos ok?

Una vez tienes el buffer de datos recibidos de longitud incierta sabes el tamaño del buffer pero no los bytes utiles, ahora entra en juego la serializacion del envio: 2 bytes numerico + x bytes de texto; primero sacas el numero y luego haces lo mismo que en la funcion de enviar pero comenzando a contar en rb = sizeof(unsigned short)

Código:
void reb() {
        char buffer[1024];
	int rb, lon_texte;
	unsigned short nbo;
	short num_rec;

        //reseteo el buffer, muy importante
        memset(buffer, 0, sizeof(buffer));	
    
        //me salto las comprovaciones e imagino que a la primera recibo todo
        recvfrom(sockClient, buffer, sizeof(buffer), 0, (struct sockaddr*)&client, &client_t);
	
        //aqui se supone que recvfrom ha retornado 0 para finalizar la conexion y todo
        //ha ido sin errores, la utopia del programador :)

        //aplicas la des-serializacion

        //extraes el numero
	memcpy(&nbo, buffer, sizeof(unsigned short));
	num_rec = ntohs(nbo);
	
        //calculas el tamaño de texto
	rb = sizeof(unsigned short);
	lon_texte = 0;
	while(strlen(buffer + rb) > 0) {
		lon_texte += strlen(buffer + rb) + 1;
		rb += (strlen(buffer + rb) + 1);
	}
	
	//ya tienes lon_texte que es la longitud del texto, lo extraes del buffer con un offset de 2 bytes
        memcpy(buffer_destino_texto, buffer, sizeof(unsigned short));
}
La cosa funciona porque el bufer esta iniciado a nulo, y el texto recibido termina en nulo: para esta serializacion es indispensable que el texto a enviar termine en nulo y que ademas el nulo final se compute como byte a enviar y por lo tanto como byte a recibir, por eso puedes aplicar la desserializacion a los datos recibidos

Otra cosa, eso de stshort me he imaginado que estas convertiendo a network byte order, para ello he usado htons para el envio y ntohs para el recibo.

Mas cosas, echa un vistazo a la funcion recvfrom que has colgado, el tercer argumento es el tamaño disponible (ojo, tu has puesto la longitud de texto) y debes indicar sizeof(buffer), o en caso de concatenar paquetes recibidos será el tamaño de buffer no ocupado.


"...Para empezar tengo que enviar un "codigo + Hello World" del servidor al cliente, esto me funciona bien, pero hay una cosa que no me acava de convencer...."

'acava' va con b :)

Saludos
vosk