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;
}
以上就是本文的全部内容,希望对大家的学习有所帮助。