Click to See Complete Forum and Search --> : Asynchronous sockets?


logan
March 5th, 2006, 12:18 PM
I have a question regarding asynchronous sockets. I have a server and client application. The client selects a file and that file should be transmitted to the server. The client connects to the server starts reading the file and transmits its contents, the problem is how on the server side do I collect them? What I mean is that in case of asynchronus sockets the server is notified only when there is data to be recieved, in such a case recv () is thus non blocking, then how do i determine from where is the data coming? And how do I collect it?

On the server side I start a thread everytime a client connects but the recv () being non blocking immediately goes out and the thread ends. I tried something like this:

//The message handler which is called each time there is data to be recieved
HRESULT CASyncServerDlg::OnSocket (WPARAM wParam, LPARAM lParam)
{
sockaddr_in from;
int len = sizeof (from);
SOCKET client;
TCHAR buffer [MAX_PATH];
if (LOWORD (lParam) == FD_ACCEPT)
{
client= accept (server,
(struct sockaddr*)&from, &len);
sprintf (buffer, "Message from server! You are connected!");
send (client, buffer, _tcslen (buffer), 0);
AfxBeginThread (ClientThread, (LPVOID)client);
}
/*
if (LOWORD (lParam) == FD_READ)
{
int nRet = recv ((SOCKET)wParam, buffer, MAX_PATH, 0);
buffer [nRet] = 0;
m_ListBox.AddString (buffer);
}
*/
return 0;
}

//The thread function
UINT CASyncServerDlg::ClientThread (LPVOID pParam)
{
SOCKET client = (SOCKET)pParam;
TCHAR buffer [MAX_PATH];
CFile file;
file.Open ("C:\\file.dat", CFile::modeWrite | CFile::modeCreate);
int nRet = recv (client, buffer, MAX_PATH, 0);
do
{
file.Write (reinterpret_cast<void *>(buffer), MAX_PATH);
nRet = recv (client, buffer, MAX_PATH, 0);
}
while (nRet > 0);
file.Close ();
return 0;
}


Iam quite perplexed, looking for some directions now...

Regards.

MikeAThon
March 5th, 2006, 01:16 PM
You have a few issues with this code.

First, in the main thread, when a socket is accepted, it inherits the charactersitics of the listening socket. In your case, the listening socket is non-blocking, but since you want your clients to be hanfdled synchronously in a separate thread, you need them to be changed to blocking sockets. You can change the accepted socket to blocking with ioctlsocket, but you first need to call WSAAsyncSelect with NULL for the notifications, as described in the documentation.

Again in the main thread, your accepted SOCKET is a stack object that will go out-of-scope as soon as the OnAccept() function returns. Thus, the thread will not assuredly get a valid socket handle (although it might get a valid handle simply by chance, depending on how the compiler handles memory).

On the thread side, your "successful connection" method should probably be moved to the thread since it's now a blocking send that might stall the main thread.

The file.Write operations should not write MAX_PATH number of bytes, since this is not necessarily the amount of bytes that the recv() will give you. You should write nRet number of bytes instead.

Here's a quick idea of what I mean by the above:


//The message handler which is called each time there is data to be recieved
HRESULT CASyncServerDlg::OnSocket (WPARAM wParam, LPARAM lParam)
{
sockaddr_in from;
int len = sizeof (from);
//// SOCKET client;
TCHAR buffer [MAX_PATH];
if (LOWORD (lParam) == FD_ACCEPT)
{
SOCKET* pClient = new SOCKET;
*pClient= accept (server,
(struct sockaddr*)&from, &len);
WSAAsyncSelect( *pClient, hMainWnd, 0, 0 );
ulong blocking = 0;
ioctlsocket( *pClient, FIONBIO, &blocking );
//// sprintf (buffer, "Message from server! You are connected!");
//// send (client, buffer, _tcslen (buffer), 0);
AfxBeginThread (ClientThread, (LPVOID)pClient);
}
/*
/// you are correct that this code is not needed
if (LOWORD (lParam) == FD_READ)
{
int nRet = recv ((SOCKET)wParam, buffer, MAX_PATH, 0);
buffer [nRet] = 0;
m_ListBox.AddString (buffer);
}
*/

return 0;
}

//The thread function
UINT CASyncServerDlg::ClientThread (LPVOID pParam)
{
SOCKET client = *((SOCKET*)pParam);
TCHAR buffer [MAX_PATH];
sprintf (buffer, "Message from server! You are connected!");
send (client, buffer, _tcslen (buffer), 0);
CFile file;
file.Open ("C:\\file.dat", CFile::modeWrite | CFile::modeCreate);
int nRet = recv (client, buffer, MAX_PATH, 0);
do
{
file.Write (reinterpret_cast<void *>(buffer), nRet );
nRet = recv (client, buffer, MAX_PATH, 0);
}
while (nRet > 0);
file.Close ();
delete &client;
return 0;
}


You also need a lot of error checking (your code now has none).

Mike

logan
March 5th, 2006, 02:10 PM
Thank you very much. It was very helpful.

since you want your clients to be hanfdled synchronously in a separate thread

Is there a way by which I can achieve the above scenariao via asynchronous sockets?

Regards.

philkr
March 5th, 2006, 02:19 PM
Is there a way by which I can achieve the above scenariao via asynchronous sockets?
You can use WSAEventSelect(). This is the initialization:

DWORD dwThreadID;
WSAEVENT event = WSACreateEvent();
WSAEventSelect(socket, event, FD_READ | FD_CLOSE);
HANDLE hThread = CreateThread(NULL, 0, &EventThread, (LPVOID)event, 0, &dwThreadID);
CloseHandle(hThread);

This is the event thread:
DWORD WINAPI EventThread(LPVOID lpParameter)
{
WSAEVENT hNetEvent = (WSAEVENT)lpParameter;
while(true)
{
WSANETWORKEVENTS dNetworkEvents;

WSAWaitForMultipleEvents(1, &hNetEvent, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(socket, hNetEvent, &dNetworkEvents);
if(dNetworkEvents.lNetworkEvents == FD_READ)
OnReceive();
else if(dNetworkEvents.lNetworkEvents == FD_CLOSE)
{
OnClose();
break;
}
}
return 0;
}

MikeAThon
March 5th, 2006, 03:27 PM
..Is there a way by which I can achieve the above scenariao via asynchronous sockets?
Besides using event-based sockets (i.e., using WSAEventSelect in a separate thread), you can use ordinary message-based sockets (i.e., WSAAsyncSelect) in your main thread without using a worker thread.

Use this architecture only if you do not expect too many simultaneous connections to your server. It's the second-least scalable solution for a server. However, for around less than 100 simultaneous connections tot he server, it's probably fine.

Look at the CHATSRVR example in the FAQ: "Where can I find examples of socket programs?" at http://www.codeguru.com/forum/showthread.php?t=326666

Mike

MikeAThon
March 5th, 2006, 03:34 PM
You can use WSAEventSelect(). This is the initialization:

DWORD dwThreadID;
WSAEVENT event = WSACreateEvent();
WSAEventSelect(socket, event, FD_READ | FD_CLOSE);
HANDLE hThread = CreateThread(NULL, 0, &EventThread, (LPVOID)event, 0, &dwThreadID);
CloseHandle(hThread);

This is the event thread:
DWORD WINAPI EventThread(LPVOID lpParameter)
{
WSAEVENT hNetEvent = (WSAEVENT)lpParameter;
while(true)
{
WSANETWORKEVENTS dNetworkEvents;

WSAWaitForMultipleEvents(1, &hNetEvent, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(socket, hNetEvent, &dNetworkEvents);
if(dNetworkEvents.lNetworkEvents == FD_READ)
OnReceive();
else if(dNetworkEvents.lNetworkEvents == FD_CLOSE)
{
OnClose();
break;
}
}
return 0;
}


It's possible for more than one network event to be signalled at one time, i.e., WSAEnumNetworkEvents will return a WSANETWORKEVENTS structure with more than one event signalled.

So, do not check it with a "if -else if" logic, since that would detect only the first of possibly several network events. Use a sequence of plain "if" statments:

WSAWaitForMultipleEvents(1, &hNetEvent, FALSE, WSA_INFINITE, FALSE);
WSAEnumNetworkEvents(socket, hNetEvent, &dNetworkEvents);

if( (dNetworkEvents.lNetworkEvents & FD_READ )== FD_READ)
{
OnReceive();
}

//// else <--- delete this
if ( (dNetworkEvents.lNetworkEvents & FD_CLOSE )== FD_CLOSE)
{
OnClose();
}

if ( (dNetworkEvents.lNetworkEvents & FD_WRITE ) == FD_WRITE)
{
OnSend();
}

//// etc.



Mike

philkr
March 6th, 2006, 04:50 AM
Thanks for the correction, Mike!

logan
March 6th, 2006, 09:26 AM
Thanks a lot guys. I will get back as the need be.

Regards.