DatagramPakets

W

Wimme

Gast
Hi!

Ich möchte Daten über einen DatagramSocket als DatagramPaket senden. Der Inhalt von den DatagramPakets kann von ein paar verschiedenen Typen sein, welche wiederum andere Inhalte haben.

Beispielsweise würde ich gerne soetwas übertragen:
paket.typ = 1
paket.time = 10

oder

paket.typ = 2
paket.inhalt = "hallo"


1. Frage: In C würde ich jetzt für die einzelnen Pakettypen structs in einer Headerdatei erstellen. Da es in Java ja keine Structs gibt, muss ich hier hier für jeden Pakettypen eine public class in einer separaten Datei erstellen?

2. Frage: Ein DatagramPaket enthalt ja byte[]. Wie kann ich mein Objekt dann in byte[] überführen?
 

Kevin94

Top Contributor
1. Die Klassen müssen nicht public sein, aber ja du brauchst für jeden Typ eine Klasse. Ich würde das so realisieren:
Java:
public abstract class Data implements Serializable //s. 2
{
public final int dataType;

protected Data(int type)
{
    dataType=type;
}

public static class StringData extends Data
{
    public static final int DATA_TYPE=1;
    public String content;

    public StringData(String c)
    {
        super(DATA_TYPE);
        content=c;
    }
}

public static class MyData extends Data
{
    public static final int DATA_TYPE=2;
    public MyData content;

    public StringData(MyData c)
    {
        super(DATA_TYPE);
        content=c;
    }
}

}
Du kannst dann anhand von dataType swichen und ohne instanceof casten.

2. Du musst die Objekte serializieren. Es gibt in Java bereits eine eingebaute Serilization mithilfe der Klassen [JAPI]ObjectInputStream[/JAPI] bzw. [JAPI]ObjectOutputStream[/JAPI]. Die Klassen und die alle Instanzvariablen müssen dabei das Interface [JAPI]Serializable[/JAPI] implementieren. Alternativ kannst zum obigen Beispiel kannst du die Typen die du senden willst direkt auch direkt Serializieren und dann mit instanceof auf den Typprüfen, das beitet sich an, wenn du keine stuct-Ersatzklassen bilden musst, sondern nur Typen wie String, Int(eger) oder Calendar,...

Für das obige Beispiele würde das so aussehen:
Java:
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream out=new ObjectOutputStream(bos);
out.writeObject(new StringData("test"));
byte[] send=bos.toByteArray();
//Übertragen

ByteArrayInputStream bs=new ByteArrayInputStream(send);
ObjectInputStream in=new ObjectInputStream(bis);
Data read=(Data)in.readObject(); // Das geht natürlich nur, wenn du immer genau ein Data-Objekt sendest
switch(read.dataType)
{
    case Data.StringData.DATA_TYPE:
    Data.StringData data=(Data.StringData)read;
//Do something
    break;
    case Data.MyData.DATA_TYPE:
    Data.MyData data=(Data.MyData)read;
//Do someting else
    break;
}
 
W

Wimme

Gast
Herzlichen Dank für deine Hilfe! Ich glaube das bringt mich weiter :)

1. Du hast dieses "nested-static" Variante gewählt, damit alle Datenpakete in einer .java file sein können, diese aber im Prinzip wie top-level Klassen funktionieren?

2. Das mit den Strömen habe ich noch nie so recht verstanden. Kann ich das Codestück
Java:
ByteArrayOutputStream bos=new ByteArrayOutputStream();
ObjectOutputStream out=new ObjectOutputStream(bos);
out.writeObject(new StringData("test"));
wie folgt interpretieren?
Der ObjectOutputStream out bekommt ein OBJEKT als Eingabe (was genau macht er damit?) und gibt das Objekt dann verarbeitet weiter an einen ByteArrayOutputStream (der was genau tut?). D.h. die beiden Ströme sind sozusagen in Reihe geschaltet.

3. Noch eine andere Sache: Ich habe ein set von Konstanten, über die ich die verschiedenen Typen realisiere. Wie mache ich es am besten, dass diese Konstanten im gesamten Projekt bekannt sind?
 

Kevin94

Top Contributor
1. Ja, wenn es viele verschiedene Typen sind wird das dann zwar unübersichtlich, aber für eine überschaubarere Zahl halte ich das für eine geeignete Lösung.

2. So kann man es ausdrücken. Auf "In Reihe" wäre ich zwar nicht gekommen, aber das beschreibt es ziemlich gut. Der ObjectOutputStream nimmt das Objekt, kuckt sich über Reflections die Klasse an und schreibt dann alle wichtigen Informationen und Instanzvariablen in den "dahinter liegenden" ByteArrayOutputStream. Dieser Stream macht nichts anderes, als (wie der Name es sagt) jedes hineingeschrieben Byte in ein Array zu speichern. Felder die mit dem Schlüsselwort
Code:
transient
gekennzeichnet sind, werden übrigens nicht mit serializiert.

3. Als
Code:
public static final
Variablen in eine zentrale Klasse oder ein Interface (s. [JAPI]SwingConstants[/JAPI]) packen. Wenn du diese Konstanten-Klasse nicht überall im Code haben willst, implementierst du einfach das Interface bzw. machst ein
Code:
import static package.ConstClass.*
, dann musst du nur an einer Stelle was ändern, wenn der Name nicht mehr passt oder die Klasse in ein anderes Package gewandert ist.
 
W

Wimme

Gast
Hi!

zu 3. habe irgendwo gelesen, dass das eine schlechte Idee sei (glaube bei Stackoverflow). Und man solle irgendwie enums verwenden, wobei ich nicht verstanden habe, wie ich diese dann global verfügbar mache. Ich denke ich mache es einfach so, wie du sagst.


Ich schicke jetzt im Übrigen über udp Pakete mit dem Typ 1, so wie du es vorgemacht hast. An der anderen Seite habe ich einen Server, in C geschrieben, der sich den Typ anschaut und dann entsprechend reagiert. Leider liest dieser Server statt einer 1 immer eine 172. Hast du eventuell so schon eine Idee, woran das liegen könnte? Irgendwie werden da wohl bytes verschoben o.ä. :-(
 

Kevin94

Top Contributor
Über die "Default-Serializierung" geschriebene Objekte haben einen bestimmten Aufbau der eine Menge Overhead enthält, weil diese Methode dazu gedacht ist, ohne irgend ein zusätzliches Wissen Objekte jeder beliebigen Klasse serialisieren zu können. Für genauere erklärungen les dir einfach das Kapitel in der Insel durch. Die ersten vier Byte in dem Paket sind also auf keinen Fall der Typ.

Wenn du auf der anderen Seite gar kein Java hast, halte ich es allerdings für falsch, einen ObjectOutputStream zu verwenden bzw. die Objekte direkt in den Stream zu schreiben. Ich würde eher dazu tendieren, die relevanten Informationen als primitive Datentypen in den Stream zu schreiben, den Typ natürlich als erstes.

Zu Enums: Es kommt drauf an, was du für Konstanten hast. Wenn du Konstanten hast wie E,PI,debuggingEnabled oder logger dann ist es Schmarrn dafür ein Enum zu nehmen, selbiges gilt für lose zusammenhangslose Zahlen/Objekte. Enums sind dafür gedacht, wenn eine Variable genau einen Wert aus diesen Konstanten annehmen darf und sonst nichts. Als Beispiel:
Java:
public enum Ampelfarben{
    ROT,GELB,GRUEN;
}
 
W

Wimme

Gast
Danke für deine schnelle Antwort.

Das ist ja ärgerlich. Müsste ich also einen DataOutputStream in Kombination mit einem ByteArrayOutputStream verwenden und die Felder meiner Objekte einzeln in den DataOutputStream schreiben?
 
W

Wimme

Gast
Hi,

ich denke das senden klappt jetzt!
Beim Empfangen habe ich noch Probleme. Ich habe ein byte-Array (byte[]) "data" und möchte mir beispielsweise aus byte[1] und byte[2] einen short bauen. Wie mache ich das?
 
W

Wimme

Gast
Hi ihr!

Die Kommunikation zwischen C und Java übers Netzwerk via UDP bereitet mir noch Probleme. Irgendwelche bytes werden da verschoben o.ä.

Auf Serverseite kommen 1234 Pakete an. In einem RESULT Paket, was zurück geschickt wird, steht dafür in den Bytes 1 und 2 eines byte-Arrays -46, 4 was den 1234 entsprechen sollte. Wie passt das zusammen?

Der Server macht folgendes:
Code:
scream_result_packet *result = (scream_result_packet*) malloc (sizeof (scream_result_packet));
		result->type = SC_PACKET_TYPE_RESULT;
		result->receivedPackets = session->connTable[check].receivedPackets;
		result->totalGaps = session->connTable[check].totalGaps;
		result->maxGapSize = session->connTable[check].maxGapSize;
int16_t bytes_sent = sendto (session->sockfd, result, sizeof (scream_result_packet), 0, (struct sockaddr *) &(session->addrClient), session->addrClientLength);

wobei definiert ist:
Code:
typedef struct {
	uint8_t type;
	uint16_t receivedPackets;
	uint16_t unorderedPackets;
	uint16_t totalGaps;
	uint16_t maxGapSize;
} __attribute__((packed)) scream_result_packet;


Mein Android Client macht mit dem Paket folgendes:
Java:
public static DataPacket buildPacket(DatagramPacket packet, Activity mainAct) {
		
		DataPacket returnPacket = null;
		byte[] data = packet.getData();
		byte type;
		ByteBuffer bb = ByteBuffer.allocate(256); // provides methods to get bytes and shorts from the byte array
		bb.put(data,0,data.length);
		
		// we need to check the first byte of the DatagramPacket to determine the type
		type = bb.get(0);
		// build a new DataPacket depending on the type
		switch (type) {
		case 1:
			...
			break;
		case 2:
			..
			break;
		case RESULT:
		    short receivedPackets = bb.getShort(1);
		    short unorderedPackets = bb.getShort(3);
		    short totalGaps = bb.getShort(5);
		    short maxGapSize = bb.getShort(7);
		    returnPacket = new ResultPacket(receivedPackets, unorderedPackets, totalGaps, maxGapSize, mainAct);
			break;
		default: 
                     ...
		}	
		return returnPacket;
	}

Wobei eben der Inhalt vom DatagramPacket packet "5, -46, 4 , ...." ist. Die 5 ist auch korrekt, das entspricht dem Typ RESULT.

Wisst ihr, was da schief läuft?
 

Kevin94

Top Contributor
Wieso, stimmt doch. Die Bytes -46 und 4 entsprechen der 1234 die gesendet wurden in Littel Endian Darstellung. Der Default für ByteBuffer ist Big Endian, also sollte folgender Code dir die richtigen Werte liefern:

Java:
public static DataPacket buildPacket(DatagramPacket packet, Activity mainAct) {
        DataPacket returnPacket = null;
        ByteBuffer bb = ByteBuffer.wrap(packet.getData());
// Du musst ihn nich neu anlegen
        bb.order(ByteOrder.LITTLE_ENDIAN);        

        // we need to check the first byte of the DatagramPacket to determine the type
        type = bb.get(0);
        // build a new DataPacket depending on the type
        switch (type) {
        case 1:
            ...
            break;
        case 2:
            ..
            break;
        case RESULT:
            short receivedPackets = bb.getShort(1);
            short unorderedPackets = bb.getShort(3);
            short totalGaps = bb.getShort(5);
            short maxGapSize = bb.getShort(7);
//man beachte hier das short in Java signed ist, wie jeder primitive Datentyp, ggf. durch folgenden Code ersetzen und als int speichern:
            int receivedPackets = bb.getShort(1) & 0xFFFF; //usw. 
            returnPacket = new ResultPacket(receivedPackets, unorderedPackets, totalGaps, maxGapSize, mainAct);
            break;
        default: 
                     ...
        }   
        return returnPacket;
    }
 
W

Wimme

Gast
Hi!

Danke dir abermals für deine Hilfe!
Wie kann man denn manuell sehen, dass -46 und 4 in Little Endian 1234 sind?

Die ByteOrder auf LITTLE_ENDIAN umzustellen, habe ich schon probiert. Dann wird aber noch nicht mal mehr der Typ richtig geschrieben.
Vielleicht hast du recht und es liegt daran, dass das in Java signed Dinger sind.

Wie genau funktioniert dieser Code denn dann:
Java:
int receivedPackets = bb.getShort(1) & 0xFFFF; //usw.
Du nimmst ja einen signed short, und davon nur die ersten acht Stellen oder? Was bringt das denn?

Außerdem, wenn ich die Sachen als Integer speichere, dann kriege ich wahrscheinlich auch in meine send-methode Probleme, oder?
Java:
	public static class ResultPacket extends DataPacket {
		private static final byte TYPE = SC_PACKET_RESULT;
	    private short receivedPackets;
	    private short unorderedPackets;
	    private short totalGaps;
	    private short maxGapSize;
...
		public boolean send(DatagramSocket socket, byte[] buffer, InetAddress address, int port) {
		    
	    	DatagramPacket send_packet;
	    	ByteArrayOutputStream bos = new ByteArrayOutputStream();
			DataOutputStream out;
			try {
					// send ACK packet
					out = new DataOutputStream(bos);
					// write the bytes, high byte first, into the DataOutputStream
					out.writeByte(ResultPacket.TYPE);
					out.writeShort(this.receivedPackets);
					out.writeShort(this.unorderedPackets);
					out.writeShort(this.totalGaps);
					out.writeShort(this.maxGapSize);
					// write bytes into buffer
					buffer = bos.toByteArray();
					// create the packet to send
					send_packet = new DatagramPacket(buffer, buffer.length, address, port);
					socket.send(send_packet);
			} catch (SocketTimeoutException e){
				return false;
			} catch(Exception e) { 
				this.getMainActivity().runOnUiThread(new UIThread("Failed to send Result packet: " + e, this.getMainActivity()));
				return false;
			}
			return true;
	    }}

Das ich hier high byte first schreibe, ist ja vermutlich auch falsch. Wie kann ich das denn hier ändern?
 
W

Wimme

Gast
sorry, dass dann noch nichtmal mehr der Typ richtig geschrieben wird, war Blödsinn!
Im Moment spinnt mein Internet leider so rum, dass ich gar nicht mehr vernünftig testen kann. Ich frage mich aber immer noch, wie das mit dem signed funktioniert und wie ich meine send Methode korrigieren kann..
 

Kevin94

Top Contributor
Java:
int receivedPackets = bb.getShort(1) & 0xFFFF;
Das ist einfach erkärt: Ein Short ist Vorzeichen behaftet, wenn man ihn zu einem int castet (Was hier impliziet geschieht) werden die führenden zwei Bytes mit 1'en gefüllt (aufgrund der darstellung im Zweierkomplement). Wenn man jetzt ein bitweises und mit 0xFFFF (das sind 16 bit, nicht 8) werden alle Bits der oberen zwei Bytes mit 0'en gefüllt und du hast den Ursprungswert als unsigned int.

Sehen kann man sowas z.B. mit der Programmiererfunktion des Taschenrechners von Windoof. Wenn du es in Java testen möchtest:
Java:
byte[] read=new byte[]{-46,4};
int unsignedValue=(read[0]&0xFF)|((read[1]&0xFF)<<8)

Am einfachsten wäre es aus Java Sicht, wenn du deinen C-Server so umstellst, das du Big Endian zum Lesen und Schreiben der Packete verwendest. Ansonsten würde ich zum erstellen des ByteArrays beim Senden einfach auch einen ByteBuffer verwenden.
 

Neue Themen


Oben