C++实现简单的HTTP服务器
本文实例为大家分享了C++实现HTTP服务器的相关代码,供大家参考,具体内容如下
#include<Winsock2.h> #include<windows.h> #include<malloc.h> #include<stdio.h> #include<string.h> #include<time.h> #pragmacomment(lib,"ws2_32") #defineuPort80 #defineMAX_BUFFER100000 #defineSENDBLOCK200000 #defineSERVERNAME"AcIDSoftWebServer/0.1b" #defineFileName"HelloWorld.html" typedefstruct_NODE_ { SOCKETs; sockaddr_inAddr; _NODE_*pNext; }Node,*pNode; //多线程处理多个客户端的连接 typedefstruct_THREAD_ { DWORDThreadID; HANDLEhThread; _THREAD_*pNext; }Thread,*pThread; pNodepHead=NULL; pNodepTail=NULL; pThreadpHeadThread=NULL; pThreadpTailThread=NULL; boolInitSocket();//线程函数 DWORDWINAPIAcceptThread(LPVOIDlpParam); DWORDWINAPIClientThread(LPVOIDlpParam); boolIoComplete(char*szRequest);//数据包的校验函数 boolAddClientList(SOCKETs,sockaddr_inaddr); boolAddThreadList(HANDLEhThread,DWORDThreadID); boolParseRequest(char*szRequest,char*szResponse,BOOL&bKeepAlive); //我们存放Html文件的目录 charHtmlDir[512]={0}; voidmain() { if(!InitSocket()) { printf("InitSocketError\n"); return; } GetCurrentDirectory(512,HtmlDir); strcat(HtmlDir,"\\HTML\\"); strcat(HtmlDir,FileName); //启动一个接受线程 HANDLEhAcceptThread=CreateThread(NULL,0,AcceptThread,NULL,0,NULL); //在这里我们使用事件模型来实现我们的Web服务器 //创建一个事件 WaitForSingleObject(hAcceptThread,INFINITE); } DWORDWINAPIAcceptThread(LPVOIDlpParam)//接收线程 { //创建一个监听套接字 SOCKETsListen=WSASocket(AF_INET,SOCK_STREAM,0,NULL,0,WSA_FLAG_OVERLAPPED);//使用事件重叠的套接字 if(sListen==INVALID_SOCKET) { printf("CreateListenError\n"); return-1; } //初始化本服务器的地址 sockaddr_inLocalAddr; LocalAddr.sin_addr.S_un.S_addr=INADDR_ANY; LocalAddr.sin_family=AF_INET; LocalAddr.sin_port=htons(uPort); //绑定套接字80端口 intRet=bind(sListen,(sockaddr*)&LocalAddr,sizeof(LocalAddr)); if(Ret==SOCKET_ERROR) { printf("BindError\n"); return-1; } //监听 listen(sListen,5); //创建一个事件 WSAEVENTEvent=WSACreateEvent(); if(Event==WSA_INVALID_EVENT) { printf("CreateWSAEVENTError\n"); closesocket(sListen); CloseHandle(Event);//创建事件失败关闭套接字关闭事件 return-1; } //将我们的监听套接字与我们的事件进行关联属性为Accept WSAEventSelect(sListen,Event,FD_ACCEPT); WSANETWORKEVENTSNetWorkEvent; sockaddr_inClientAddr; intnLen=sizeof(ClientAddr); DWORDdwIndex=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) { //我们要为新的连接进行接受并申请内存存入链表中 SOCKETsClient=WSAAccept(sListen,(sockaddr*)&ClientAddr,&nLen,NULL,NULL); if(sClient==INVALID_SOCKET) { continue; } else { //如果接收成功我们要把用户的所有信息存放到链表中 if(!AddClientList(sClient,ClientAddr)) { continue; } } } } } return0; } DWORDWINAPIClientThread(LPVOIDlpParam) { //我们将每个用户的信息以参数的形式传入到该线程 pNodepTemp=(pNode)lpParam; SOCKETsClient=pTemp->s;//这是通信套接字 WSAEVENTEvent=WSACreateEvent();//该事件是与通信套接字关联以判断事件的种类 WSANETWORKEVENTSNetWorkEvent; charszRequest[1024]={0};//请求报文 charszResponse[1024]={0};//响应报文 BOOLbKeepAlive=FALSE;//是否持续连接 if(Event==WSA_INVALID_EVENT) { return-1; } intRet=WSAEventSelect(sClient,Event,FD_READ|FD_WRITE|FD_CLOSE);//关联事件和套接字 DWORDdwIndex=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)//这里很有意思的 { DWORDNumberOfBytesRecvd; WSABUFBuffers; DWORDdwBufferCount=1; charszBuffer[MAX_BUFFER]; DWORDFlags=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; } DWORDNumberOfBytesSent=0; DWORDdwBytesSent=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) { //在这里我没有处理,我们要将内存进行释放否则内存泄露 } } return0; } boolInitSocket() { WSADATAwsadata; if(WSAStartup(MAKEWORD(2,2),&wsadata)==0)//使用Socket前必须调用参数作用返回值 { returntrue; } returnfalse; } boolAddClientList(SOCKETs,sockaddr_inaddr) { pNodepTemp=(pNode)malloc(sizeof(Node)); HANDLEhThread=NULL; DWORDThreadID=0; if(pTemp==NULL) { printf("NoMemory\n"); returnfalse; } 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); returnfalse; } if(!AddThreadList(hThread,ThreadID)) { free(pTemp); returnfalse; } } returntrue; } boolAddThreadList(HANDLEhThread,DWORDThreadID) { pThreadpTemp=(pThread)malloc(sizeof(Thread)); if(pTemp==NULL) { printf("NoMemory\n"); returnfalse; } else { pTemp->hThread=hThread; pTemp->ThreadID=ThreadID; pTemp->pNext=NULL; if(pHeadThread==NULL) { pHeadThread=pTailThread=pTemp; } else { pTailThread->pNext=pTemp; pTailThread=pTailThread->pNext; } } returntrue; } //校验数据包 boolIoComplete(char*szRequest) { char*pTemp=NULL;//定义临时空指针 intnLen=strlen(szRequest);//请求数据包长度 pTemp=szRequest; pTemp=pTemp+nLen-4;//定位指针 if(strcmp(pTemp,"\r\n\r\n")==0)//校验请求头部行末尾的回车控制符和换行符以及空行 { returntrue; } returnfalse; } //分析数据包 boolParseRequest(char*szRequest,char*szResponse,BOOL&bKeepAlive) { char*p=NULL; p=szRequest; intn=0; char*pTemp=strstr(p,"");//判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。 n=pTemp-p;//指针长度 //pTemp=pTemp+n-1;//将我们的指针下移 //定义一个临时的缓冲区来存放我们 charszMode[10]={0}; charszFileName[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 { returnfalse; } } else { returnfalse; } //分析链接类型 pTemp=strstr(szRequest,"\nConnection:Keep-Alive");//协议版本 n=pTemp-p; if(p>0) { bKeepAlive=TRUE; } else//这里的设置是为了Proxy程序的运行 { bKeepAlive=TRUE; } //定义一个回显头 charpResponseHeader[512]={0}; charszStatusCode[20]={0}; charszContentType[20]={0}; strcpy(szStatusCode,"200OK"); strcpy(szContentType,"text/html"); charszDT[128]; structtm*newtime; longltime; time(<ime); newtime=gmtime(<ime); strftime(szDT,128,"%a,%d%b%Y%H:%M:%SGMT",newtime); //读取文件 //定义一个文件流指针 FILE*fp=fopen(HtmlDir,"rb"); fpos_tlengthActual=0; intlength=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; returntrue; }
以上就是本文的全部内容,希望对大家的学习有所帮助。