Este código funciona, aumentando los tiempos de espera cuando se esperaba (que es una versión modificada del ejemplo he vinculado en la pregunta):
// copied from Mono, because CF lacks this enum
enum SocketError
{
IOPending = 997,
NoBufferSpaceAvailable = 10055,
TimedOut = 10060,
WouldBlock = 10035
}
// milliseconds
int receiveTimeout = 20000;
int sendTimeout = 20000;
public override int Read(byte[] buffer, int offset, int size)
{
int startTickCount = Environment.TickCount;
int received = 0;
do
{
List<Socket> sock = new List<Socket>(new Socket[] {socket});
Socket.Select(sock, null, null, receiveTimeout*1000 + 1);
if (Environment.TickCount > startTickCount + receiveTimeout)
throw new SocketException((int) SocketError.TimedOut);
try
{
received += socket.Receive(buffer, offset + received,
size - received, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.ErrorCode == (int) SocketError.WouldBlock ||
ex.ErrorCode == (int) SocketError.IOPending ||
ex.ErrorCode == (int) SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably empty, wait and try again
Thread.Sleep(30);
}
else
throw; // any serious error occurr
}
} while (received < size);
return received;
}
public override void Write(byte[] buffer, int offset, int size)
{
int startTickCount = Environment.TickCount;
int sent = 0;
do
{
List<Socket> sock = new List<Socket>(new Socket[] {socket});
Socket.Select(null, sock, null, sendTimeout*1000 + 1);
if (Environment.TickCount > startTickCount + sendTimeout)
throw new SocketException((int) SocketError.TimedOut);
try
{
sent += socket.Send(buffer, offset + sent,
size - sent, SocketFlags.None);
}
catch (SocketException ex)
{
if (ex.ErrorCode == (int) SocketError.WouldBlock ||
ex.ErrorCode == (int) SocketError.IOPending ||
ex.ErrorCode == (int) SocketError.NoBufferSpaceAvailable)
{
// socket buffer is probably full, wait and try again
Thread.Sleep(30);
}
else
throw; // any serious error occurr
}
} while (sent < size);
}
El elemento crucial que falta en el ejemplo que he encontrado es Socket.Select(IList checkRead, IList checkWrite, IList checkError, int microSeconds)
. Tenga en cuenta que este método puede modificar la lista que se le pasa (es por eso que mi código crea uno nuevo cada vez) y mide el tiempo en microsegundos en lugar de milisegundos. Y recuerde utilizar Environment.TickCount
(que es monotonic time source) en lugar de DateTime.Now
para medir el tiempo.
¿Para qué sirven las llamadas Socket.Select? ¿Por qué no simplemente Enviar/Recibir inmediatamente y luego dormir más tiempo (por ejemplo, 500 ms) cuando se lanza una excepción? –
Socket.Select asegura que la siguiente operación no se bloqueará. Si no estuviera presente, toda la cosa aún esperaría infinitamente. – skolima
socket.Poll() es una opción un poco más simple, si no necesita esperar múltiples sockets. –