Click to See Complete Forum and Search --> : WSAAsyncSelect with IOCP for client


keenlearner
June 30th, 2007, 10:14 AM
I am learning to build a mini web robot for my own interest, I want to make many concurrent request to many different servers at the same time. So I found the way by creating a socket for each server.

I used both WSAAsyncSelect and IO completion port to do different task. For every created socket, I associate with WSAAsyncSelect with the last parameter FD_CONNECT, because I want to get the connected notification only. After I get notification of connected socket, I associate the socket with IOCP with CreateIoCompletionPort() to get the notifications of the IO operation, this association is done in the FD_CONNECT which is at the window procedure. My problem is :

Window Procedure receive the data about socket in wParam, error in WSAGETSELECTERROR(lParam), event in WSAGETSELECTEVENT(lParam). I need more data like URL, Domain name to fill up the PER_HANDLE_DATA structure.

see the implementation below, which I tried to summarize as simple as possible.


#include <winsock2.h>
#include <stdlib.h>
#include <stdio.h>
#include <string>
#include <windows.h>

char g_debug[200]; // for debugging

typedef struct _PER_HANDLE_DATA
{
SOCKET serverSocket;
SOCKADDR_IN serverAddr;
char * host;
char * filename;
char debug[100];
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

int getNoOfProcessor();
DWORD WINAPI workerThread(LPVOID);

HANDLE g_hCompletionPort;
HWND g_hwnd;
char g_className[] = "WindowClass";

int WINAPI WinMain(
HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpCmdLine,
int nShowCmd
)
{
hwnd = CreateWindow(); //assume window has been created

WSADATA wsaData;
int nResult;
HOSTENT *pHost;
LPPER_HANDLE_DATA lpPerHandleData;
char buff[BUFF_SIZE];
int i;
std::string str;

char domain[] = "www.google.com";
char filename[] = "/news/";

nResult = WSAStartup(MAKEWORD(2,2),&wsaData);

if(nResult != NO_ERROR)
{
printf("WSAStartup error");
return 0;
}

for(i =0; i<getNoOfProcessor(); i++)
{
hWorkerThread = CreateThread(NULL, 0, workerThread, g_hCompletionPort, 0, NULL);

// close thread handle
CloseHandle(hWorkerThread);
}

g_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

//This is the for loop to create a socket for each web server, for now we have only one web server (Google)
for(i = 0; i< 1; i++ )
{
lpPerHandleData = new PER_HANDLE_DATA;


lpPerHandleData->serverSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

if(lpPerHandleData->serverSocket == SOCKET_ERROR)
{
return 0;
}

pHost = gethostbyname(domain);

lpPerHandleData->serverAddr.sin_family = AF_INET;
lpPerHandleData->serverAddr.sin_port = htons(80);
memcpy((char *)&lpPerHandleData->serverAddr.sin_addr.s_addr, (char *)pHost->h_addr_list[0], pHost->h_length);

lpPerHandleData->host = domain;

WSAAsyncSelect(lpPerHandleData->serverSocket, g_hwnd, WM_SOCKET, FD_CONNECT);

if(connect(lpPerHandleData->serverSocket, (sockaddr *) &lpPerHandleData->serverAddr, sizeof(lpPerHandleData->serverAddr)) == SOCKET_ERROR )
{
if(WSAGetLastError() != WSAEWOULDBLOCK)
{
MessageBox(NULL,"Error connecting" ,"Error", MB_OK | MB_ICONEXCLAMATION);
return 0;
}
}
}


while(GetMessage(&msg, NULL, 0, 0) > 0)
{
MessageBox(NULL, "Message dispatched","", MB_OK | MB_ICONEXCLAMATION);
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return msg.wParam;

}


int getNoOfProcessor()
{
SYSTEM_INFO si;
GetSystemInfo(&si);
return si.dwNumberOfProcessors;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CREATE:
g_hwnd = hwnd;
return true;

case WM_SOCKET:
switch(WSAGETSELECTEVENT(lParam))
{
case FD_CONNECT: // if it's connected then perform the overlapped i/o
LPPER_HANDLE_DATA pHandleData;
pHandleData = new PER_HANDLE_DATA;
pHandleData->serverSocket = (SOCKET)wParam;


CreateIoCompletionPort((HANDLE)wParam, g_hCompletionPort, (DWORD)pHandleData, 0);

WSABUF wsaBuf;
LPDWORD lpNumberOfBytesSent = 0;
DWORD dwFlags = 0;
WSAOVERLAPPED wsaOverlapped;

//****************** PROBLEM HERE*************************************************
//How can I pass the filename and domain name to this window procedure ?
sprintf(wsaBuf.buf,"GET %s HTTP/1.1\r\nHost: %s\r\n\r\n", filename, domain);
wsaBuf.len = strlen(wsaBuf.buf);

//initiate the overlapped request
if(WSASend(wParam, &wsaBuf, 1, lpNumberOfBytesSent, dwFlags, &wsaOverlapped, NULL) == SOCKET_ERROR)
{
if(WSAGetLastError() != WSA_IO_PENDING)
{
MessageBox(NULL,"WSASend Error" ,"Status", MB_OK | MB_ICONEXCLAMATION);
}
}

}
}
return false;
}

DWORD WINAPI workerThread(LPVOID lParam)
{
//this is the workerThread pool that service all the io operations for many server sockets
//almost the same as in http://www.codeproject.com/internet/SimpleIOCPApp.asp
return true;
}

I was thinking to associate the socket by calling CreateIoCompletionPort before calling connect() which in in the WinMain function. But what will happen if I got many socket that has been associated with IOCP but when it's not connected to the server. In other words, is it fine to associate socket with IOCP when we never perform any IO operation ? Please give me any other advices. Thank you so much.

MikeAThon
June 30th, 2007, 06:46 PM
You could use a std::map to map the socket handle (which will be unique) to a pointer to whatever data you need.

Other approaches are possible.

I do not think that WSAAsyncSelect() is compatible with IOCP. Let us know if you get this to work. Why don't you simply use IOCP for the connect phase too?

Mike

keenlearner
July 1st, 2007, 12:47 AM
So far I have not find WSAAsyncSelect() work with IOCP.

What I know is IOCP only work for INPUT OUTPUT completion operation phase such as calling the function WSARecv() and WSASend(), I saw not one article using IOCP for connect phase. or have you tried that out and does it work ? If so then it will make my code even easier. Thank you for your reply.

MikeAThon
July 1st, 2007, 01:01 AM
An approachable introduction to completion ports can be found at "A simple IOCP Server/Client Class" at http://www.codeproject.com/internet/IOCP_Server_client.asp

Another good article is found in the October 2000 issue of MSDN magazine: "Windows Sockets 2.0: Write Scalable Winsock Apps Using Completion Ports" by Anthony Jones (well-known winsock expert at Microsoft) at http://msdn.microsoft.com/msdnmag/issues/1000/Winsock/

Mike

keenlearner
July 1st, 2007, 04:32 AM
Thanks for the reply again, I have read those given articles, this is also a good one http://www.codeproject.com/internet/SimpleIOCPApp.asp. I also have the Network Programming for Microsoft Windows by Anthony Jones and Jim Ohlund in my Online Safari Books which all of them never used the IOCP for connection phase, I hope IOCP can be used for connection phase too, but it seems not working.

and about WSAAsyncSelect with IOCP, I find it works now. The above program always give me the GetLastError() value 6 which is "The Handle is invalid". But after I set wsaOverlapped struct to 0 with memset(&wsaOverlapped, 0, sizeof(WSAOVERLAPPED));
It works now. It took me one day just to figure this out. :)

keenlearner
July 1st, 2007, 08:59 AM
Finally, you are right MikeAThon, I found a better way now using ConnectEx() where I don't have to use WSAAsyncSelect for connect phase, but all using IOCP. Hehe.

keenlearner
July 1st, 2007, 11:15 AM
Opps I got another problem again, I have improved the code below much. The error is at the WSARecv() return WSAEFAULT, there is something to do with pointer address wsaBuf, but I just can't solve out. If you can help, would be much appreciated. Thank you.



#include <winsock2.h>
#include <stdio.h>
#include <Mswsock.h>
#include <conio.h>

#include "main.h"

HANDLE g_hCompletionPort;
#define MAX_BUFFER_LEN 10000

#define OP_CONNECT 0
#define OP_READ 1

char g_debug[200]; // for debugging
char g_buffer[200];

typedef struct _PER_HANDLE_DATA
{
OVERLAPPED ol;
SOCKET serverSocket;
SOCKADDR_IN serverAddr;
char * host;
int opCode;
char * debug;
}PER_HANDLE_DATA, *LPPER_HANDLE_DATA;

int main(int agc, char * argv[])
{
int nResult, i;
WSADATA wsaData;
HANDLE hWorkerThread;
LPPER_HANDLE_DATA lpHandleData;
DWORD dwError, dwBytes;

if(agc < 2)
{
printf("At least one host\n");
return 0;
}

nResult = WSAStartup(MAKEWORD(2,2),&wsaData);

if(nResult != NO_ERROR)
{
printf("WSAStartup error\n");
return 0;
}

g_hCompletionPort = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);

if(g_hCompletionPort == NULL )
printf("CreateIoCompletionPort Error\n");

for(i =0; i< 1; i++)
{
hWorkerThread = CreateThread(NULL, 0, workerThread, g_hCompletionPort, 0, 0);
// close thread handle
//CloseHandle(phWorkerThread);
}


for(i = 1; i< agc; i++ )
{
lpHandleData = new PER_HANDLE_DATA;

if(lpHandleData == NULL)
{
printf("Insufficient physical memory\n");
return 0;
}

memset(lpHandleData, 0, sizeof(PER_HANDLE_DATA));

printf("Creating socket for %s\n",argv[i]);

lpHandleData->serverSocket = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);

if(lpHandleData->serverSocket == SOCKET_ERROR)
{
printf("Error Creating socket for %s\n",argv[i]);
return 0;
}

printf("Connecting to socket %d\n", lpHandleData->serverSocket);

HOSTENT *pHost = gethostbyname(argv[i]);

lpHandleData->serverAddr.sin_family = AF_INET;
lpHandleData->serverAddr.sin_port = htons(80);
memcpy((char *)&lpHandleData->serverAddr.sin_addr.s_addr, (char *)pHost->h_addr_list[0], pHost->h_length);
lpHandleData->host = (char *)argv[i];
lpHandleData->opCode = OP_CONNECT;
lpHandleData->debug = "Debugging\n";

printf("Connecting to %s\n", argv[i]);

//refer to http://www.codeguru.com/forum/archive/index.php/t-312668.html
LPFN_CONNECTEX lpfnConnectEx = NULL; // a pointer to the 'ConnectEx()' function
GUID GuidConnectEx = WSAID_CONNECTEX; // The Guid


dwError = WSAIoctl(lpHandleData->serverSocket,
SIO_GET_EXTENSION_FUNCTION_POINTER,
&GuidConnectEx, sizeof(GuidConnectEx),
&lpfnConnectEx, sizeof(lpfnConnectEx),
&dwBytes, NULL, NULL);

if(dwError == SOCKET_ERROR)
printf("WSAIoctl ERROR : %d\n", WSAGetLastError());

if(dwError == 0)
printf("WSAIoctl success\n");

memset(&lpHandleData->ol, 0, sizeof(lpHandleData->ol));

SOCKADDR_IN service;
service.sin_port = htons(0); //Binding to a specific port number other than port 0 is discouraged for client applications because there is a danger of conflicting with another socket already using that port number. MSND - bind()
service.sin_family = AF_INET;
service.sin_addr.s_addr = INADDR_ANY;

bind(lpHandleData->serverSocket, (sockaddr *) &service, sizeof(service)); // bind connectEx performance

memset(&lpHandleData->ol, 0, sizeof(lpHandleData->ol));

DWORD dwByteSent;
sprintf(g_buffer, "GET / HTTP/1.1\r\nHost: %s\r\n\r\n", argv[i]);

CreateIoCompletionPort((HANDLE)lpHandleData->serverSocket, g_hCompletionPort, (DWORD)lpHandleData, 0);

nResult = lpfnConnectEx(lpHandleData->serverSocket,
(sockaddr *) &lpHandleData->serverAddr, sizeof(lpHandleData->serverAddr),
g_buffer, sizeof(g_buffer), &dwByteSent, &lpHandleData->ol);

if(nResult == FALSE)
{
if(WSA_IO_PENDING != WSAGetLastError())
printf("lpfnConnectEx ERROR : %d\n", WSAGetLastError());
else printf("lpfnConnectEx WSA_IO_PENDING\n");
}
else printf("lpfnConnectEx complete immediately\n");
}

while(!_kbhit())
{
Sleep(0); //switch to some other thread
}
}

DWORD WINAPI workerThread(LPVOID lParam)
{
DWORD dwBytesTransfered = 0, *lpNumberOfBytesRecvd, *lpFlags = 0;
void *lpContext = NULL;
OVERLAPPED *pOverlapped = NULL;
LPPER_HANDLE_DATA lpHandleData = NULL;
int nResult;
WSABUF wsaBuf;
char buffer[MAX_BUFFER_LEN];
//memset(&buffer, 0, MAX_BUFFER_LEN);
//memset(&wsaBuf, 0, sizeof(wsaBuf));

wsaBuf.len = MAX_BUFFER_LEN;
wsaBuf.buf = buffer;

while(1)
{
//printf("DEBUG %d\n", GetLastError());
nResult = GetQueuedCompletionStatus(g_hCompletionPort, &dwBytesTransfered,(LPDWORD)&lpContext, &pOverlapped, 10000);

if(nResult == FALSE || ((nResult == TRUE) && (dwBytesTransfered == 0)))
{
printf("GetQueuedCompletionStatus ERROR : %d\n", GetLastError());
if(GetLastError() != 0) // application has completed successfully
continue;
}

lpHandleData = (LPPER_HANDLE_DATA) lpContext;

printf("Bytes transfered : %d\n", dwBytesTransfered);
switch(lpHandleData->opCode)
{
case OP_READ:
printf("Total bytes received : %d\n", wsaBuf.len);
printf("Data received :\n%s\n",wsaBuf.buf);
break;
case OP_CONNECT: // just connected to the server
printf("Connected\n");
lpHandleData->opCode = OP_READ;

printf("DEBUG %d\n", GetLastError());


memset(&lpHandleData->ol, 0, sizeof(lpHandleData->ol));

if(WSARecv(lpHandleData->serverSocket, &wsaBuf, 1, lpNumberOfBytesRecvd, lpFlags, &lpHandleData->ol, NULL) == SOCKET_ERROR)
if(WSAGetLastError() == WSA_IO_PENDING)
printf("WSARecv WSA_IO_PENDING\n");
else
/***************************ERROR 10014 WSAEFAULT **********************
printf("WSARecv ERROR : %d\n", WSAGetLastError());
break;
}
}

return 0;
}