Non blocking socket


#1

Hi Jules,

I’ve met a problem with URL::createInputStream(). It’s been called in a thread, but if the server doesn’t reply, it’s blocked for a long time.
There is no way to specify a timeout, I guess because the default OS “connect” is used.

Could you implement a non blocking connect (below is my code for this under both windows and linux) so user can specify a time out.
I can do it myself, but to make sure it will be used.

Here’s the code:

#ifdef _WIN32
// Asynchronous Connect with timeout in ms
int Socket::Connect(const uint32 & lTimeout) const
{
	int ioNonBlock = 1, iRet = 0;
	// Set non blocking socket now
	if (ioctlsocket(mxSocket, FIONBIO, (u_long FAR*) &ioNonBlock) != 0) return SOCKET_ERROR;

	// Then attempt a connection
	if (connect(mxSocket, (const struct sockaddr *)&mxSockOut, sizeof(mxSockOut)) == SOCKET_ERROR)
	{
		// Failed to connect, check what happened
		if (WSAGetLastError() == WSAEWOULDBLOCK)
		{
			// Connection is quite long so check if we can wait it
			// Select stuff
			fd_set  fds;
			struct timeval tv_timeout;

			FD_ZERO(&fds); 
			FD_SET(mxSocket,&fds); 
			tv_timeout.tv_sec  =  lTimeout / 1000; 
			tv_timeout.tv_usec = 1000 * (lTimeout%1000);

			if (select(mxSocket + 1, NULL, &fds, NULL, &tv_timeout) <= 0)
			{
				// Socket doesn't got connection in the specified amount of time
				// Reset the blocking mode of the socket
				ioNonBlock = 0;
				if (ioctlsocket(mxSocket, FIONBIO, (u_long FAR*) &ioNonBlock) != 0) return SOCKET_ERROR;

				// Cannot contact server, so exit now
				return SOCKET_ERROR;
			}
		}
		else
		{
			// Reset the blocking mode of the socket
			ioNonBlock = 0;
			if (ioctlsocket(mxSocket, FIONBIO, (u_long FAR*) &ioNonBlock) != 0) return SOCKET_ERROR;
			// Cannot contact server, so exit now
			return SOCKET_ERROR;
		}
	}

	// Reset the blocking mode of the socket
	ioNonBlock = 0;
	if (ioctlsocket(mxSocket, FIONBIO, (u_long FAR*) &ioNonBlock) != 0) return SOCKET_ERROR;
	// Connection succeeded
	return 0;
}
#else
// Asynchronous Connect with timeout in ms
int Socket::Connect(const tULong & lTimeout) const
{
	int socketFlags = 0, iRet = 0;
	// Set non blocking socket now
	socketFlags = fcntl(mxSocket, F_GETFL, 0);
	if (socketFlags == -1) return SOCKET_ERROR;
	if (fcntl(mxSocket, F_SETFL, socketFlags | O_NONBLOCK) != 0) return SOCKET_ERROR;

	// Then attempt a connection
	if ((iRet = connect(mxSocket, (const struct sockaddr *)&mxSockOut, sizeof(mxSockOut))) < 0)
	{
		// Failed to connect, check what happened
		if (iRet == EINPROGRESS)
		{
			// Connection is quite long so check if we can wait it
			// Select stuff
			fd_set  fds;
			struct timeval tv_timeout;

			FD_ZERO(&fds); 
			FD_SET(mxSocket,&fds); 
			tv_timeout.tv_sec  =  lTimeout / 1000; 
			tv_timeout.tv_usec = 1000 * (lTimeout%1000);

			if (select(mxSocket + 1, NULL, &fds, NULL, &tv_timeout) <= 0)
			{
				// Socket doesn't got connection in the specified amount of time
				// Reset the blocking mode of the socket
				if (fcntl(mxSocket, F_SETFL, socketFlags) != 0) return SOCKET_ERROR;

				// Cannot contact server, so exit now
				return SOCKET_ERROR;
			}
		}
		else
		{
			// Reset the blocking mode of the socket
			if (fcntl(mxSocket, F_SETFL, socketFlags) != 0) return SOCKET_ERROR;
			// Cannot contact server, so exit now
			return SOCKET_ERROR;
		}
	}

	// Reset the blocking mode of the socket
	if (fcntl(mxSocket, F_SETFL, socketFlags) != 0) return SOCKET_ERROR;
	// Connection succeeded
	return 0;
}
#endif

// Non blocking receive on a connected socket
int Socket::Recv(char * buf, int len, int flags, uint32 lTimeOut) const
{
	// Select stuff
	fd_set  fds;
	struct timeval tv_timeout;
	
	FD_ZERO(&fds); 
	FD_SET(mxSocket,&fds); 
	tv_timeout.tv_sec  =  lTimeOut / 1000; 
	tv_timeout.tv_usec = 1000 * (lTimeOut%1000);

	if (select(mxSocket + 1, &fds, NULL, NULL, &tv_timeout) <= 0)
	{
		// No data arrived in time, return error
		return SOCKET_ERROR;
	}

	// Read the data and send it now
	return recv(mxSocket, buf, len, flags);
}

BTW, the recv should also be non blocking similarly.


#2

Nevermind,

I’ve seen the code is already here in Socket class, so I guess it’s not used in URL class.
Effectively, JUCE is using InternetConnect under Win32, which doesn’t let specify a timeout.

Is there a reason why you’re not using Sockets here ?


#3

I used InternetConnect because it was simpler than using sockets, but later did a socket-based implementation for mac/linux. Never had a problem with the win32 one, so didn’t think I should change it…


#4

Looking in InternetConnect API (which I didn’t know about before),
INTERNET_FLAG_ASYNC can be used in InternetOpen for asynchronous connection.
However using it would require breaking current code (juce_openInternetFile will have to be rewritten).

I guess there is no easy way to solve this issue. I guess the best cross platform way would be to rewrite the juce_openInternetFile & co to use the socket code (as linux doesn’t implement this at all).


#5