TCP "Threadsafe"?

Network

Top Contributor
Hi,

ich weiss nicht wie man es bei einer TCP-Server-Client-Verbindung sonst nennen soll, deshalb die vieleicht etwas komische Frage: Ist TCP Threadsafe?

Gemeint ist:
- Programm A Thread 1 sendet ein byte[]-Array der Länge 1000 an Programm B
- Programm A Thread 2 sendet ein byte[]-Array der Länge 1000 an Programm B

-> Kann Programm B davon ausgehen, dass die erhaltenen Daten von Thread 1 und 2 nicht vermischt wurden?
(Und wie sieht es mit UDP aus?)

Vielen Dank
Net
 

Bernd Hohmann

Top Contributor
Das Problem erübrigt sich dadurch, dass Du auf dem Server "B" einen ServerSocket laufen lässt und dort jedes .accept() einen eigenen Socket für den Empfang erzeugt.

Solltest Du aber innerhalb des Programms "A" in Thread 1+2 den gleichen Socket zu "B" nutzen, ist das nicht Threadsafe (soweit wie ich den Source kenne). Zwar wird das seriell abgearbeitet (auf der Ebene des zugrundeliegenden TCP/IP Stacks), aber spätestens bei der Rückgabe vom Server kann man nicht mehr auseinanderfusseln ob das nun die Antwort für Thread 1 oder Thread 2 ist.

Bei UDP ist es genauso.

Bernd
 
T

tuxedo

Gast
Die Frage ob das Thread-Safe ist oder nicht, kommt wohl in der Praxis auf den verwendeten Stream-Typ an und dessen write()-Methoden an. Der Standard OutputStream (OutputStream (Java 2 Platform SE v1.4.2)) synchronisiert die write()-Methode nicht:

java.io_OutputStream - Klasse:

Java:
    public void write(byte b[], int off, int len) throws IOException {
	if (b == null) {
	    throw new NullPointerException();
	} else if ((off < 0) || (off > b.length) || (len < 0) ||
		   ((off + len) > b.length) || ((off + len) < 0)) {
	    throw new IndexOutOfBoundsException();
	} else if (len == 0) {
	    return;
	}
	for (int i = 0 ; i < len ; i++) {
	    write(b[off + i]);
	}
    }

Wenn nun zwei Threads gleichzeitig byte[]s schreiben, so werden diese mit hoher wahrscheinlichkeit durcheinandergeworfen geschrieben.

D.h. es kommt dann drauf an was darunter (die betriebssystemabhängige JVM) oder darüber (eine Erweiterung des OutputStreams) liegt. Ich bezweifle aber dass unterhalb von OutputStream noch synchronisiert wird. Wenn du unbedingt synchronisieren willst, leite von OutputStream ab und synchronisier das ganze selbst. Ob das dann gut oder schlecht ist musst du selbst wissen.

- Alex
 

xote

Mitglied
Network hat gesagt.:
-> Kann Programm B davon ausgehen, dass die erhaltenen Daten von Thread 1 und 2 nicht vermischt wurden?
Wenn die beiden Threads unterschiedliche Verbindungen (Sockets) benutzen, dann ja. Wenn es die gleiche Verbindung ist, dann muss sich Programm B selbst drum kümmern was von Thread 1 und was von Thread 2 kommt. Eine TCP-Verbindung ist unique, es gibt

Network hat gesagt.:
(Und wie sieht es mit UDP aus?)
Bei UDP ist nichtmal die richtige Reihenfolge beim Empfang gewährleistet, von daher also nicht.
 

Bernd Hohmann

Top Contributor
Die Frage ob das Thread-Safe ist oder nicht, kommt wohl in der Praxis auf den verwendeten Stream-Typ an und dessen write()-Methoden an. Der Standard OutputStream (OutputStream (Java 2 Platform SE v1.4.2)) synchronisiert die write()-Methode nicht:

Wenn Source, dann den richtigen :)

Interessant ist, was die jeweilige SocketImpl für einen konkreten Stream abwirft. In der Regel ist das ein SocketOutputStream, der reicht write(byte[],int,int) direkt ans .socketWrite und von dort aus direkt ans native socketWrite0(..). Da der byte-Buffer nirgendwo angefasst wird und 1:1 an die drunterliegende C/C++ Library weitergegeben wird ist in Java noch alles im grünen Bereich (sofern man seinen buffer nicht gerade zwischen zwei Threads shared).

Ob jetzt die Socket-Library des OS einen Lock beim Schreiben setzt, darüber hab ich nichts gefunden - aber ich vermute mal stark, dass die writes irgendwo serialisiert werden,

Bernd
 

Network

Top Contributor
Naja geplant war eigentlich, dass ich kleine Datenpäckchen schicke.
Wenn bei einem Datenpäckchen das erste byte = 255 beträgt, soll eine Art Schnittstelle das auslesen, erkennen und nicht an das eigentliche Programm weitergeben. Die anderen nachfolgenden bytes sind dann bspw. eine IP-Addresse.

-> Dann würde ich das in etwas so verschicken:
write( new byte[]{ 255, 127, 0, 0, 1 } );

-> Nur wenn dann Thread 2 zum selben Zeitpunkt z.B. sowas verschickt:
write( new byte[]{ 42, 42, 42, 42, 42 } );

-> Sollte dann natürlich nicht am Empfänger beim auslesen, dass hier herauskommen:
read( new byte[5] ); = { 255, 42, 42, 127, 0 }

Ich denke das Problem sollte klar sein, die Zwischenstelle liest dann 255 aus als ersten Byte und denkt die nachfolgenden Daten würden eine IP-Addresse darstellen, dabei sind aber beim Threadparallelen 2. versenden 42er reingerutscht.

Dass bei UDP Datenpäckchen in der falschen Reihenfolge ankommen können und evt. danach sogar beschädigt sind, ist mir bekannt, nur so ein Päckchen sollte dann ja schon in einem Stück ankommen oder?
Und 2 Daten-Päckchen sich beim versenden nicht vermischen weil sie gleichzeitig ankommen, oder(?)

Net

[EDIT]
Danke für die Antworten aber ich glaube die Streifen teilweise an meinem Problem vorbei, deshalb nochmals erklärt.
[/EDIT]
 
Zuletzt bearbeitet:
T

tuxedo

Gast
@Bernd

Wo bitte waren meine Aussagen falsch?

Der zitierte Sourceabschnitt zeigt, dass byte[]s byte für byte gesendet werden. Dass "unten auf nativer Ebene" das Schreiben einzelner bytes in sich synchronisiert ist mag sein. Aber wenn man über die gezeigte write() Methode ganze Arrays sendet ist da nix synchronisiert. Dass einzelne Bytes synchronisiert werden ist toll, bringt einem aber nix wenn man mit mehreren Threads arbeitet und die eigenen "Pakete" größer als ein Byte sind (was wohl in 99,9% der Fälle der Fall sein wird). Nicht mehr und nicht weniger zeigt das Code-Zitat.
 

Network

Top Contributor
@tuxedo
Jetzt habe ich deinen Beitrag erst richtig verstanden was du damit aussagen wolltest, ich hab auf den Sourcecode geschaut und erstmal nicht viel gesehen gehabt.
Danke >.<
 

Bernd Hohmann

Top Contributor
Wo bitte waren meine Aussagen falsch?

Der zitierte Sourceabschnitt zeigt, dass byte[]s byte für byte gesendet werden.

Da bin ich auch mal drauf reingefallen :D

Da write(byte) die kleinste mögliche Einheit für einen Binary-Stream ist und diese Methode immer implementiert werden *muss*, liefert OutputStream eine Notlösung für den Fall, dass write(byte[],ofs,len) nicht überschrieben wurde.

Real schickt keine Implementation die bytes einzeln rüber (das ist schweinelangsam) sondern überschreibt die Blockmethode write(byte{], ofs, len) und Du bekommst vom Socket auch keinen OutputStream sondern einen java.net.SocketOutputStream extends java.io.FileOutputStream extends java.io_OutputStream.

Es kommt noch schlimmer: die native Implementation der Sockets kennt gar kein Wegschreiben eines einzelnen bytes sodass der Umweg über ein byte-array genommen werden muss.

Im SocketOutputStream sieht das dann so aus:
Code:
    public void write(int b) throws IOException {
        temp[0] = (byte)b;
        socketWrite(temp, 0, 1);
    }

    public void write(byte b[]) throws IOException {
        socketWrite(b, 0, b.length);
    }

    public void write(byte b[], int off, int len) throws IOException {
        socketWrite(b, off, len);
    }

    private void socketWrite(byte b[], int off, int len) throws IOException {
        [...]
        FileDescriptor fd = impl.acquireFD();
        try {
            socketWrite0(fd, b, off, len);
        } catch (SocketException se) {
        [...]
        }
    }

    private native void socketWrite0(FileDescriptor fd, byte[] b, int off, int len) throws IOException;
 
T

tuxedo

Gast
Tatsache. Dennoch bleiben die Symptome die gleichen: Das Schreiben eines byte[]s ist nicht synchronisiert :-( Wenn man das unbedingt haben möchte muss man das selbst tun.

Würde an und für sich aber eher vorschlagen ein Protokoll aufzusetzen das die zu sendenden Pakete kapselt und somit voneinander trennt. Dann kann jeder beliebige Thread Pakete "einreichen" welche vom Protokoll verpackt und mit einer ID oder sonstwas versehen werden, so dass die Empfängerseite eine Chance hat die Pakete ausseinander zu halten und vor allem zu wissen, welches Paket von welchem Thread kommt.

- Alex
 

Bernd Hohmann

Top Contributor
Tatsache. Dennoch bleiben die Symptome die gleichen: Das Schreiben eines byte[]s ist nicht synchronisiert :-(

Muss ja auch nicht synchronisiert sein (sofern ich das mit den Threads richtig in Erinnerung habe): bis auf socketWrite(byte) wird immer mit dem durchgereichten "byte buffer[]" gearbeitet, ansonsten arbeitet jede Methode mit ihren eigenen Variablen (dh. auf dem eigenen Methodenstack), und der ist zwangsläufig vom Stack der Methode eines anderen Threads unterschiedlich. Da muss also nichts synchronisiert werden.

In diesem ganz speziellen Fall ist also nicht ersichtlich, ob der Stream threadsafe ist weil es stark auf den zugrundeliegenden TCP/IP Stack ankommt.

Das ist aber eh nur eine akademische Diskussion, denn im Caller (also dem Programm vom TO) wird beim reinschreiben der Bytes in den Buffer aus anderen Gründen etwas zu synchronisieren sein (und wenns die Methode selber ist weil die globale Objekte verändert).

LG,
Bernd
 

Neue Themen


Oben