免费提供一个C++编程及网络编程的案例,还附送有关问题解答,是的,你没有听错^

免费提供一个C++编程及网络编程的案例,还附送问题解答,是的,你没有听错^-^
这是一个C++实现的HTTP服务器,下面将讲解其实现过程.
不尽完美之处欢迎大家拍砖指正,期待大家的参与,谢谢~

先总体的说说,主要包括3部分:
  1.底层Socket实现. 这里用的是select模型.
  2.HTTP协议,请求,回应,解析等.
  3.处理流程,逻辑方面的处理.
 
上2个图:
效果图:
免费提供一个C++编程及网络编程的案例,还附送有关问题解答,是的,你没有听错^

功能图(暂不管中间的点歌功能,重点讲HTTP):
免费提供一个C++编程及网络编程的案例,还附送有关问题解答,是的,你没有听错^

如果对HTTP协议不了解的话可以去看看这两个链接:
 http://www.blogjava.net/zjusuyong/articles/304788.html 
 http://www.cnblogs.com/li0803/archive/2008/11/03/1324746.html
 
首先分析一下HTTP的处理流程:
 2.1.接收HTTP请求,并解析请求内容
  首先通过tcp套接字将用户请求读入到STL的string中,之后判断是否为http请求,也就是看是否在首行存在“HTTP”字串,然后判断是否为Post或Get或其它的方式,也是搜索字符串,接着是解析出其请求内容,比如“Get /index.html HTTP/1.1 …”,那么可以用substr方法截取出“index.html”来。其它头的解析还包括请求类型Content Type、请求报文长度Content Length等。表单和cookie的解析通过拆分子串实现,并将解析出的结果存入map中。最后将解析出的内容全部封装到HttpRequest类里。
  2.2.分析请求内容,处理用户请求内容
  通过解析之后,根据HttpRequest对象的各个成员变量来处理用户请求,如通过“type”来决定保存用户上传文件还是给用户读入所请求文件抑或是点歌,而通过“Content Length”来读取大小为contentLength的上传文件。
  2.3.根据请求给予应答
  处理好请求之后,准备响应,首先写入HTTP响应头,包括通用头、响应头、实体头,包括最主要的状态码“Status”,服务器名称Server头、是否保连接Connection头等响应头,以及通用头如时间Date头、缓存时长Cache Control头、,还有实体头如响应内容的类型Content Type头、长度Content Length头。
 
上个流程图:
免费提供一个C++编程及网络编程的案例,还附送有关问题解答,是的,你没有听错^

第1部分了:Socket部分的实现

class SelectModelServer
{
public:
SelectModelServer(ReceiveAble& receiver);
~SelectModelServer();
private:
int m_nCurrentConCount;
SOCKET m_serverSocket;  
SOCKET m_socketArray[FD_SETSIZE];
//bool m_bUseableArray[FD_SETSIZE];
//sockaddr_in peerAddrs[MAXIMUM_WAIT_OBJECTS];

ReceiveAble* m_pReceiver;
private:
void onAccept();
void onReceive(SOCKET&  clientSocket);
void onClose(SOCKET&  clientSocket);
public:
void init(unsigned short port);
void startServer();
void closeServer();
void close(const SOCKET&  socket);
}; 
#endif

 

#include "SelectModelServer.h"

#include "ReceiveAble.h"

#pragma comment(lib,"ws2_32.lib")

SelectModelServer::SelectModelServer(ReceiveAble& receiver)
{
this->m_pReceiver=&receiver;
m_nCurrentConCount=0;
/*for (int i=0;i<FD_SETSIZE;i++)
{
m_bUseableArray[i]=true;
}*/
for (int i=0;i<FD_SETSIZE;i++)
{
m_socketArray[i]=INVALID_SOCKET;
}
}
SelectModelServer::~SelectModelServer()
{
closeServer();
}
void SelectModelServer::init(unsigned short port)
{
cout<<"init server(port:"<<port<<")..."<<endl;
if(m_nCurrentConCount!=0)
return;
//1.初始化环境
SocketTools::initSocketContext();
//2.创建套接字
m_serverSocket=socket(AF_INET,SOCK_STREAM,0);
if(m_serverSocket<0)
throw SocketException(WSAGetLastError(),"创建服务套接字失败");
//3.绑定本地ip和端口
sockaddr_in local_addr;
int length =sizeof(sockaddr_in);
memset(&local_addr,0,length);
local_addr.sin_family=AF_INET;
local_addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//htonl(inet_addr("127.0.0.1"));
local_addr.sin_port=htons(port);
if(bind(m_serverSocket,(struct sockaddr*)&local_addr,length) == SOCKET_ERROR)
throw SocketException(WSAGetLastError(),"绑定失败");
//4.侦听端口
if(listen(m_serverSocket,SOMAXCONN)== SOCKET_ERROR)
throw SocketException(WSAGetLastError(),"侦听失败");
//5.设置非阻塞
unsigned long nonBlock=1;
if(ioctl(m_serverSocket,FIONBIO,&nonBlock)==SOCKET_ERROR) 
throw SocketException(WSAGetLastError(),"设置为非阻塞失败"); 
//6.加入集合
this->m_socketArray[0]=m_serverSocket;
m_nCurrentConCount++;
}
void SelectModelServer::startServer()
{
cout<<("star server success.\n");
int nReturn,i;
struct timeval timeDelay;
timeDelay.tv_sec=0;
timeDelay.tv_usec=1000*10;
fd_set readFdSet;
while (true)
{
FD_ZERO(&readFdSet);
for (i = 0; i <FD_SETSIZE; i++)// m_nCurrentConCount
{
if(m_socketArray[i]!=INVALID_SOCKET)
FD_SET(this->m_socketArray[i],&readFdSet);
}
nReturn=select(0,&readFdSet,NULL,NULL,&timeDelay);//返回 大于0--有效数 等于0--超时 小于0--异常
if(nReturn==0)//超时
{
if(timeDelay.tv_usec<1000*1000)//1秒
timeDelay.tv_usec+=10;//增加0.01毫秒
continue;
}
else if(nReturn==SOCKET_ERROR)
{
cout<<"select error:"<<SocketTools::getWinsockErrorMsg(::WSAGetLastError())<<endl;
continue;
}
if(timeDelay.tv_usec>1000*2)//不超时时,若延时大于2毫秒则减少
{
timeDelay.tv_usec-=1000;//减少1000微秒
}
#ifdef _DEBUG_TIME
{
//正常
printf("select sleep time:%d usecond\n",timeDelay.tv_usec);
}
#endif
//如何判断对方关闭
for (i=0;i<FD_SETSIZE;i++)
{
if (FD_ISSET(m_socketArray[i], &readFdSet))
{
if(i==0)
onAccept();
else
onReceive(m_socketArray[i]);
}
}
}
}
void SelectModelServer::closeServer()
{
//7.关闭服务套接字
closesocket(m_serverSocket);
//8.清除环境
SocketTools::cleanUpSocketContext();
}
void SelectModelServer::onAccept()
{
//5.接收连接
sockaddr_in peerAddr;
int length=sizeof(sockaddr);
//clientSocket=accept(m_serverSocket,(struct sockaddr*)(peerAddrs+m_nCurrentConCount),&length);
SOCKET clientSocket=accept(m_serverSocket,(struct sockaddr*)(&peerAddr),&length);
if(clientSocket==INVALID_SOCKET)
{
printf("accept error\n");
return;
}
//连接达到上限
if(m_nCurrentConCount>=FD_SETSIZE-1)
{
closesocket(clientSocket);
printf("reached max con:%d\n",m_nCurrentConCount);
return;
}
//数组前面的某个socket关闭了
if(m_socketArray[m_nCurrentConCount]!=INVALID_SOCKET)
{
int i;
for(i=0;i<m_nCurrentConCount;i++)
{
if(m_socketArray[i]==INVALID_SOCKET)
{
m_socketArray[i]=clientSocket;
break;
}
}
if(i==m_nCurrentConCount)//异常
{
printf("error when deal with con:%d\n",m_nCurrentConCount);
closesocket(clientSocket);
return;
}
}
else
{
this->m_socketArray[m_nCurrentConCount]=clientSocket;
}
//增加一个连接
{
m_pReceiver->addPeerAddr(clientSocket,peerAddr);
m_nCurrentConCount++;
}
}
void SelectModelServer::onReceive(SOCKET& clientSocket)
{
//6.接收信息
/*
char buf[1024];
int length=recv(clientSocket,buf,sizeof(buf),0);
if(length<=0 ||  strcmp(buf,"exit")==0)
{
printf("\n error\n");
onClose(clientSocket);
return;
}
buf[length]='\0';
printf("收到信息(字节数%d):%s",length,buf);
//*/
unsigned long length=0;
ioctl(clientSocket,FIONREAD,&length);
if(length==0)
{
this->onClose(clientSocket);
return;
}
this->m_pReceiver->onReceive(clientSocket,*this);
}
void SelectModelServer::onClose(SOCKET& clientSocket)
{
if(clientSocket==INVALID_SOCKET)
return;
int n=closesocket(clientSocket);
if(n==SOCKET_ERROR)
{
printf("close socket error\n");
return;
}
m_pReceiver->removePeer(clientSocket);
clientSocket=INVALID_SOCKET;
m_nCurrentConCount--;
}
void SelectModelServer::close(const SOCKET&  socket)
{
this->onClose((SOCKET)socket);
}

Select模型中:1、创建监听套接字并绑定端口及进入侦听状态,初始化套接字集合,用FD_SET将套接字加入集合;2、利用select函数选择出请求建立连接的用户与之建立连接即调用accept;3、利用select函数选择出可读套接字,调用recv读取数据。循环往复的执行2、3步骤。
  服务器套接字接收请求、读写操作
  在服务套接字侦听状态时,以while循环来选择请求连接用户与之建立连接、选择可读套接字读入数据,分别调用accept()、recv()方法,这里将内容读入到了string中: 
 len= readBytes(buf , BUFFER_SIZE , socket); 
 requestContent+=string(buf,len);
  readBytes()是浅层封装了recv的一个方法,其中加入了异常处理。
  发送回应时调用send()函数。

有了底层的Socket模块,我们可以用其接收请求和发送回应了,接下来看看收到HTTP请求后的处理流程:
void SocketChanel::onReceive(const SOCKET& socket,SelectModelServer& server)
{
ClientMsg *pClientMsg=this->getClientMsg(socket);

string requestContent="";
/*try{
str=this->readLineByGbk(socket);
}catch(SocketException& e)
{
e.printErrorMsg();
if(e.getError()==10054)
server.close(socket);
return;
}*/
char buf[BUFFER_SIZE];
int len,count=0;
try{
while(true)
{
len=this->readBytes(buf,BUFFER_SIZE,socket); //File file("d:/a.txt");file.writeBytes(buf,len);
//if(pClientMsg->request.isStart)
// pClientMsg->receivedLength+=len;
requestContent+=string(buf,len);
if(len==BUFFER_SIZE)
{
count++;
if(count<10)
continue;
}
break;
}
}catch(SocketException& e)
{
e.printErrorMsg();
if(e.getError()==10054)
server.close(socket);//必须交给SelectModelServer处理!
return;
}
/*****************************************************************************************/
//printf("\n收到信息(last size is %d) :\n%s\n",len,requestContent.c_str());
//解析
m_httpParser.parse(requestContent,pClientMsg->request);
doSomethingOther(requestContent,pClientMsg->request);
//响应
HttpResponse response(socket);
pClientMsg->pMusicList=&player;
try{
service(*pClientMsg,response);
//server.close(socket);//为何要关闭才显示?
}
catch(Exception& e)
{
e.printException();
server.close(socket);
}
//出错时直接关闭
if(pClientMsg->request.isError)
{
server.close(socket);//是否有更好的方法?
return;
}
}


回过来,总体的看看代码的执行流程
 首先执行主函数:
 新建主类: MainClass mainClass;
 启动8000端口: mainClass.run(8000); 
 在run方法中:
		try{
SocketChanel receiver;//实现ReceiveAble接口的处理数据类
SelectModelServer server(receiver); //监听类,并注入receiver
server.init(port); //初始化端口
server.startServer();//进入select循环
}catch(Exception e)
{
e.printException();
}

在select循环中,当有数据可读时调用receiver的onReceive方法来处理数据;在onReceive中,先将数据读入到字符串requestContent中,然后调用HttpParser的parse方法进行解析,之后调用SocketChanel 的service方法来处理用户请求,最后利用HttpResponse来响应请求。

总体的流程有了,下面看看每部分具体的处理过程:
收到数据之后,
第1步,解析数据,把它还原为一个可以理解的HTTP请求包,在这里用HttpRequest来封装一个请求;
第2步,根据HttpRequest所请求的内容做出相应的处理;
第3步,将处理的结果,这里用HttpResponse来封装,返回给客户端.
待续...

------解决思路----------------------
啊哈,what are you want to di?
------解决思路----------------------
免费提供一个C++编程及网络编程的案例,还附送有关问题解答,是的,你没有听错^
------解决思路----------------------
不错。先收藏了
------解决思路----------------------
一个DDOS攻击就废了   
------解决思路----------------------
有时间再看看. 开源是一种文化, 支持楼主!