对于之前学过的无论是聊天室服务器,还是回射服务器都是在Linux环境下,写一个终端客户端,用户通过客户端和服务器通过定长的数据包或者Json键值包进行交互.而对于web服务器,服务器还是在终端运行,但是客户端成为了浏览器,用户或者爬虫程序通过浏览器和服务器交互数据,对于一些数据格式分析上,C/S两端也应该达成共识,HTTP是一个客户端和服务器端请求和应答的标准.
- HTTP协议
在TCP中,服务器启动后,通过在浏览器输入服务器的 IP:端口 来建立连接,建立连接后,浏览器会发送一个请求头,拿以上请求为例,我们请求的IP是www.baidu.com端口属于知名端口,请求的资源紧跟其后为/,百度服务器处理这种请求的结果就是发送一个html网页.
请求方法是GET, 请求指定的页面信息,并返回实体主体。除此之外还有比较常用的POST, 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。
这里有一个html例子可以参考:
<html>
<head>
<title></title>
</head>
<body>
<form method="post">
<div>NAME:<input type="text" value="JAMES" name="user"/></div>
<br/>
<div>PASSWORD:<input type="password" value="James" name="pwd"/></div>
<br/>
<div>
SEX:<select name="sex">
<option>male<option>
<option>famale<option>
</select>
</div>
<br/>
<div>
<input type="submit" value="SUBMIT"/>
<input type="button" value="RESET TABLE"/>
</div>
</form>
</body>
</html>
这是一个简单的提交数据信息的html.
提交的效果:
使用post方法:
使用get方法:
很明显,使用get方法会使我们的提交数据显示在url框中,而使用post方法,它把我们的提交数据都隐藏在了请求行的最后面进行加密处理,不会显示出来,比较安全.
远程地址:即web服务器的地址和端口.
状态码是服务器返回的,200表示数据请求正常,404表示请求的资源不存在.其他的状态码可以参考状态码大全.
版本:即就是HTTP协议的版本号.
请求头
作简单分析吧!
Host:域名
User-Agent:用户代理,因为我们用的就是ubuntu Firefox浏览器来访问Host 的.
Accept:告诉WEB服务器自己接受什么介质类型,/ 表示任何类型,type/* 表示该类型下的所有子类型,type/sub-type。
Connection:使用的连接方式,长连接/短连接.此处为长连接.
响应头
挑主要的说吧!
第一行是版本和状态码.
content-type text/html; charset=utf-8 返回数据的格式及字符集编码.
Content-Length 这一次发送的数据长度.
以上是服务器响应浏览器必备的要素,其他的请参考这篇文章
一般浏览器给服务器传过来请求的格式如下:
请求行数据
GET /1.html HTTP/1.1\r\n
Host: 127.0.0.1:2345\r\n
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:64.0) Gecko/20100101 Firefox/64.0\r\n
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2\r\n
Accept-Encoding: gzip, deflate\r\n
Connection: keep-alive\r\n
Upgrade-Insecure-Requests: 1\r\n
\n
如果是post请求的话,此处应该有数据.
对于我们作服务器的来说,只关心想要请求的数据是什么?然后发送正确的响应即可.服务器端解析第一行,解析出是get请求,就不用解析最后面的信息了,要是post的话,就得解析最后面的信息.
然后空一空格,取出要请求的资源信息,此处为资源目录下的1.html文件.
在空一格,为HTTP协议版本号.
下面是服务器解析请求头行的实现代码:
//sock 是浏览器套接字 line指向每一行的首地址 s是sizeof(line)
int get_line( int& sock , char* line ,int& len)
{
int i = 0 ;
char c = '\0' ;
int n ;
while( (i < len - 1)&&(c != '\n'))
{
n = recv(sock , &c , 1 , 0 ) ;
if( n == 0 )
{
break ;
}
if( n > 0 )
{
if( c == '\r' )
{
n = recv( sock , &c , 1 , MSG_PEEK ) ;
if( n > 0 && c == '\n')
{
recv(sock , &c , 1 , 0);
}
else
{
c = '\n' ;
}
}
line[i] = c ;
i++ ;
}
else
{
c = '\n' ;
}
}
line[i] = '\0' ;
if( n == -1 )
{
i = -1 ;
}
return i ;
}
将请求解析出来以后,我们的服务器必须要组建基本的响应头,并发送资源
下面是发送响应的函数代码实现:
//connfd 套接字 no和desc是状态码 type是资源在浏览器中的显示格式以及字符集编码 len发送数据的总长度
send_response_head(int connfd, int no ,const char*desc ,const char *type ,long len){
char buf[1024];
//状态行
sprintf(buf,"http/1.1 %d %s\r\n",no,desc);
cout<<buf<<endl;
send(connfd ,buf ,strlen(buf),0);
//消息包头
sprintf(buf,"Content-Type: %s\r\n",type);
send(connfd ,buf ,strlen(buf),0);
sprintf(buf+strlen(buf),"Content-Length: %ld\r\n",len);
send(connfd ,buf ,strlen(buf),0);
cout<<buf<<endl;
//空行
sprintf(buf,"\r\n");
send(connfd ,buf ,2,0);
}
服务器每次在发送资源之前必须发送响应信息,才能发送资源信息.
下面是基于线程池的web简单的服务器实现:
git源代码 Welcome Fork And Star
下面是服务器的一些问题,加载页面时,有时候加载不出图片,不知道为什么,看好像是浏览器对图片的请求到不了服务器不知道为什么,很恼火??? 在post请求提交信息时,CGI程序能收到从服务器传来的信息,并通过将客户端套接字指定为标准输出后,请求能不断刷新尝试的情况下(不知为什么出现这些奇怪的buger~~~
),请求能到达浏览器的如下图:
但是在页面上返回的信息只是一闪而过就变成连接已重置,不知道怎么回事???调了一天多,实在不行了!!!