网络程序之Http Server的实现

本文介绍了一个简单的HTTP服务器的实现过程,主要支持GET请求,并详细解释了如何解析HTTP请求及响应流程。
网络程序之Http Server的实现

Http协议目前大家比较熟悉.因为我们现在的所谓的上网其中所遵循的应用层协议就是RFC2616这个规范.

我研究HTTP协议算起来已有两年多了.开发了多个HTTP服务器端软件版本.今天就给出自已两年前的第一个实验版本.在这个版本上只实现了GET功能.只支持网页文件下载.

我的设计思路是这样的.首先初始化服务器端套接字.让其侦听在80端口.当有客户端到达时,接收其连接请求,同时分析其请求数据.我们先了解一下一个GET请求的格式:

GET /index.htm HTTP/1.1
Host: localhost
Accept: */*User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; .NET CLR 1.1.4322)
Pragma: no-cache
Cache-Control: no-cache
Connection: close
这是一个GET的请求格式. 因为这里只实现GET功能,故我们的解析工作只针对第一行.我们分析得知.这个请求是要求服务器传送index.htm这个文件.服务器识别出这个请求后.开始从当前目录查找这个文件.若找到则发送给对方.否则发送一个当前没有这个文件的信息.然后断开连接.下面有全部的代码:
#include<WINSOCK2.h>
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
//#include "AccessRecord.h"
#include "HeadDefine.h"
#include "ServerError.h"
//#include "Process.h"
//服务器默认端口
#define DEFAULTPORT 80
//////////////////////////
//客户机请求信息
char buf[1000];//保存用户请求
char bufHead[180];//发送请求头
char filename[30];//保存用户请求的文件名
char requestInfor[1000];//客户机请求交互信息
int InformationLength;//信息长度
////////////////////////////////////////
//目志信息数据类型
char lpAddress[16];//IP地址
unsigned lpPort;//客户机端口号
char lpConnectTime[26];//客户机连接时间
char lpDisconnectTime[26];//断开时间
short requestHead;//请求头
char requestInfo[20];//请求内容
//////////////////////////////////////////
//传送标头
void sendHead(char *ResponseHead,char *Last_Modified,char *Date,int Content_Length,char *Content_Type)
{
sprintf(bufHead,"HTTP/1.1 %s/r/nDate: %s/r/nServer: LiuHttpServer (Windowxp)/r/nLast-Modified: %s/r/nAccept-Ranges: bytes/r/nContent-Length: %d/r/nContent-Type: %s/r/nConnection: close/r//r/n",ResponseHead,Date,Last_Modified,Content_Length,Content_Type);
}
/////////////////////////////
//将文件名置为默认
void fileName()
{
filename[0]='i';
filename[1]='n';
filename[2]='d';
filename[3]='e';
filename[4]='x';
filename[5]='.';
filename[6]='h';
filename[7]='t';
filename[8]='m';
filename[9]='/0';
}//处理GET请求
//当传输SWF等文件时有问题,出在Content-Type: 这一字段
void Get(SOCKET s,bool headorget)//bool变量若为true则为GET请求,否则为HEAD请求
{
FILE *p;
if((p=fopen(filename,"r+b"))==NULL)
{
char sendbuf[200]="HTTP/1.1 404 没找到请求资源/n<html>可能是服务器上没有该资源或你被拒绝访问</html>";
printf("打开文件出错%s/n",filename);
send(s,sendbuf,sizeof(sendbuf),0);
return ;
}
char *buf1;
fpos_t length;
fseek(p,0,SEEK_END);
fgetpos(p,&length);
fseek(p,0,SEEK_SET);
//若为TRUE 则为GET请求
if(headorget)
{
buf1=new char[(long)length+1];
fread(buf1,1,(long)length,p);
}
fclose(p);
char buftime[29];//时间缓存区
struct tm *ptime;//time结构体
long ltime;
time(&ltime);
ptime=localtime(&ltime);
strftime(buftime,29,"%a %d %b %Y %H:%M:%S GMT",ptime);
//对标头进行设置
sendHead("200 OK ",buftime,buftime,(long)length,".swf/x-shockwave-flash ");
///此处有问题Content-Type: 不确定
int tSend=2;//send(s,bufHead,sizeof(bufHead),0);
//传送标头的异常检测
if(tSend==SOCKET_ERROR||tSend==0)
{
printf("发送数据失败错误代码:");
sendError(WSAGetLastError());
}
else
{
printf("发送标头字节数为:%d/n",tSend);
printf("****************************************8/n");
}
////////////////////////////
//处理GET请求
if(headorget)
{
int iSend=send(s,buf1,(long)length,0);
if(iSend==SOCKET_ERROR)
{
printf("发送数据失败错误代码 ");
sendError(WSAGetLastError());
return ;
}
else if(iSend==0)
return ;
else
{
printf("发送文件字节数为:%d/n",iSend);
printf("****************************************8/n");
}
}
delete buf1;
buf1=NULL;
}
//////////////////////////////////
//解析请求
int processCommand()
{
///当前请求为GET/get时
printf("processcommand is running../n");
if((buf[0]=='G'||buf[0]=='g')&&(buf[1]=='E'||buf[1]=='e')&&(buf[2]=='T'||buf[2]=='t'))
{
//GET /fa?d HTTP/1.1
int length=0;//请求总信息的长度
int inforlength=0;//交互信息长度;
int filelength=0;//请求文件的长度
bool flag=false;//分析是否有为交互信息
/////是否为默认主页
if(buf[5]==' ')
{
fileName();//="index.htm";
return GET;
}
////若不是则分析
else
{
printf("Get else is running../n");
while(buf[length+5]!=' ')
{
if(buf[length+5]=='?')//如果为?则当前有客户的交互信息
{
flag=true; //为真
length++; //指向下一个字符,因为请求中不需要'?'字符
}
/////为假是则为文件头信息
if(!flag)
filename[filelength++]=buf[length+5];
/////为真时为交互信息
else
requestInfor[inforlength++]=buf[length+5];//分离出请求头
length++;
}
filename[filelength]='/0';
if(requestInfor!=NULL)
requestInfor[inforlength]='/0';
if(!flag)
return GET;
if((filename[filelength-1]=='e'||filename[filelength-1]=='E')&&(filename[filelength-1]=='x'||filename[filelength-1]=='X')&&(filename[filelength-1]=='e'||filename[filelength-1]=='E'))
return GET_CGI;
else
return GET_SCRIPT;
}
}
////////当前请求为HEAD时
else if((buf[0]=='H'||buf[0]=='h')&&(buf[1]=='E'||buf[1]=='e')&&(buf[2]=='A'||buf[2]=='a')&&(buf[3]=='D'||buf[3]=='d'))
{
//HEAD / HTTP/1.1
//当前请求为空时
if(buf[6]==' ')
fileName();//="index.htm";

///不为空时
else
{
int length=0;
//保存文件名
while(buf[length+5]!=' ')
{
filename[length]=buf[length+5];
length++;
}
}
return HEAD;
}
//当请求为POST时
else if((buf[0]=='P'||buf[0]=='p')&&(buf[1]=='O'||buf[1]=='o')&&(buf[2]=='S'||buf[2]=='s')&&(buf[3]=='T'||buf[3]=='t'))
{
if(buf[6]==' ')
return POST_DESTINATIONISNULL;
else
{
int length=0;//为文件头长度
while(buf[length+6]!=' ')
{
filename[length]=buf[length+6];
length++;
}
int length1=InformationLength;//为总信息数
//将信息放入请求头
while(buf[length1]!=' '&&buf[length1]!='/r'&&buf[length1]!='/n')
{
requestInfor[InformationLength-length1]=buf[length1];
length1--;
}
if((filename[length-1]=='E'||filename[length-1]=='e')&&(filename[length-1]=='X'||filename[length-1]=='x')&&(filename[length-1]=='E'||filename[length-1]=='e'))
return POST_CGI;
else
return POST_SCRIPT;
}
}
//put请求
else if((buf[0]=='P'||buf[0]=='P')&&(buf[1]=='U'||buf[1]=='u')&&(buf[2]=='T'||buf[2]=='t'))
{
return PUT;
}
///options请求
else if((buf[0]=='o'||buf[0]=='O')&&(buf[1]=='p'||buf[1]=='P')&&(buf[2]=='t'||buf[2]=='T')&&(buf[3]=='i'||buf[3]=='I')&&(buf[4]=='o'||buf[4]=='O')&&(buf[5]=='n'||buf[5]=='N')&&(buf[6]=='s'||buf[6]=='S'))
{
return OPTIONS;
}
//trace 请求
else if((buf[0]=='T'||buf[0]=='t')&&(buf[1]=='r'||buf[1]=='R')&&(buf[2]=='a'||buf[2]=='A')&&(buf[3]=='c'||buf[3]=='C')&&(buf[4]=='e'||buf[4]=='E'))
{
return TRACE;
}
return QEQUEST_HEAD_ERROR;
}
/////////////////////////////////////////
/////////////////////////
void run(SOCKET S)
{
//copyChar(requestInfo,filename);
switch(requestHead = processCommand())
{
//客户用GET要求传输一网页
case GET:
Get(S,true);
break;
//客户用GET传送了交互性信息并要求CGI给予处理
case GET_CGI:
break;
//客户用GET传送了交互性信息并要求脚本给予处理
case GET_SCRIPT:
break;
//客户用HEAD要求传送文件头信息
case HEAD:
Get(S,false);
break;
////客户用POST传送了交互性信息并要求CGI给予处理
case POST_CGI:
break;
//客户用POST传送了交互性信息并要求脚本给予处理
case POST_SCRIPT:
break;
//客户请求PUT
case PUT:
break;
//客户请求TRACE
case TRACE:
break;
//客户请求OPTIONS
case OPTIONS:
break;
//客户传送交互式信息时未指定处理该信息的页面而发生的错误
case POST_DESTINATIONISNULL:
break;
//客户机传送了不可识别的请求头
case QEQUEST_HEAD_ERROR:
break;
}

}
////////////////////
//当前时间
void getCurrentTime(char timeBuf[])
{
struct tm *p;
long ltime;
time(&ltime);
p=localtime(&ltime);
strftime(timeBuf,25,"%a %d %b %Y %H:%M:%S",p);
}
////////////////////////
void printcurrenttime()//打印当前时间
{
char buf[29];
struct tm *p;
long ltime;
_strtime(buf);
printf("当前时间为:/t/t/t/t%s/n", buf);
_strdate(buf);
printf("当前日期为:/t/t/t/t%s/n", buf);
time(&ltime);
p=localtime(&ltime);
strftime(buf,29,"%a %d %b %Y %H:%M:%S GMT",p);
printf("服务器启动时间:%s/n",buf);
}
void main()
{
int iPort=DEFAULTPORT;
WSADATA wsaData;
SOCKET sListen,sAccept;
int iLen;
struct sockaddr_in server,client;
if(WSAStartup(MAKEWORD(2,2),&wsaData)!=0)
{
printf("无法加载套接字协议栈/n");
WSAStartupError(WSAGetLastError());
return ;
}
sListen=socket(AF_INET,SOCK_STREAM,0);
if(sListen==INVALID_SOCKET)
{
printf("套接字初始化出现错误;错误号:");
socketError(WSAGetLastError());
return ;
}
server.sin_family=AF_INET;
server.sin_port=htons(iPort);
server.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sListen,(LPSOCKADDR)&server,sizeof(server))==SOCKET_ERROR)
{
printf("绑定出现问题:");
bindError(WSAGetLastError());
return ;
}
if(listen(sListen,5)==SOCKET_ERROR)
{
printf("监听出现问题错误号码:");
listenError(WSAGetLastError());
return ;
}
iLen=sizeof(client);
printf("*********************************************/n");
printf("服务器当前已启动......./n");
printcurrenttime();
printf("*********************************************/n");
//showRecord();
while(true)
{
sAccept=accept(sListen,(struct sockaddr*)&client,&iLen);
if(sAccept==INVALID_SOCKET)
{
printf("连接出现错误代码: ");
acceptError(WSAGetLastError());
break;
}
//获取客户机连接时间
getCurrentTime(lpConnectTime);
printf("%s客户机请求时间 /n",lpConnectTime);
InformationLength=recv(sAccept,buf,sizeof(buf),0);
buf[InformationLength++]='/0';
int t1=0;
while(buf[t1]!='/0')
{
printf("%c",buf[t1]);
t1++;
}
printf("/n请求的客户IP地址为:[%s],端口号:[%d]/n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));
lpPort = ntohs(client.sin_port);//获取客户机端口号
// copyChar(lpAddress,inet_ntoa(client.sin_addr));//客户机IP
// printf("%s 客户机IP /n",lpAddress);
run(sAccept);
getCurrentTime(lpDisconnectTime);
printf("%s客户机断开时间 /n",lpDisconnectTime);
closesocket(sAccept);
// insertRecord(lpAddress,lpPort,lpConnectTime,lpDisconnectTime,requestHead,requestInfo);
}
closesocket(sListen);
WSACleanup();
}
//////////////////////////////////////////
HeadDefine.h
//全局定义的宏
#ifndef _HEADDEFINE
#define _HEADDEFINE
//////////////////
//请求类
#define GET 1 //当前请求为GET
#define GET_CGI 2 //当前请求为GET_CGI
#define GET_SCRIPT 3 //当前请求为GET脚本
#define HEAD 4 //为HEAD请求
#define POST_CGI 5 // 为POST CGI
#define POST_SCRIPT 6 //为POST 脚本
#define POST_DESTINATIONISNULL 7 //客户机没有给出处理该PSOT请求的目标文件(错误)
#define QEQUEST_HEAD_ERROR 8 //客户机请求头出现了不可识别的错误
#define PUT 9 //PUT请求
#define OPTIONS 10 //OPTIONS请求
#define TRACE 11 //TRACE 请求
//////////////////////
//文件类
#define CANOTOPENFILE 12 //打开文件出错
#define INSERTRECORDERROR 13 //添加记录出错
#define INSERTRECORDSUCESS 14//添加记录成功
#endif
这样就实现了HTTP Server有GET功能.运行这个程序后你可以从浏览器中输入 Http://localhost/index.html 同时保证当前目录有这个一个文件.这是不是就实现了一个小型的HTTP服务器.
现在我基本上完成了一个开源的LIU Server(HTTP服务器)这个功能比较强大.支持GET和POST功能.支持各种文件传输和流媒体在线下载与观看.自定义CGI开发接口能轻松的部署WEB应用程序.想要了解更多功能请与我联系,最好的办法是留言.我的邮箱是:my2005lb@126.com欢迎有相同爱好的朋友与我共同开发这个项目.我的开发团队代号为Liu
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值