Re: proper way to close a socket?
Bill, this is a repost from google groups. My last post direct to the
group didn't seem to make it. I spent time on this so I don't want it
to waste.
On Mar 11, 8:40 pm, "Bill Brehm" <don't want spam> wrote:
Hector,
But do you think this is the cause of my particular crash / assert? In my
case I'm trying to connect to a socket that doesn't exist (for testing
purposes) and want the user to be able to break the connection attempt
without waiting for the 20 second timeout. Since the connection is never
made, there is no chance of data being sent and received so would half
closing the socket from the client side have an affect on the assert that
I'm getting (intermittently)?
If all you want is a faster response, you should already have the
asynchronous behavior in CAsyncSocket for pClientSocket->Connect()
with an error of WSAEWOULDBLOCK.
The solution is to take control of it during the time CAsyncSocket is
waiting the 20 secs. You can use the socket select() command to do
three things:
- set the new timeout
- dictate when it connects
- dictate when it fails or denied
You need something this:
if (!m_pClientSocket->Connect(host,port)) {
if (GetLastError() == WSAEWOULDBLOCK) {
//
// need to use a "select" here
// Wait X seconds
//
if (!m_pClientSocket->WaitConnect(5)) {
// CONNECT ERROR, PRINT MESSAGE
}
} else {
// CONNECT ERROR, PRINT MESSAGE
}
}
The wait block will use select() which allows you to detect read,
write and error events. In this case, you need two events:
write event - signals the connection is ready
error event - something went wrong
You can use a CMyAsyncSocket member function like this to handle it:
BOOL CMyAsyncSocket::WaitConnect(int nTimeout)
{
int nSleep = 100;
int nCountDown = nTimeout*1000 / nSleep;
for (;;) {
nCountDown--;
if (nCountDown <= 0) {
// user defined timeout
SetLastError(WSAETIMEDOUT);
return FALSE;
}
// prepare fd_set structures for write/error detects
fd_set efds; FD_ZERO(&efds); FD_SET(m_hSocket, &efds);
fd_set wfds; FD_ZERO(&wfds); FD_SET(m_hSocket, &wfds);
// set the timeout
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = nSleep*1000;
// check it
int rc = select(0, NULL, &wfds, &efds, &tv);
switch (rc) {
case 0:
// WE TIMED OUT!!, Yield, try again.
WindowsSlice(100);
break;
case SOCKET_ERROR:
if (GetLastError() != WSAEWOULDBLOCK) return FALSE;
break;
default:
if (FD_ISSET(m_hSocket,&wfds)) {
// WE CONNECTED!!
SetLastError(0);
return TRUE;
}
if (FD_ISSET(m_hSocket,&efds)) {
// WE FAILED
SetLastError(WSAEHOSTUNREACH);
return FALSE;
}
break;
}
}
return FALSE;
}
The only thing with this is that it will block your GUI, so you need
to be able to yield to the message pump. There are probably more
elegant ways here, but its quite simple just to use something like
this member function:
BOOL CMyAsyncSocket::WindowsSlice(DWORD nDelay)
{
DWORD nDone = (GetTickCount() + nDelay);
while (nDone > GetTickCount()){
Sleep(75);
MSG msg;
while (::PeekMessage(&msg,NULL,0,0,PM_REMOVE)){
::TranslateMessage(&msg);
::DispatchMessage(&msg);
}
}
return FALSE;
}
Stick it in the loop (like in the "WE TIMED OUT" block) and your gui
will be responsive. You could pass the dialog class as "this" to the
CMyAsyncSocket so you access maybe a "Abort" button or use some global
atomic ABORT flag. Whatever. :)