C++实现简单的HTTP服务器

 更新时间:2016年05月02日 19:29:52   作者:suvllian  
这篇文章主要为大家详细介绍了C++实现简单的HTTP服务器的相关资料,感兴趣的朋友可以参考下

本文实例为大家分享了C++实现HTTP服务器的相关代码,供大家参考,具体内容如下

#include <Winsock2.h>
#include <windows.h>
#include <malloc.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
 
#pragma comment (lib,"ws2_32")
#define uPort 80
#define MAX_BUFFER   100000
#define SENDBLOCK   200000
#define SERVERNAME   "AcIDSoftWebServer/0.1b"
#define FileName   "HelloWorld.html"
 
typedef struct _NODE_ 
{
 SOCKET s;
 sockaddr_in Addr;
 _NODE_* pNext;
 
}Node,*pNode;
 
 
//多线程处理多个客户端的连接
typedef struct _THREAD_
{
 DWORD ThreadID;
 HANDLE hThread;
 _THREAD_* pNext;
}Thread,*pThread;
 
pNode pHead = NULL;
pNode pTail = NULL;
pThread pHeadThread = NULL;
pThread pTailThread = NULL;
 
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);
 
//我们存放Html文件的目录
char HtmlDir[512]={0};
 
void main()
{
 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);
}
 
DWORD WINAPI AcceptThread(LPVOID lpParam)   //接收线程
{
 //创建一个监听套接字
 SOCKET sListen = WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED); //使用事件重叠的套接字
 if (sListen==INVALID_SOCKET)
 {
  printf("Create Listen Error\n");
  return -1; 
 }
 //初始化本服务器的地址
 sockaddr_in LocalAddr;
 LocalAddr.sin_addr.S_un.S_addr = INADDR_ANY;
 LocalAddr.sin_family = AF_INET;
 LocalAddr.sin_port = htons(uPort);
 //绑定套接字 80端口
 int Ret = bind(sListen,(sockaddr*)&LocalAddr,sizeof(LocalAddr));
 if (Ret==SOCKET_ERROR)
 {
  printf("Bind Error\n");
  return -1;
 }
 //监听
 listen(sListen,5);
 //创建一个事件
 WSAEVENT Event = WSACreateEvent();
 if (Event==WSA_INVALID_EVENT)
 {
  printf("Create WSAEVENT Error\n");
  closesocket(sListen);
  CloseHandle(Event);     //创建事件失败 关闭套接字 关闭事件
  return -1;
 }
 //将我们的监听套接字与我们的事件进行关联属性为Accept
 WSAEventSelect(sListen,Event,FD_ACCEPT);
 WSANETWORKEVENTS NetWorkEvent;
 sockaddr_in ClientAddr;
 int nLen = sizeof(ClientAddr);
 DWORD dwIndex = 0;
 while (1)
 {
  dwIndex = WSAWaitForMultipleEvents(1,&Event,FALSE,WSA_INFINITE,FALSE);
  dwIndex = dwIndex - WAIT_OBJECT_0;
  if (dwIndex==WSA_WAIT_TIMEOUT||dwIndex==WSA_WAIT_FAILED)
  {
   continue;
  }
  //如果有真正的事件我们就进行判断
  WSAEnumNetworkEvents(sListen,Event,&NetWorkEvent);
  ResetEvent(&Event);   //
  if (NetWorkEvent.lNetworkEvents == FD_ACCEPT)
  {
   if (NetWorkEvent.iErrorCode[FD_ACCEPT_BIT]==0)
   {
    //我们要为新的连接进行接受并申请内存存入链表中
    SOCKET sClient = WSAAccept(sListen, (sockaddr*)&ClientAddr, &nLen, NULL, NULL);
    if (sClient==INVALID_SOCKET)
    {
     continue;
    }
    else
    {
     //如果接收成功我们要把用户的所有信息存放到链表中
     if (!AddClientList(sClient,ClientAddr))
     {
      continue;
     }  
    }
   }
  }
 }
 return 0;
}
 
DWORD WINAPI ClientThread(LPVOID lpParam)
{
 //我们将每个用户的信息以参数的形式传入到该线程
 pNode pTemp = (pNode)lpParam;
 SOCKET sClient = pTemp->s; //这是通信套接字
 WSAEVENT Event = WSACreateEvent(); //该事件是与通信套接字关联以判断事件的种类
 WSANETWORKEVENTS NetWorkEvent;
 char szRequest[1024]={0}; //请求报文
 char szResponse[1024]={0}; //响应报文
 BOOL bKeepAlive = FALSE; //是否持续连接
 if(Event == WSA_INVALID_EVENT)
 {
  return -1;
 }
 int Ret = WSAEventSelect(sClient, Event, FD_READ | FD_WRITE | FD_CLOSE); //关联事件和套接字
 DWORD dwIndex = 0;
 while (1)
 {
  dwIndex = WSAWaitForMultipleEvents(1,&Event,FALSE,WSA_INFINITE,FALSE);
  dwIndex = dwIndex - WAIT_OBJECT_0;
  if (dwIndex==WSA_WAIT_TIMEOUT||dwIndex==WSA_WAIT_FAILED)
  {
   continue;
  }
  // 分析什么网络事件产生
  Ret = WSAEnumNetworkEvents(sClient,Event,&NetWorkEvent);
  //其他情况
  if(!NetWorkEvent.lNetworkEvents)
  {
   continue;
  }
  if (NetWorkEvent.lNetworkEvents & FD_READ) //这里很有意思的
  {
    DWORD NumberOfBytesRecvd;
    WSABUF Buffers;
    DWORD dwBufferCount = 1;
    char szBuffer[MAX_BUFFER];
    DWORD Flags = 0;
    Buffers.buf = szBuffer;
    Buffers.len = MAX_BUFFER;
    Ret = WSARecv(sClient,&Buffers,dwBufferCount,&NumberOfBytesRecvd,&Flags,NULL,NULL);
    //我们在这里要检测是否得到的完整请求
    memcpy(szRequest,szBuffer,NumberOfBytesRecvd);
    if (!IoComplete(szRequest)) //校验数据包
    {
     continue;
    }
    if (!ParseRequest(szRequest, szResponse, bKeepAlive)) //分析数据包
    {
     //我在这里就进行了简单的处理
     continue;
    }
    DWORD NumberOfBytesSent = 0;
    DWORD dwBytesSent = 0;
    //发送响应到客户端
    do
    {
     Buffers.len = (strlen(szResponse) - dwBytesSent) >= SENDBLOCK ? SENDBLOCK : strlen(szResponse) - dwBytesSent; 
     Buffers.buf = (char*)((DWORD)szResponse + dwBytesSent);  
     Ret = WSASend(
      sClient,            
      &Buffers,          
      1,         
      &NumberOfBytesSent,
      0,           
      0,       
      NULL);  
     if(SOCKET_ERROR != Ret)
      dwBytesSent += NumberOfBytesSent;
    }
    while((dwBytesSent < strlen(szResponse)) && SOCKET_ERROR != Ret); 
  }
 
  if(NetWorkEvent.lNetworkEvents & FD_CLOSE)
  {
    //在这里我没有处理,我们要将内存进行释放否则内存泄露
  }
 }
 return 0;
}
 
bool InitSocket()
{
 WSADATA wsadata;
 if (WSAStartup(MAKEWORD(2,2),&wsadata)==0)    //使用Socket前必须调用 参数 作用 返回值
 {
  return true;
 }
 return false;
}
 
bool AddClientList(SOCKET s,sockaddr_in addr)
{
 pNode pTemp = (pNode)malloc(sizeof(Node));
 HANDLE hThread = NULL;
 DWORD ThreadID = 0;
 if (pTemp==NULL)
 {
  printf("No Memory\n");
  return false;
 }
 else
 {
  pTemp->s = s;
  pTemp->Addr = addr;
  pTemp->pNext = NULL;
  if (pHead==NULL)
  {
   pHead = pTail = pTemp;
  }
  else
  {
   pTail->pNext = pTemp;
   pTail = pTail->pNext;
  }
  //我们要为用户开辟新的线程
  hThread = CreateThread(NULL,0,ClientThread,(LPVOID)pTemp,0,&ThreadID);
  if (hThread==NULL)
  {
   free(pTemp);
   return false;
  }
  if (!AddThreadList(hThread,ThreadID))
  {
   free(pTemp);
   return false;
  }
 }
 return true;
}
 
bool AddThreadList(HANDLE hThread,DWORD ThreadID)
{
 pThread pTemp = (pThread)malloc(sizeof(Thread)); 
 if (pTemp==NULL)
 {
  printf("No Memory\n"); 
  return false;
 }
 else
 {
  pTemp->hThread = hThread;
  pTemp->ThreadID = ThreadID;
  pTemp->pNext = NULL; 
  if (pHeadThread==NULL)
  {
   pHeadThread = pTailThread = pTemp;
  } 
  else
  {
   pTailThread->pNext = pTemp;  
   pTailThread = pTailThread->pNext;
  }
 }
 return true;
}
 
//校验数据包
bool IoComplete(char* szRequest)
{
 char* pTemp = NULL;   //定义临时空指针
 int nLen = strlen(szRequest); //请求数据包长度
 pTemp = szRequest;   
 pTemp = pTemp+nLen-4; //定位指针
 if (strcmp(pTemp,"\r\n\r\n")==0)   //校验请求头部行末尾的回车控制符和换行符以及空行
 {
  return true;
 }
 return false;
}
 
//分析数据包
bool ParseRequest(char* szRequest, char* szResponse, BOOL &bKeepAlive)
{
 char* p = NULL;
 p = szRequest;
 int n = 0;
 char* pTemp = strstr(p," "); //判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
 n = pTemp - p;    //指针长度
// pTemp = pTemp + n - 1; //将我们的指针下移
 //定义一个临时的缓冲区来存放我们
 char szMode[10]={0};
 char szFileName[10]={0};
 memcpy(szMode,p,n);   //将请求方法拷贝到szMode数组中
 if (strcmp(szMode,"GET")==0)  //一定要将Get写成大写
 { 
 //获取文件名
  pTemp = strstr(pTemp," ");
  pTemp = pTemp + 1;   //只有调试的时候才能发现这里的秘密
  memcpy(szFileName,pTemp,1);
  if (strcmp(szFileName,"/")==0)
  {
   strcpy(szFileName,FileName);
  }
  else
  {
   return false;
  }
 }
 else
 {
  return false;
 }
 // 分析链接类型
 pTemp = strstr(szRequest,"\nConnection: Keep-Alive");  //协议版本
 n = pTemp - p;
 if (p>0)
 {
  bKeepAlive = TRUE;
 }
 else  //这里的设置是为了Proxy程序的运行
 {
  bKeepAlive = TRUE;
 }
 //定义一个回显头
 char pResponseHeader[512]={0};
 char szStatusCode[20]={0};
 char szContentType[20]={0};
 strcpy(szStatusCode,"200 OK");
 strcpy(szContentType,"text/html");
 char szDT[128];
 struct tm *newtime;
 long ltime;
 time(&ltime);
 newtime = gmtime(&ltime);
 strftime(szDT, 128,"%a, %d %b %Y %H:%M:%S GMT", newtime);
 //读取文件
 //定义一个文件流指针
 FILE* fp = fopen(HtmlDir,"rb");
 fpos_t lengthActual = 0;
 int length = 0;
 char* BufferTemp = NULL;
 if (fp!=NULL)
 {
  // 获得文件大小
  fseek(fp, 0, SEEK_END);
  fgetpos(fp, &lengthActual);
  fseek(fp, 0, SEEK_SET);
  //计算出文件的大小后我们进行分配内存
  BufferTemp = (char*)malloc(sizeof(char)*((int)lengthActual));
  length = fread(BufferTemp,1,(int)lengthActual,fp);
  fclose(fp);
  // 返回响应
  sprintf(pResponseHeader, "HTTP/1.0 %s\r\nDate: %s\r\nServer: %s\r\nAccept-Ranges: bytes\r\nContent-Length: %d\r\nConnection: %s\r\nContent-Type: %s\r\n\r\n",
   szStatusCode, szDT, SERVERNAME, length, bKeepAlive ? "Keep-Alive" : "close", szContentType);   //响应报文
 }
 //如果我们的文件没有找到我们将引导用户到另外的错误页面
 else
 {
 }
 strcpy(szResponse,pResponseHeader);
 strcat(szResponse,BufferTemp);
 free(BufferTemp);
 BufferTemp = NULL;
 return true;
}

以上就是本文的全部内容,希望对大家的学习有所帮助。

相关文章

  • C语言中常用的几个头文件及库函数

    C语言中常用的几个头文件及库函数

    这篇文章主要介绍了C语言中常用的几个头文件及库函数的相关资料,需要的朋友可以参考下
    2017-09-09
  • C++实现猜数小游戏的实现

    C++实现猜数小游戏的实现

    这篇文章主要介绍了C++实现猜数小游戏的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-02-02
  • C语言中读取时间日期的基本方法

    C语言中读取时间日期的基本方法

    这篇文章主要介绍了C语言中读取时间日期的基本方法,分别是time()函数和gmtime()函数的使用,注意返回值的区别,需要的朋友可以参考下
    2015-08-08
  • C++ 关于 CMFCPropertyGridCtrl 的使用方法

    C++ 关于 CMFCPropertyGridCtrl 的使用方法

    这篇文章主要介绍了C++ 关于 CMFCPropertyGridCtrl 的使用方法的相关资料,需要的朋友可以参考下
    2015-06-06
  • c语言中数组名a和&a详细介绍

    c语言中数组名a和&a详细介绍

    其实这两个东西挺难理解的,应该也没有那么重要,了解一下好了,主要还是要多多理解数组指针的运算
    2013-08-08
  • 构建mfc窗体的简单示例

    构建mfc窗体的简单示例

    这篇文章主要介绍了构建mfc窗体的简单示例,需要的朋友可以参考下
    2014-04-04
  • 基于字符串移位包含的问题详解

    基于字符串移位包含的问题详解

    本篇文章是对字符串移位包含的问题的解决方法进行了详细的分析介绍,需要的朋友参考下
    2013-05-05
  • 解决在Mac下直接解压C++静态库出现的问题

    解决在Mac下直接解压C++静态库出现的问题

    最近在研究C++的各种编译构建过程,学习了一下cmake,gyp/ninja这些自动化构建工具后,想着自己试下用纯命令行跑一遍编译流程。在试图把C++静态库编译为动态库的过程中遇到了棘手的问题,找了好久后发现是跟Mac平台相关的,这里记录一下,望对遇到类似问题的童鞋有帮助。
    2016-12-12
  • c++中stack、queue和vector的基本操作示例

    c++中stack、queue和vector的基本操作示例

    这篇文章主要给大家介绍了关于c++中stack、queue和vector基本操作的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面跟着小编来一起学习学习吧。
    2017-08-08
  • 6个变态的C语言Hello World程序

    6个变态的C语言Hello World程序

    这篇文章主要介绍了6个变态的C语言Hello World程序,需要的朋友可以参考下
    2016-05-05

最新评论