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.
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.