C++实现简单的HTTP服务器
更新时间:2020年4月25日 17:35 点击:1633
本文实例为大家分享了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(<ime); newtime = gmtime(<ime); 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; }
以上就是本文的全部内容,希望对大家的学习有所帮助。
相关文章
- vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
- 这篇文章主要介绍了C#实现简单的Http请求的方法,以实例形式较为详细的分析了C#实现Http请求的具体方法,需要的朋友可以参考下...2020-06-25
- 这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
- 这篇文章主要介绍了Java如何发起http请求的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-03-31
- 本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
- 这篇文章主要介绍了C#实现HTTP下载文件的方法,包括了HTTP通信的创建、本地文件的写入等,非常具有实用价值,需要的朋友可以参考下...2020-06-25
C#使用Http Post方式传递Json数据字符串调用Web Service
这篇文章主要为大家详细介绍了C#使用Http Post方式传递Json数据字符串调用Web Service,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-06-25解决Feign切换client到okhttp无法生效的坑(出现原因说明)
这篇文章主要介绍了解决Feign切换client到okhttp无法生效的坑(出现原因说明),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25完美解决SpringCloud-OpenFeign使用okhttp替换不生效问题
这篇文章主要介绍了完美解决SpringCloud-OpenFeign使用okhttp替换不生效问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-02-25- 整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
- 这篇文章主要介绍了解决Java处理HTTP请求超时的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...2021-03-29
- 这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
- 这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
- 简单的php获取linux服务器状态的代码,不多说-直接上函数:复制代码 代码如下:function get_used_status(){ $fp = popen('top -b -n 2 | grep -E "^(Cpu|Mem|Tasks)"',"r");//获取某一时刻系统cpu和内存使用情况 $rs =...2014-05-31
- 这篇文章主要介绍了Springboot+TCP监听服务器搭建过程,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2020-10-28
- 这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
- 本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
- 前一段时间使用NetStat命令查看服务器端口时,发现服务器udp端口开放了好多,最少在1000个以上,当时事情比较多,没有管它,今天终于有点时间,仔细检查了一下,排除了这个问题. ...2016-01-27