Ajustando HttpWebRequest Connection Timeout en C #

Creo que después de una larga investigación y búsqueda, descubrí que lo que quiero hacer probablemente sea más útil configurando una conexión asincrónica y finalizándola después del tiempo de espera deseado … ¡Pero seguiré y preguntaré de todos modos!

Fragmento de código rápido:

HttpWebRequest webReq = (HttpWebRequest)HttpWebRequest.Create(url); webReq.Timeout = 5000; HttpWebResponse response = (HttpWebResponse)webReq.GetResponse(); // this takes ~20+ sec on servers that aren't on the proper port, etc. 

Tengo un método HttpWebRequest que está en una aplicación multiproceso, en la que me estoy conectando a una gran cantidad de servidores web de la empresa. En los casos en que el servidor no responde, la HttpWebRequest.GetResponse() tarda unos 20 segundos en HttpWebRequest.GetResponse() , aunque he especificado un tiempo de espera de solo 5 segundos. Con el interés de pasar por los servidores en un intervalo regular, quiero omitir aquellos que tardan más de 5 segundos para conectarse.

Entonces, la pregunta es: “¿Existe una manera simple de especificar / disminuir el tiempo de espera de una conexión para una WebRequest o HttpWebRequest?”

Creo que el problema es que WebRequest mide el tiempo solo después de que se realiza realmente la solicitud. Si envía varias solicitudes a la misma dirección, ServicePointManager acelerará sus solicitudes y solo enviará tantas conexiones simultáneas como el valor del ServicePoint.ConnectionLimit correspondiente que de forma predeterminada obtiene el valor de ServicePointManager.DefaultConnectionLimit . El host CLR de la aplicación establece esto en 2, el host ASP en 10. Entonces, si tiene una aplicación multiproceso que envía múltiples solicitudes al mismo host, solo dos se colocan en el cable, el rest se pone en cola.

No he investigado esto con una evidencia concluyente sobre si esto es realmente lo que sucede, pero en un proyecto similar tuve cosas horribles hasta que ServicePoint limitación de ServicePoint .

Otro factor a considerar es el tiempo de búsqueda de DNS. Una vez más, mi creencia no está respaldada por pruebas sólidas, pero creo que WebRequest no cuenta el tiempo de búsqueda de DNS con el tiempo de espera de la solicitud. El tiempo de búsqueda de DNS puede aparecer como factor de tiempo muy grande en algunas implementaciones.

Y sí, debe codificar su aplicación en WebRequest.BeginGetRequestStream (para POST con contenido) y WebRequest.BeginGetResponse (para GET y POSTS s). Las llamadas sincrónicas no se escalarán (no entraré en detalles por qué, pero tengo pruebas fehacientes). De todos modos, el problema de ServicePoint es ortogonal a esto: el comportamiento de cola también ocurre con las llamadas asincrónicas.

Lo siento por utilizar un hilo antiguo, pero creo que algo que se dijo anteriormente puede ser incorrecto / engañoso.

Por lo que puedo decir. Timeout NO es el tiempo de conexión, es el tiempo total permitido durante toda la vida de HttpWebRequest y la respuesta. Prueba:

Lo puse:

 .Timeout=5000 .ReadWriteTimeout=32000 

El tiempo de conexión y publicación de HttpWebRequest tomó 26ms

pero la llamada subsiguiente HttpWebRequest.GetResponse () excedió el tiempo de espera en 4974ms, lo que demuestra que 5000 ms era el límite de tiempo para toda la solicitud de envío / obtener un conjunto de llamadas de respuesta.

No verifiqué si la resolución del nombre DNS se midió como parte del tiempo, ya que esto es irrelevante para mí, ya que nada de esto funciona de la manera que realmente necesito que funcione; mi intención era agotar el tiempo de conexión más rápido cuando me conectaba a sistemas que no aceptaban conexiones como las mostradas fallando durante la fase de conexión de la solicitud.

Por ejemplo: estoy dispuesto a esperar 30 segundos en una solicitud de conexión que tenga una posibilidad de devolver un resultado, pero solo deseo grabar 10 segundos esperando enviar una solicitud a un host que se está portando mal.

Algo que encontré más tarde que ayudó, es la propiedad .ReadWriteTimeout . Esto, además de la propiedad .Timeout , parecía finalmente reducir el tiempo que los hilos pasarían tratando de descargar desde un servidor problemático. El tiempo predeterminado para .ReadWriteTimeout es de 5 minutos, lo que para mi aplicación fue demasiado largo.

Entonces, me parece a mí:

.Timeout = tiempo dedicado a intentar establecer una conexión (sin incluir el tiempo de búsqueda) .ReadWriteTimeout = tiempo dedicado a intentar leer o escribir datos una vez establecida la conexión

Más información: HttpWebRequest.ReadWriteTimeout Property

Editar:

Por el comentario de Per @ KyleM, la propiedad Timeout es para todo el bash de conexión, y la lectura en MSDN muestra:

El tiempo de espera es el número de milisegundos que una solicitud síncrona posterior realizada con el método GetResponse espera una respuesta, y el método GetRequestStream espera una transmisión. El tiempo de espera se aplica a toda la solicitud y respuesta, no individualmente a las llamadas al método GetRequestStream y GetResponse. Si el recurso no se devuelve dentro del período de tiempo de espera, la solicitud arroja una WebException con la propiedad Estado establecida en WebExceptionStatus.Timeout.

(Énfasis mío)

De la documentación de la propiedad HttpWebRequest.Timeout:

Una consulta del Sistema de nombres de dominio (DNS) puede demorar hasta 15 segundos para regresar o expirar. Si su solicitud contiene un nombre de host que requiere resolución y establece Timeout en un valor de menos de 15 segundos, puede tomar 15 segundos o más antes de que se genere una WebException para indicar un tiempo de espera excedido en su solicitud.

¿Es posible que su consulta DNS sea la causa del tiempo de espera?

No importa lo que hayamos intentado, no logramos obtener el tiempo de espera por debajo de 21 segundos cuando el servidor que estábamos verificando no funcionaba.

Para solucionar este problema, combinamos una comprobación de TcpClient para ver si el dominio estaba activo, seguido de una comprobación por separado para ver si la URL estaba activa.

 public static bool IsUrlAlive(string aUrl, int aTimeoutSeconds) { try { //check the domain first if (IsDomainAlive(new Uri(aUrl).Host, aTimeoutSeconds)) { //only now check the url itself var request = System.Net.WebRequest.Create(aUrl); request.Method = "HEAD"; request.Timeout = aTimeoutSeconds * 1000; var response = (HttpWebResponse)request.GetResponse(); return response.StatusCode == HttpStatusCode.OK; } } catch { } return false; } private static bool IsDomainAlive(string aDomain, int aTimeoutSeconds) { try { using (TcpClient client = new TcpClient()) { var result = client.BeginConnect(aDomain, 80, null, null); var success = result.AsyncWaitHandle.WaitOne(TimeSpan.FromSeconds(aTimeoutSeconds)); if (!success) { return false; } // we have connected client.EndConnect(result); return true; } } catch { } return false; }