前段时间看了《深入理解计算机系统》,在网络编程这一章看到了一个http服务器的简单实现,然后也想自己在windows下面实现一个简单的http服务器。
为了方面叙述,下面从实现的角度来一步步的说明怎么实现这个http服务器。其实说到http服务器也就是从浏览器发送给服务器一个http请求,服务器通过分析浏览器传过来的http包,解析并返回一个http响应给浏览器。所以说到底就是浏览器和服务器之间的网络通讯,通过操作系统给我们提供的socket接口函数,然后按照http协议打包数据就可以很容易的一个简单的http服务器。
既然涉及到socket编程,那么我们就从socket接口方面入手来讨论http服务器。那么首先,我们必须明白http服务器是基于TCP协议的,而且默认端口为80,当然根据需要可以随时更换端口。
要实现http服务器,第一步就是需要我们服务器在80端口上进行监听,等待客户端的连接。所以在程序的一开始,有如下的代码:
unsigned short usPort;
printf("please input the port you want to listen:");
scanf("%d",&usPort);
SOCKETsocketListen=StartListen(usPort);
if(socketListen==-1)
{
printf("Sorry,you can't open the listen socket.\n");
return-1;
}
printf("启动web服务器成功,服务器地址为本机ip,端口为%d.\n",usPort);
StartListen这个函数里面创建了监听socket。
为了便于对程序流程的理解,暂时不在这里贴出StartListen的实现,大家只需要知道在这个函数里面服务器在usPort这个端口上实现了监听,并且返回监听套接字socketListen。
开启了监听之后,服务器当然要等待客户端连接并处理连接,所以接下来肯定是要调用accept函数来,但是在怎么来调用accept函数呢,因为accpet函数的作用是等待客户端连接,并返回与客户端通讯的套接字。所以在这里我们必须要注意,我们做为服务器肯定不能只接受一个客户端请求,accept函数肯定要循环调用,调用完之后必须要与客户端进行交互,这里就有问题来了是直接在循环里面交互还是利用多线程进行交互?根据不同的交互模式有不同的模型。如果是单线程循环交互,那么一般就用select模型,如果多线程进行交互,就在accept成功返回后创建新的线程进行交互,在这里我用的是多线程模型,代码如下:
SOCKADDR_IN addrClient;
intiAddr=sizeof(SOCKADDR_IN);
SOCKETsocketSerice;
SYSTEMTIMEst;
while(1)
{
socketSerice=accept(socketListen,(SOCKADDR*)&addrClient,&iAddr);
if(socketSerice<=0)
{
printf("sorry,you failed toaccept a client connect\n");
continue;
}
GetLocalTime(&st);
printf("有一个客户端连接.客户端的ip为:%s\t%d-%d-%d %d:%d:%d\n",inet_ntoa(addrClient.sin_addr),st.wYear,st.wMonth,st.wDay,st.wHour,st.wMinute,st.wSecond);
unsigneduThreadID;
_beginthreadex(NULL,0,ServerThread,(void*)socketSerice,0,&uThreadID);
}
WSACleanup();
上面就是我们http服务器的大致流程。
当然具体的http协议的交互在ServerThread里面与客户端进行交互。
下面来看StartListen的实现:
SOCKET StartListen(unsigned short usPort)
{
InitSocket();//调用WSAStartup
SOCKETsocketListen=socket(AF_INET,SOCK_STREAM,0);//创建TCP Socket
if(socketListen<=0)
return-1;
intiVal=1;
if(setsockopt(socketListen,SOL_SOCKET,SO_REUSEADDR,(const char*)&iVal,sizeof(int))<0)//使端口可以复用
return-1;
SOCKADDR_INaddrServer;
memset(&addrServer,0,sizeof(addrServer));
addrServer.sin_family=AF_INET;
addrServer.sin_addr.s_addr=htonl(INADDR_ANY);
addrServer.sin_port=htons(usPort);
if(bind(socketListen,(SOCKADDR*)&addrServer,sizeof(addrServer))<0)
return-1;
if(listen(socketListen,1024)<0)//开启监听
return-1;
returnsocketListen;
}
接着来看线程函数ServerThread的实现:
unsigned WINAPI ServerThread(void*pVoid)
{
SOCKETsocketSerice =(SOCKET)pVoid;
charpcBuffer[MaxBuffer];
intiRecv=0;
RequestCotentrc;
iRecv=GetData(socketSerice /*socketSerice*/,pcBuffer,MaxBuffer);//接收数据,也就是接受http请求
if(iRecv<=0)
{
closesocket(socketSerice /*socketSerice*/);
return-1;
}
rc=ParseData(pcBuffer,iRecv);//解析数据,也就是解析http包
ResponseData(socketSerice /*socketSerice*/,rc);//根据解析的内容相应http请求
closesocket(socketSerice /*socketSerice*/);//关闭该连接
return0;
}