PDA

View Full Version : [Java] socket e codifica stringhe


Oceans11
19-09-2008, 12:13
Mi sono scritto un applicazione lato server (per il momento) che gestisce la ricezione di un file. Per mia comodità ho usato DataInputStream per leggere dalla socket, e DataOutputStream per scrivere su file.
Ora volevo provare il programma, quindi mi sono detto: "usa telnet e vedi", ma devo avere qualche problema con la codifica delle stringhe.
Una volta che un client si connette, apro gli streams e faccio:


String filename = input.readUTF();
// sì, voglio ricevere il file
output.writeInt(ACK);
// prendo dimensione del file
int size = input.readInt();
// trasferisco il file


ora da telnet provo a scrivere qualcosa (dovrebbe prenderlo come nome del file) ma il server non "sente". Come risolvo? E' come penso un problema di codifica?

banryu79
19-09-2008, 12:31
String filename = input.readUTF();
// sì, voglio ricevere il file
output.writeInt(ACK);
// prendo dimensione del file
int size = input.readInt();
// trasferisco il file


ora da telnet provo a scrivere qualcosa (dovrebbe prenderlo come nome del file) ma il server non "sente". Come risolvo? E' come penso un problema di codifica?

Non ne ho idea ma come ulteriore verifica fatti un print in System.out di "filename" e vedi cosa salta fuori.

Oceans11
19-09-2008, 12:50
Non ne ho idea ma come ulteriore verifica fatti un print in System.out di "filename" e vedi cosa salta fuori.

Hai ragione, scusa ma la stampa a video di debug l'ho omessa quando ho copiato il codice...cmq non esce fuori niente, rimane in attesa del benedetto input.readUTF(); ecco perchè dicevo che forse aspetta un CRLF o similia.

banryu79
19-09-2008, 13:23
Due cose:

1] prova a inserire la chiamata a .readUTF() dentro un blocco try-catch in cui catturi l'eccezione di tipo: UTFDataFormatException. Esegui e vedi se in output si verifica questa eccezione [the bytes do not represent a valid UTF-8 encoding of a string... come dicono i javadoc dell'interfaccia DataInput]

2] leggiti il contratto dichiarato dall'interfaccia DataInput per quanto riguarda l'implementazione del metodo .readUTF():

Reads in a string that has been encoded using a modified UTF-8 format. The general contract of readUTF is that it reads a representation of a Unicode character string encoded in Java modified UTF-8 format; this string of characters is then returned as a String.

First, two bytes are read and used to construct an unsigned 16-bit integer in exactly the manner of the readUnsignedShort method . This integer value is called the UTF length and specifies the number of additional bytes to be read. These bytes are then converted to characters by considering them in groups. The length of each group is computed from the value of the first byte of the group. The byte following a group, if any, is the first byte of the next group.

If the first byte of a group matches the bit pattern 0xxxxxxx (where x means "may be 0 or 1"), then the group consists of just that byte. The byte is zero-extended to form a character.

If the first byte of a group matches the bit pattern 110xxxxx, then the group consists of that byte a and a second byte b. If there is no byte b (because byte a was the last of the bytes to be read), or if byte b does not match the bit pattern 10xxxxxx, then a UTFDataFormatException is thrown. Otherwise, the group is converted to the character:

(char)(((a& 0x1F) << 6) | (b & 0x3F))


If the first byte of a group matches the bit pattern 1110xxxx, then the group consists of that byte a and two more bytes b and c. If there is no byte c (because byte a was one of the last two of the bytes to be read), or either byte b or byte c does not match the bit pattern 10xxxxxx, then a UTFDataFormatException is thrown. Otherwise, the group is converted to the character:


(char)(((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F))


If the first byte of a group matches the pattern 1111xxxx or the pattern 10xxxxxx, then a UTFDataFormatException is thrown.

If end of file is encountered at any time during this entire process, then an EOFException is thrown.

After every group has been converted to a character by this process, the characters are gathered, in the same order in which their corresponding groups were read from the input stream, to form a String, which is returned.

The writeUTF method of interface DataOutput may be used to write data that is suitable for reading by this method.


Spero possa esserti di aiuto.

Oceans11
19-09-2008, 14:35
1] prova a inserire la chiamata a .readUTF() dentro un blocco try-catch in cui catturi l'eccezione di tipo: UTFDataFormatException. Esegui e vedi se in output si verifica questa eccezione [the bytes do not represent a valid UTF-8 encoding of a string... come dicono i javadoc dell'interfaccia DataInput]

Niente da fare, non viene lanciata alcuna eccezione.
Si comporta come se non ricevesse una specie di fine stringa, (nel vecchio e deprecato readLine() sarebbe il CRLF), oppure come se non svuotasse il buffer.

banryu79
19-09-2008, 15:49
Prova a forzare il flush() dell'input stream, non so se possa servire almeno per avere un parziale output.

Posto i sorgenti del metodo .readUTF() della classe DataInputStream, magari con un'occhiata qualcuno di più esperto può capire quale sia il problema.

545 /**
546 * See the general contract of the <code>readUTF</code>
547 * method of <code>DataInput</code>.
548 * <p>
549 * Bytes
550 * for this operation are read from the contained
551 * input stream.
552 *
553 * @return a Unicode string.
554 * @exception EOFException if this input stream reaches the end before
555 * reading all the bytes.
556 * @exception IOException the stream has been closed and the contained
557 * input stream does not support reading after close, or
558 * another I/O error occurs.
559 * @exception UTFDataFormatException if the bytes do not represent a valid
560 * modified UTF-8 encoding of a string.
561 * @see java.io.DataInputStream#readUTF(java.io.DataInput)
562 */
563 public final String readUTF() throws IOException {
564 return readUTF(this);
565 }
566
567 /**
568 * Reads from the
569 * stream <code>in</code> a representation
570 * of a Unicode character string encoded in
571 * <a href="DataInput.html#modified-utf-8">modified UTF-8</a> format;
572 * this string of characters is then returned as a <code>String</code>.
573 * The details of the modified UTF-8 representation
574 * are exactly the same as for the <code>readUTF</code>
575 * method of <code>DataInput</code>.
576 *
577 * @param in a data input stream.
578 * @return a Unicode string.
579 * @exception EOFException if the input stream reaches the end
580 * before all the bytes.
581 * @exception IOException the stream has been closed and the contained
582 * input stream does not support reading after close, or
583 * another I/O error occurs.
584 * @exception UTFDataFormatException if the bytes do not represent a
585 * valid modified UTF-8 encoding of a Unicode string.
586 * @see java.io.DataInputStream#readUnsignedShort()
587 */
588 public final static String readUTF(DataInput in) throws IOException {
589 int utflen = in.readUnsignedShort();
590 byte[] bytearr = null;
591 char[] chararr = null;
592 if (in instanceof DataInputStream) {
593 DataInputStream dis = (DataInputStream)in;
594 if (dis.bytearr.length < utflen){
595 dis.bytearr = new byte[utflen*2];
596 dis.chararr = new char[utflen*2];
597 }
598 chararr = dis.chararr;
599 bytearr = dis.bytearr;
600 } else {
601 bytearr = new byte[utflen];
602 chararr = new char[utflen];
603 }
604
605 int c, char2, char3;
606 int count = 0;
607 int chararr_count=0;
608
609 in.readFully(bytearr, 0, utflen);
610
611 while (count < utflen) {
612 c = (int) bytearr[count] & 0xff;
613 if (c > 127) break;
614 count++;
615 chararr[chararr_count++]=(char)c;
616 }
617
618 while (count < utflen) {
619 c = (int) bytearr[count] & 0xff;
620 switch (c >> 4) {
621 case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
622 /* 0xxxxxxx*/
623 count++;
624 chararr[chararr_count++]=(char)c;
625 break;
626 case 12: case 13:
627 /* 110x xxxx 10xx xxxx*/
628 count += 2;
629 if (count > utflen)
630 throw new UTFDataFormatException(
631 "malformed input: partial character at end");
632 char2 = (int) bytearr[count-1];
633 if ((char2 & 0xC0) != 0x80)
634 throw new UTFDataFormatException(
635 "malformed input around byte " + count);
636 chararr[chararr_count++]=(char)(((c & 0x1F) << 6) |
637 (char2 & 0x3F));
638 break;
639 case 14:
640 /* 1110 xxxx 10xx xxxx 10xx xxxx */
641 count += 3;
642 if (count > utflen)
643 throw new UTFDataFormatException(
644 "malformed input: partial character at end");
645 char2 = (int) bytearr[count-2];
646 char3 = (int) bytearr[count-1];
647 if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80))
648 throw new UTFDataFormatException(
649 "malformed input around byte " + (count-1));
650 chararr[chararr_count++]=(char)(((c & 0x0F) << 12) |
651 ((char2 & 0x3F) << 6) |
652 ((char3 & 0x3F) << 0));
653 break;
654 default:
655 /* 10xx xxxx, 1111 xxxx */
656 throw new UTFDataFormatException(
657 "malformed input around byte " + count);
658 }
659 }
660 // The number of chars produced may be less than utflen
661 return new String(chararr, 0, chararr_count);
662 }
663 }


Inoltre: hai provato a usare qualche altro sistema invece che Telnet per inviare il nome del file?
Tanto per verificare che non sia un problema di formato incompatibile causato da Telnet.

PS.: nella parte in grassetto del codice: bytearr è un membro della classe dichiarato così:

/**
56 * working arrays initialized on demand by readUTF
57 */
58 private byte bytearr[] = new byte[80];
59 private char chararr[] = new char[80];

Oceans11
19-09-2008, 15:57
ho provato anche con netcat, stessa storia.....ora però mi sono scritto il client e funziona, almeno sembra!

ancor di più penso sia un problema dovuto al fatto che il server non si accorge di quello che dice il client: per il server il nome del file potrebbe essere lungo righe e righe di testo scritto con questi tool. :mc:

beh in ogni caso grazie per l'aiuto!


PS: se vuoi posto le 2 classi complete