Mehrere Verbindungen gleichzeitig in einem Thread mit ApacheHTTP

7bestman

Mitglied
Hallo,
ich versuche gerad mehrere Verbindungen in einem Thread mit httpcore-nio zu verarbeiten. Ich hab mich an dem Beispiel http://hc.apache.org/httpcomponents...org/apache/http/examples/nio/NHttpClient.java entlang gehangelt, jedoch werden meine Anfragen nacheinander abgearbeitet. Das sieht dann so aus:
Code:
[HTTP] attempting to open http://127.0.1.2:80/bild.zip
[HTTP] attempting to open http://127.0.2.3:80/bild.zip
[HTTP] attempting to open http://127.0.0.1:80/bild.zip
[HTTP] HTTP/1.1 200 OK
[HTTP] Request finished
[HTTP] HTTP/1.1 200 OK
[HTTP] Request finished
[HTTP] HTTP/1.1 200 OK
[HTTP] Request finished

Die Datei ist jeweils etwa 1mb groß, also sollte sie eigentlich einen Moment für die Übertragung benötigen. Verwende ich mehrere Threads, werden auch mehrere Dateien parallel übertragen.

Ich verstehe nicht, warum das nicht funktioniert! 🙁
Kann mir da jemand helfen?

Gruß,
Olli

Java:
import java.io.IOException;
import java.io.InterruptedIOException;

import org.apache.http.HttpRequestInterceptor;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.nio.DefaultHttpClientIODispatch;
import org.apache.http.impl.nio.pool.BasicNIOConnPool;
import org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.client.methods.AsyncByteConsumer;
import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.nio.protocol.HttpAsyncRequestExecutor;
import org.apache.http.nio.protocol.HttpAsyncRequester;
import org.apache.http.nio.reactor.IOReactorException;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.SyncBasicHttpParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.HttpProcessor;
import org.apache.http.protocol.ImmutableHttpProcessor;
import org.apache.http.protocol.RequestConnControl;
import org.apache.http.protocol.RequestContent;
import org.apache.http.protocol.RequestExpectContinue;
import org.apache.http.protocol.RequestTargetHost;
import org.apache.http.protocol.RequestUserAgent;

public class HTTPClient {
	private BasicNIOConnPool pool;
	private HttpAsyncRequester requester;
	private DefaultConnectingIOReactor ioReactor;
	private DefaultHttpClientIODispatch ioEventDispatch;

	public HTTPClient() throws IOException {
		init();
	}

	private void init() throws IOReactorException {
		// HTTP parameters for the client
		HttpParams params = new SyncBasicHttpParams();
		params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 3000)
				.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 3000)
				.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 32 * 1024)
				.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
				.setParameter(CoreProtocolPNames.USER_AGENT, "Test/1.1");

		// Create HTTP protocol processing chain
		HttpProcessor httpproc = new ImmutableHttpProcessor(new HttpRequestInterceptor[] {
				// Use standard client-side protocol interceptors
				new RequestContent(), new RequestTargetHost(), new RequestConnControl(), new RequestUserAgent(),
				new RequestExpectContinue() });

		// Create client-side HTTP protocol handler
		HttpAsyncRequestExecutor protocolHandler = new HttpAsyncRequestExecutor();

		// Create client-side I/O event dispatch
		ioEventDispatch = new DefaultHttpClientIODispatch(protocolHandler, params);

		// Create client-side I/O reactor
		IOReactorConfig config = new IOReactorConfig();
		config.setIoThreadCount(1);

		ioReactor = new DefaultConnectingIOReactor(config);

		// Create HTTP connection pool
		pool = new BasicNIOConnPool(ioReactor, params);
		pool.setDefaultMaxPerRoute(8);
		pool.setMaxTotal(64);

		requester = new HttpAsyncRequester(httpproc, new DefaultConnectionReuseStrategy(), params);
	}

	public void execute(HttpGet request, AsyncByteConsumer<Boolean> consumer) {
		requester.execute(HttpAsyncMethods.create(request), consumer, pool, new BasicHttpContext());
	}

	public void start() {
		new Thread(new Runnable() {
			public void run() {
				try {
					ioReactor.execute(ioEventDispatch);
				} catch (InterruptedIOException ex) {
					System.err.println("[HTTP] Interrupted");
				} catch (IOException e) {
					System.err.println("[HTTP] I/O error: " + e.getMessage());
				}

				System.out.println("[HTTP] Shutdown");
			}
		}).start();
	}

	public void stop() {
		try {
			ioReactor.shutdown();
		} catch (IOException e) {
			// ignore
		}
	}
}
 
Und wenn ich das ganze mit einem DefaultHTTPAsyncClient mache, macht er jede Anfrage in einem anderen Thread:

Java:
import java.io.IOException;

import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.nio.client.DefaultHttpAsyncClient;
import org.apache.http.nio.client.methods.AsyncByteConsumer;
import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.params.CoreConnectionPNames;

public class HTTPClient {
	private DefaultHttpAsyncClient httpClient;

	public HTTPClient() throws IOException {
		httpClient = new DefaultHttpAsyncClient();
		httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 3000)
				.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 3000)
				.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 32 * 1024)
				.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);

		httpClient.start();
	}

	public void execute(HttpGet request, AsyncByteConsumer<Boolean> consumer) {
		httpClient.execute(HttpAsyncMethods.create(request), consumer, null);
	}
}

:/ Warum kann Java nicht so einfach wie Python sein?
 
Grundsätzlich gilt : wenn du etwas "parallel" machen willst brauchst du erstmal grundsätzlich Threads. NIO alleine reicht da leider nicht für (das N steht für Non-blocking).
Ich kenne mich zwar nicht mit Apache Commons aus, aber Zeile 63 sieht schon mal verdächtig aus. Wie soll er denn etwas parallel mit mehreren Threads ausführen wenn du ihm ein Thread-Limit von "1" vorschreibst. Ich würde erstmal versuchen was passiert wenn dieser Wert erhöt wird.
Andernfalls ganz einfach mit der SE-API : für jeden Request einfach einen neuen Thread erstellen und darin ablaufen lassen.
 
Grundsätzlich gilt : wenn du etwas "parallel" machen willst brauchst du erstmal grundsätzlich Threads. NIO alleine reicht da leider nicht für (das N steht für Non-blocking).
Ich kenne mich zwar nicht mit Apache Commons aus, aber Zeile 63 sieht schon mal verdächtig aus. Wie soll er denn etwas parallel mit mehreren Threads ausführen wenn du ihm ein Thread-Limit von "1" vorschreibst. Ich würde erstmal versuchen was passiert wenn dieser Wert erhöt wird.
Andernfalls ganz einfach mit der SE-API : für jeden Request einfach einen neuen Thread erstellen und darin ablaufen lassen.

Das ist nicht ganz richtig. Um I/O "parallel" auszuführen, benötigt man nur einen einzigen Thread, wenn man eventbasiert arbeitet. Du kannst dem Betriebssystem sagen: "Hier hast du 10 Filedescriptoren, schau du mal bis ich irgendwo von lesen kann und dann sag mir Bescheid, von welchem ich blockierungsfrei lesen kann"
Mehr Infos z.B. hier: Asynchronous I/O - Wikipedia, the free encyclopedia

Das N in NIO steht laut Wikipedia auch nciht für non-blocking sondern für "New IO".

Das ganze Problem hat sich erledigt. Das funktioniert ziemlich genau wie in meinem zweiten Stück Code geschrieben. Meine Übertragung ging nur wirklich zu schnell. Habe es mit einem langsamen Server und größerer Datei probiert, und alles ist super!

Was nun noch schön wäre, wenn ich den HTTPClient im EventDispatcher-Thread einfach 20-30 mal pro Sekunde für ein paar Milisekunden laufen lassen könnte. Dann entfällt nämlich jegliches Synchronisieren mit der UI, weil das komplette Programm sexy in nur einem Thread laufen kann.

Java:
import java.io.IOException;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.impl.nio.client.DefaultHttpAsyncClient;
import org.apache.http.impl.nio.conn.PoolingClientAsyncConnectionManager;
import org.apache.http.impl.nio.reactor.IOReactorConfig;
import org.apache.http.nio.client.methods.AsyncByteConsumer;
import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.params.CoreConnectionPNames;

public class HTTPClient {
	private DefaultHttpAsyncClient httpClient;

	public HTTPClient() throws IOException {
		IOReactorConfig ioReactorConfig = new IOReactorConfig();
		ioReactorConfig.setIoThreadCount(1);

		httpClient = new DefaultHttpAsyncClient(ioReactorConfig);
		httpClient.getParams().setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 3000)
				.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 3000)
				.setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 32 * 1024)
				.setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true);

		PoolingClientAsyncConnectionManager mgr = (PoolingClientAsyncConnectionManager) httpClient
				.getConnectionManager();
		mgr.setDefaultMaxPerRoute(16);
		mgr.setMaxTotal(64);

		httpClient.start();
	}

	public void execute(HttpGet request, AsyncByteConsumer<Boolean> consumer) {
		httpClient.execute(HttpAsyncMethods.create(request), consumer, null);
	}

	public void execute(String url, FutureCallback<HttpResponse> future) {
		httpClient.execute(new HttpGet(url), future);
	}
}
 
"I/O-OPs im EDT" ... AUA ... da wird mir schon beim lesen schlecht. Denn DAS sollte man in Java GRUNDSÄTZLICH vermeiden. I/O-OPs gehören IMMER in einen eigenen Thread. Was du dann in diesem machst ist eigentlich ziemlich egal, aber den EDT mit I/O-OPs zu belasten ist einfach extrem schlechtes Design. Mal davon abgesehen das Java auch nicht "in nur einem Thread" läuft. Es werden gleich zum Start der VM mehreren deamon-Thredas gestartet. Der einzige non-deamon-Thread zum Zeitpunkt des "Starts" ist der "main-Thread" der public static void main(String[]) callt. Zumindest diesen Fehler solltest du dringend beheben.
 
Das ist ja Quatsch. Das Ding heißt Event Dispatcher Thread weil dort Events verarbeitet und weitergehen werden. Das selbe passiert auch beim eventbasierten IO. Warum soll ich nun zwei EDT laufen haben und hin und her synchronisieren müssen? Solange ich nichts blockierenden ausführe, ist doch alles super. und eventbasiertes IO ist entsprechend nicht blockierend.
 
Ich enthalte mich weiteren Anmerkungen zu diesem Design-Fehler.
Vielleicht noch folgendes : der EDT ist ausschließlich für die Verarbeitung von User-Inputs sowie die Manipulation der GUI verantwortlich. Alles andere hat NICHTS im EDT zu suchen, auch kein non-blocking-I/O (wie gesagt : I/O hat sowieso nichts im EDT verloren).
 
Java:
void onButtonClick() {
    http.open("http://example.com/changelog.txt", new ResponseHandler() {
        public void onResponse(String text) {
            changelog.setText(text);
        }
    });
}

ist ja wohl 10 mal hübscher als Code, der ständig explizit zwischen
den Threads hin und her synchronisieren muss.
Java:
void onButtonClick() {
    runOnUiThread(new Runnable() {
        public void run() {
            final String text = httpFetch("http://example.com/changelog.txt");
            runOnUiThread(new Runnable() {
                public void run() {
                    changelog.setText(text);
                }
            });
        }
    });
}

Und besonders doof wird es, wenn die UI und der IO Thread auch noch gemeinsam
auf eine Daten-Struktur zugreifen wollen, die dann erstmal ge_lockt_ werden
muss. dann sind wir nämlich wieder dabei, etwas blockierendes im UI Thread
zu machen. Hübsch? Nein.

Ein Blick in die Beschreibung zu Concurrency in Swing [1] erzählt im übrigens
nichts darüber, dass ausschließlich User-Inputs verarbeitet werden dürfen.
Es steht sogar explizit da: "Application code can schedule additionals tasks
on the event dispatch thread (if they complete quickly,"
Außerdem nehmen sich die Events von event-basiertem IO und User-Input
Events nichts. (Streng genommen sind die auf einem Unix-System mit
X-Server sogar die gleichen, da die X-Events auch von einem Socket gelesen
werden können, über das mit dem X-Server kommuniziert wird).

Bei GLIB und GTK wurde das meiner Meinung nach richig gemacht. Dort gibt
es nur einen Eventloop, den glib MainLoop, und es werden neben dem
GTK Toolkit, die den Eventloop verwendet auch IO-Operationen [2] angeboten,
die mit dem gleichen Eventloop laufen,

Wenn du mir nun also mal erzählen könntest, "warum" Event basiertes IO
deiner Meinung nach nichts im EDT zu suchen hat, dann wär das mal schön.

[1] Initial Threads (The Java™ Tutorials > Creating a GUI With JFC/Swing > Concurrency in Swing)
[2] IO Channels
 

Zurück
Oben