When writing the socket program with blocking socket functions, in most cases you need to start a thread to accept connection or write and read data.
Here are basic ways to end a thread:
1, Use global variable as a flag to end loop. The variable can be the SOCKET, like
while (sock != INVALID_SOCKET) …
2, Use event or mutex. Just use WaitForSingleObject();
3, call TerminateThread() by force.
In socket thread, the thread may be blocked, it’s not easy to terminate thread gracefully. “Graceful” or “elegantly”, I love those words. Don’t always use the TerminateThread, it’s too violent. Here I will introduce two ways to terminate blocking socket thread gracefully.
1. close the socket out of thread
we need to keep the socket object, usually we restore them in a list. when we call closesocket(),Any pending blocking, asynchronous calls issued by any thread in this process are canceled. The steps are:
a) shot down and close socket out of thread. Shutdown(), closesocket().
b) Sleep for a while, to give the socket thread a chance to end.
c) Blocking function then return INVALID_SOCKET when the socket is closed. End thread.
d) Wait for the socket, here, the thread should end by itself,
e) If wait timeout, terminate it. TerminateThread().
f) Close thread hancle.
Notes:
a) Don’t close the socket in thread.
b) Just use the SOCKET handle as a flag to control loop.
c) You need to restore the socket and thread handle
d) It’s strange, by calling BIO_free() can’t terminate the blocking socket function.
Here is the code:
void ECMG::stopListen() { if (m_listenSock != INVALID_SOCKET) { // SOCKET m_listenSock
shutdown(m_listenSock, SD_BOTH); closesocket(m_listenSock); m_listenSock = INVALID_SOCKET;
Sleep(60); // give the thread a chance to end if (m_listenThread != NULL) {
// wait the thread to end if (WAIT_TIMEOUT == WaitForSingleObject(m_listenThread, 2000)) {
// time out TerminateThread(m_listenThread, 0); } CloseHandle(m_listenThread); m_listenThread = NULL; } } } |
2. Use global variable to terminate socket thread
In the blocking thread, in a loop, use select() to detect if there is any accept, read or write request. We can use a global variable such as socket object to control the loop. So if wanna terminate the thread, just turn on the flag. Obviously it’s not as so efficient.
Using steps:
a) Setup fd_set
b) Check the loop flag in a loop
c) Call select(), if there is data, then process it.
notes:
before calling select(), you need to re initial the fd_set.
Here is the code:
#define DEFAULT_WAIT_TIME 100 DWORD WINAPI ECMG::rwThread(LPVOID lpParameter) { ECMG *pthis = (ECMG *)lpParameter; BIO *cbio = pthis->m_bClient; int iLen; char buf[SO_MAX_MSG_SIZE] = {0}; unsigned long ulthID = GetCurrentThreadId(); fd_set fdRead = {0}; TIMEVAL stTime; TIMEVAL *pstTime = NULL;
SOCKET s = (SOCKET)BIO_get_fd(cbio, 0); if (INVALID_SOCKET == s) return 0;
stTime.tv_sec = DEFAULT_WAIT_TIME / 1000; stTime.tv_usec = DEFAULT_WAIT_TIME % 1000; pstTime = &stTime; FD_SET(s, &fdRead);
while(cbio) { // notes that we need to initial fd_set every time. if (!FD_ISSET(s, &fdRead)) FD_SET(s, &fdRead); int res = select(s+1, &fdRead, NULL, NULL, pstTime); if (res > 0) {
memset(buf, 0, SO_MAX_MSG_SIZE); iLen = BIO_read(cbio, buf, SO_MAX_MSG_SIZE); if (0 == len || -1 == len) {
::PostMessage(pthis->m_hWnd, MSG_THREAD_EVENT, EVT_CONDROP, (LPARAM)cbio); break; } // process the message pthis->OnReceiveData(cbio, buf, len); } Sleep(0); }
return 0; } |