关于HTTP服务器的访问流程及基本的工作原理,我已经在上一篇博文中简单叙述过。详见:http://www.cnblogs.com/suvllian/articles/5366341.html

  这次主要说一下如何实现一个简单的HTTP服务器程序。

  源代码地址:http://www.oschina.net/code/snippet_2685955_55369

  主要函数如下:

bool InitSocket();
DWORD WINAPI AcceptThread(LPVOID lpParam);
DWORD WINAPI ClientThread(LPVOID lpParam);
bool IoComplete(char* szRequest);                  //数据包的校验函数
bool AddClientList(SOCKET s,sockaddr_in addr);
bool AddThreadList(HANDLE hThread,DWORD ThreadID);
bool ParseRequest(char* szRequest, char* szResponse, BOOL &bKeepAlive);

  主函数如下:

if (!InitSocket())
    {
        printf("InitSocket Error\n");
        return;
    }
    GetCurrentDirectory(512,HtmlDir);
    strcat(HtmlDir,"\\HTML\\");
    strcat(HtmlDir,FileName);
    //启动一个接受线程
    HANDLE hAcceptThread = CreateThread(NULL,0,AcceptThread,NULL,0,NULL);
    //在这里我们使用事件模型来实现我们的Web服务器
    //创建一个事件
    WaitForSingleObject(hAcceptThread,INFINITE);

  其中有几个函数:

  1、GetCurrentDirectory:获取当前进程的当前目录

DWORD WINAPI GetCurrentDirectory(
  __in   DWORD nBufferLength,
  __out  LPTSTR lpBuffer
);
  系统参数,在一个缓冲区中装载当前目录。
  返回值:Long,装载到lpBuffer的字节数。如nBufferLength的长度不够,不足以容纳目录,则返回值是必要的缓冲区长度(要求至少这个长度),其中包括空中止字符。零表示失败。会设置GetLastError。
  参数说明:nBufferLength 缓冲区的长度、lpBuffer 指定一个预定义字串,用于装载当前目录。
  注意:VC下包含该函数的头文件 windows.h。
  该函数存在一个问题当程序里有其他地址指向的话,再调用这个函数也就跟着变了。
  例如:当用文件对话框成功打开一个文件后,该目录就被修改为被打开的文件所在目录。比如在程序中使用"123.txt"这样的文件名,希望将它存储在exe文件所在目录中,但是当用户用文件对话框打开一个文件"F:\456.txt"后,这个 123.txt会被存储到F盘下。
  所以,为解决这个问题,一般使用GetModuleFileName函数来处理,因为他获得是当前运行程序的存储路径。
  2、GetModuleFileName获取当前进程已加载模块的文件的完整路径,该模块必须由当前进程加载。
  如果想要获取另一个已加载模块的文件路径,可以使用GetModuleFileNameEx函数。
DWORD WINAPI GetModuleFileName(
    _In_opt_  HMODULE hModule,
    _Out_     LPTSTR lpFilename,
    _In_      DWORD nSize
);

   返回值:Long,如执行成功,返回复制到lpFileName的实际字符数量;零表示失败。使用GetLastError可以打印错误信息。

  参数说明:hModule Long:一个模块的句柄,用于标记这个文件资源。可以是一个DLL模块,或者是一个应用程序的实例句柄。如果该参数为NULL,该函数返回该应用程序全路径。

  lpFileName String:指定一个字串缓冲区,要在其中容纳文件的用NULL字符中止的路径名,hModule模块就是从这个文件装载进来的。

  nSize Long:装载到缓冲区lpFileName的最大字符数量。

  头文件:windows.h

BOOL CreateSampleService()
{
    TCHAR szPath[MAX_PATH];
    if( !GetModuleFileName( NULL, szPath, MAX_PATH ) )
    {
        printf("GetModuleFileName failed (%d)\n", GetLastError());
        return FALSE;
    }
return TRUE;
}

   3、CreatTherad函数:在主线程的基础上创建一个新线程

  CreateThread将在主线程的基础上创建一个新线程,大致做如下步骤:
  1、在内核对象中分配一个线程标识/句柄,可供管理,由CreateThread返回。
  2、把线程退出码置为STILL_ACTIVE,把线程挂起计数置1。
  3、分配context结构。
  4、分配两页的物理存储以准备栈,保护页设置为PAGE_READWRITE,第2页设为PAGE_GUARD。
  5、lpStartAddr和lpvThread值被放在栈顶,使它们成为传送给StartOfThread的参数。
  6、把context结构的栈指针指向栈顶(第5步)指令指针指向startOfThread函数。

  语法:hThread = CreateThread(&security_attributes, dwStackSize, ThreadProc,pParam, dwFlags, &idThread) ;

HANDLE CreateThread(
  LPSECURITY_ATTRIBUTES lpThreadAttributes,      //安全设置
  DWORD dwStackSize,                  //堆栈大小
  LPTHREAD_START_ROUTINE lpStartAddress,       //入口函数
  LPVOID lpParameter,                   // 函数参数
  DWORD dwCreationFlags,                // 启动选项
  LPDWORD lpThreadId                  //输出线程ID
); 

  参数说明:lpThreadAttributes:指向SECURITY_ATTRIBUTES型态的结构的指针。在Windows 98中忽略该参数。在Windows NT中,它被设为NULL

  dwStackSize:是用于新线程的初始堆栈大小,默认值为0。如果为0,那么默认将使用与调用该函数的线程相同的栈空间大小。在任何情况下,Windows根据需要动态延长堆栈的大小。

  lpStartAddress:是指向线程函数的指针。函数名称没有限制,但是必须以下列形式声明:DWORD WINAPI ThreadProc (PVOID pParam) ;

  lpParameter:向线程函数传递的参数,是一个指向结构的指针,这样主线程和从属线程就可以共享数据。不需传递参数时,为NULL。

  dwCreationFlags:通常为0,表示线程马上执行。当建立的线程不马上执行时为旗标CREATE_SUSPENDED。线程将暂停直到呼叫ResumeThread来恢复线程的执行为止。

  lpThreadId:保存新线程的ID。

  返回值:函数成功,返回线程句柄;函数失败返回FALSE。若不想返回线程ID,设置lpThreadId值为NULL。

   4、WSAStratup函数:WSA(Windows异步套接字)的启动命令。

int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData); 

  使用Socket的程序在使用Socket之前必须调用WSAStartup函数。

  第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;

  第二个参数返回请求的Socket的版本信息。

  当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。

  返回值:为0时,成功。
  否则返回下列的错误代码之一。
  WSASYSNOTREADY 指出网络通信依赖的网络子系统还没有准备好。

  WSAVERNOTSUPPORTED 所需的Windows Sockets API的版本未由特定的Windows Sockets实现提供。

  WSAEINVAL 应用程序指出的Windows Sockets版本不被该DLL支持。

  5、WSACleanup函数:int WSACleanup (void)

  应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。

  返回值:为0时,操作成功。

  失败为SOCKET_ERROR。同时可以调用WSAGetLastError()获得错误代码。
  6、MAKEWORD
WORD MAKEWORD(
BYTE bLow, //指定新变量的低字节序;
BYTE bHigh //指定新变量的高字节序;
);

  返回值:一个无符号16位整形数。

   7、CreateEvent函数:创建或打开一个命名的或无名的事件对象

HANDLECreateEvent(
LPSECURITY_ATTRIBUTES   lpEventAttributes,   // 安全属性
BOOL   bManualReset,   // 复位方式
BOOL   bInitialState,   // 初始状态
LPCTSTR   lpName    // 对象名称
);

  返回值:如果函数调用成功,函数返回事件对象的句柄。

  如果对于命名的对象,在函数调用前已经被创建,函数将返回存在的事件对象的句柄,而且在GetLastError函数中返回ERROR_ALREADY_EXISTS。

  如果函数失败,函数返回值为NULL,如果需要获得详细的错误信息,需要调用GetLastError。

  参数:lpEventAttributes:一个指向SECURITY_ATTRIBUTES结构的指针,确定返回的句柄是否可被子进程继承。如果lpEventAttributes是NULL,此句柄不能被继承,事件将获得一个默认的安全符。

  bManualReset:指定将事件对象创建成手动复原还是自动复原。如果是TRUE,那么必须用ResetEvent函数来手工将事件的状态复原到无信号状态。如果设置为FALSE,当一个等待线程被释放以后,系统将会自动将事件状态复原为无信号状态。
  bInitialState:指定事件对象的初始状态。如果为TRUE,初始状态为有信号状态;否则为无信号状态。
  lpName:指定事件的对象的名称,是一个以0结束的字符串指针。名称的字符格式限定在MAX_PATH之内。名字是对大小写敏感的。如果lpName为NULL,将创建一个无名的事件对象。如果lpName的和一个存在的信号、互斥、等待计时器、作业或者是文件映射对象名称相同,函数将会失败,在GetLastError函数中将返回ERROR_INVALID_HANDLE。造成这种现象的原因是这些对象共享同一个命名空间。

   8、WaitForSingleObject函数

DWORD WINAPI WaitForSingleObject(
__in HANDLE hHandle,
__in DWORD dwMilliseconds
);

  参数:hHandle:一个事件的句柄。

  dwMilliseconds:时间间隔。如果事件是有信号状态返回WAIT_OBJECT_0,如果时间超过dwMilliseconds值但时间事件还是无信号状态则返回WAIT_TIMEOUT。

  WaitForSingleObject函数用来检测hHandle事件的信号状态,在某一线程中调用该函数时,线程暂时挂起,如果在挂起的 dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但 hHandle所指向的对象还没有变成有信号状态,函数照样返回。

  参数dwMilliseconds有两个具有特殊意义的值:0和INFINITE。若为 0,则该函数立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态时为止。

  9、WaitForMultipleObjects函数

DWORD WaitForMultipleObjects(DWORD nCount,const HANDLE* lpHandles,BOOL bWaitAll,DWORD dwMilliseconds);

  参数:nCount:句柄的数量,最大值为MAXIMUM_WAIT_OBJECTS(64)。

  lpHandles:句柄数组的指针。

  HANDLE 类型可以为(Event,Mutex,Process,Thread,Semaphore )数组

  bWaitAll:等待的类型,如果为TRUE则等待所有信号量有效在往下执行,FALSE当有其中一个信号量有效时就向下执行

  dwMilliseconds:超时时间,超时向后执行。如果为WSA_INFINITE 永不超时。如果没有信号量就会在这死等。

  返回值:如果因时间终了而返回,那返回值是WAIT_TIMEOUT。

  如果bWaitAll是TRUE,那么返回值是WAIT_OBJECT_0。

  如果bWaitAll是FALSE,那么返回值减去WAIT_OBJECT_0,就表示哪一个handle被激发了。

  如果函数失败,则返回WAIT_FAILD,这时候你可以用GetLastError()找出失败原。

   10、CloseHandle函数

BOOL CloseHandle(HANDLE hObject);

  返回值:Long,非零表示成功。零表示失败。会设置GetLastError。

  函数作用:关闭一个内核对象。其中包括文件、文件映射、进程、线程、安全和同步对象等。在CreateThread成功之后会返回一个hThread的handle,且内核对象的计数加1,CloseHandle之后,引用计数减1,当变为0时,系统删除内核对象。

  若在线程执行完之后,没有调用CloseHandle,在进程执行期间,将会造成内核对象的泄露,相当于句柄泄露,但不同于内存泄露,这势必会对系统的效率带来一定程度上的负面影响。但当进程结束退出后,系统会自动清理这些资源。

  11、WSAEnumNetworkEvents()函数:检测所指定的套接口上网络事件的发生。

int WSAAPI WSAEnumNetworkEvents ( SOCKET s,WSAEVENT hEventObject, LPWSANETWORKEVENTS lpNetworkEvents, LPINT lpiCount);

 参数:s:标识套接口的描述字。

  hEventObject:(可选)句柄,用于标识需要复位的相应事件对象。

  lpNetworkEvents:一个WSANETWORKEVENTS结构的数组,每一个元素记录了一个网络事件和相应的错误代码。

  lpiCount:数组中的元素数目。在返回时,本参数表示数组中的实际元素数目;如果返回值是WSAENOBUFS,则表示为获取所有网络事件所需的元素数目。

  返回值:如果操作成功则返回0。否则的话,将返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()来获取相应的错误代码。

  12、ResetEvent函数:指定的事件对象设置为无信号状态。

BOOL ResetEvent(HANDLE hEvent);

  返回值:函数成功,返回非0值,否则返回0值,可以调用GetLastError得到错误的详细信息。

 
posted on 2016-04-13 00:20  suvllian  阅读(462)  评论(0)    收藏  举报