关于TCP/IP模型的简单介绍,在TCP/IP五层模型一文中有简单介绍,本文主要详细介绍其中的应用层相关内容。
首先介绍一个定义:
应用层协议:在网络版加法计算器一文中,我们介绍的实现网络版本的加法计算器有两种方法。其实无论是哪种方法,只要能够保证,一端发送时构造的数据,另一端能够正确的解析,就是ok的,这种约定就是应用层协议。
HTTP协议——超文本传输协议
程序员们自己编写的解决我们的实际问题,满足日常需求的网络程序,都是在应用层,也就是说应用层的协议是程序员自己定的。但是实际上,已经有一些已经定义好的且非常好用的应用层协议,可以供我们参考和使用,HTTP协议就是其中之一。
所以,http协议是应用层协议,它基于TCP协议进行可靠的传送。HTTP协议定义了浏览器(万维网客户进程)以什么样的格式向万维网服务进程请求万维网文档,以及服务器以什么样的格式将文档传送给客户程序。每个万维网网点都有一个服务器进程(如果该服务器处理http请求,则端口号绑定为为80,如果为https请求,则端口号绑定为443),它不断监听来自客户端的请求。当有浏览器发送TCP连接请求时,服务器就与其建立连接,并处理请求,返回相应的页面,最后释放链接。
1. 认识URL
(1)WWW——万维网World Wide Web
WWW是环球信息网的缩写,(亦作Web、WWW、W3),英文全称是World Wide Web,中文名是“万维网”、“环球网”等。分为Web客户端和Web服务器程序,WWW可以让Web客户端(常用浏览器)访问浏览Web服务器上的页面。它是一个由许多互相链接的超文本组成的系统,通过互联网访问。 在这个系统中,每个有用的事物,称为一样“资源”;并且由一个全局“统一资源标识符”(URI)标识;这些资源通过超文本传输协议(Hypertext Transfer Protocol)传送给用户,而后者通过点击链接来获得资源。
万维网上的各站点都必须连接在因特网上(因特网:是当前全球最大的,开放的,由众多网络互连而成的特定计算机网络,采用TCP/IP协议簇作为通信的规则)。每个站点上存放了许多文档。这些文档通过某种方式形成链接可以被其他站点通过该链接来访问该文档。该文档中也可以包含一些其他文档所形成的链接来访问其他文档。所以,万维网通过链接的方式从一个站点访问另一个站点。
万维网是一个分布式的超媒体系统,超媒体是超文本的扩充,超文本是指包含指向其他文档的链接的文本。超文本的内容是一些文本内容,而超媒体中除了文本外,还有一些图像,音频之类的文档。分布式是指万维网上的文档分布在整个因特网上,每个站点主机上都包含一些万维网文档。每台主机对这些文档进行独立管理。相对的非分布式是指所有文档都保存在同一主机上。因为万维网是分布式的,所以可能存在文档与链接不一致的情形。
万维网并不等同互联网,万维网只是互联网所能提供的服务其中之一,是靠着互联网运行的一项服务。
(2)URL
在WWW上,每一信息资源都有统一的且在网上唯一的地址,该地址就叫URL(Uniform Resource Locator,统一资源定位符),也就是我们平时俗称的“网址”,具体格式如下图:
1)协议方案名:访问万维网文档时所遵循的协议,通常为http、https
2)登录信息:通常省略
3)服务器地址:即访问的万维网文档所在的主机地址。此处给的是域名,也可以为点分十进制形式的字符串IP地址。后台会根据DNS协议将域名解析为IP地址的形式,在进行访问。
4)服务器端口号:服务器程序是该主机中的哪个进程,由端口号给出。可以写也可以不写,通常省略。http协议默认的端口号为80,https默认的端口号为443
5)带层次的文件路径:所请求的万维网文档在该主机的什么地方,由该路径给出
6)查询字符串:?之后的为查询字符串,所请求的具体内容(可省略)
7)片段标识符:可省略
(3)urlencode和urldecode
在上述的URL中像“/”、“?”、“:”等字符,已经被URL当做特殊字符处理了,所以这些字符不能随意出现。。如“?”之后的内容是表示用户要查询的内容,那如果用户要查询的内容中又有“?”这样的字符,这样就会出现误解。所以如果某个参数中需要带有这些特殊字符,就必须先对这些特殊字符进行转义。
转义规则:将需要转码的字符转化为16进制,然后从右到左,取4位(不足四位直接处理),每两位为一位,前面加%,编码为%XY的格式。
如对“+”进行转义:“+”的ASCII码是43,43转为16进制为2B,此时不足四位直接处理。直接在2B前加%即可。所以“+”转义后的编码格式为:%2B。
上述的特殊字符转义的过程就是编码urlencode,与之对应的解码urldecode就是转义的逆过程。
2. HTTP协议格式
(1)HTTP请求
主要分为四部分:
1)请求行:在HTTP请求报文中第一行即为请求行,以空格为界,分为三个区域:【请求方法,常为GET/POST】+【想请求的资源url】+【HTTP协议版本,常为1.0/1.1】;
2)请求报头Header:在HTTP请求报文中从第二行到空行之前的即为请求报头,是请求属性,均以冒号分割的键值对形式呈现,每组属性间用 \n 分隔;
3)空行:表示报头已完,不能省略
4)请求正文Body:空行以后的均是请求正文,表示要提交给浏览器看的消息,允许为空字符串。若Body存在,在Header中有一个Content-Length属性来表标识Body的长度;若服务器返回一个html页面,那么html页面内容就是在Body中
其中:
1)GET方法:请求消息在正文中
2)POST方法:请求消息在报文中
主要格式如下图:
(2)响应报文
主要分四部分:
1)响应行:在HTTP请求报文中第一行即为请求行,以空格为界,分为三个区域:【协议版本号】+【状态码】+【状态码解释】;
2)响应报头Header:在HTTP请求报文中从第二行到空行之前的即为请求报头,表示请求的属性;
3)空行:表示报头已完,不能省略;
4)响应正文Body:空行以后的均是请求正文,允许为空字符串;若Body存在,在Header中有一个Content-Length属性来表标识Body的长度;若服务器返回一个html页面,那么html页面内容就是在Body中。
具体响应报文如下图(其中一部分,响应正文没有截完):
3. HTTP的方法
具体方法见下表,常用方法有GET与POST方法:
4. HTTP的状态码
HTTP的状态码有以下几种,其中重定向状态码又分为永久性重定向、临时性重定向两种。
5. HTTP常见Header(报头)
(1)Content-Type:数据类型(text/html等)
(2)Content-Length:请求正文的长度(字节为单位)
(3)Host:客户端告知服务器,所请求的资源是在哪台主机的哪个端口上
(4)User-Agent:声明用户的操作系统和浏览器版本信息
(5)referer:表明当前页面是从哪个页面跳转过来的
(6)location:跳转重定向,告诉客户端接下来要去哪访问,要搭配状态码3xx使用
(7)Cookie:是一个本地文件,用于在客户端存储少量信息,通常用于实现会话的功能。比如,在登录某个账号以及密码时,计算机允许在信息输入后,保存到本地上,以实现下次不用输入,这些信息会在,这样产生的文件就叫做Cookie。但是Cookie有安全隐患,因为保存的信息当中可能会有密码等。
Cookie的具体工作原理为:当用户访问某个带Cookie的网站时,该网站的服务器该用户产生一个标识符,并将该标识符作为索引在后台的数据库中生成一个项目。然后在响应报文中添加一个“Set-Cookie:标识符”的键值对。当浏览器收到响应之后,会将“服务器的主机名和标识符”添加在它管理的Cookie文件中。当用户继续浏览该网站时,浏览器会将在请求报文中添加“Cookie:标识符”的键值对,发送给服务器,这样服务器便可以根据标识符知道用户之前的活动状态了。
基于TCP编写简单的HTTP服务器
实现代码如下:
//HTTP服务器,基于TCP协议
//守护进程版本
#include
#include
#include
#include
#include
#include
#include
#include //bzero清空
#include
#include
void service(int sock)
{//对客户端的请求不做任何分析,均响应回去一个"hello,..."
char buf[10240];
read(sock, buf, sizeof(buf));
//HTTP响应:给一个响应行、一个空行,正文,不给响应报头
//HTTP的1.0版本是基于短链接的
const char* response = "HTTP/1.0 200 OK\n\nhello, this is YoungMay\n";
write(sock, response, strlen(response));
}
int startup(int port)
{
int sock = socket(AF_INET, SOCK_STREAM, 0);
if(sock < 0)
{
printf("socket error\n");
exit(2);
}
struct sockaddr_in local;
local.sin_family = AF_INET;
local.sin_port = htons(port);
//参数 宏INADDR_ANY,可以监听主机所有IP
local.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
{
printf("bind error\n");
exit(3);
}
if(listen(sock, 5) < 0)
{
printf("listen error\n");
exit(4);
}
return sock;
}
//./myhttpd 8080
int main(int argc, char* argv[])
{
if(argc != 2)
{
printf("Usage: %s [port]\n", argv[0]);
return 1;
}
int listen_sork = startup(atoi(argv[1]));
daemon(0, 0);
while(1)
{
struct sockaddr_in peer;
bzero(&peer, sizeof(peer));
socklen_t len = sizeof(peer);
int sock = accept(listen_sork, (struct sockaddr*)&peer, &len);
if(sock < 0)
{
//write log
continue;
}
int id = fork();
if(id == 0)//child
{
close(listen_sork);
if(fork() > 0)
{
exit(0);
}
service(sock);
close(sock);
exit(0);
}
else if(id > 0)//father
{
close(sock);
waitpid(id, NULL, 0);
}
}
return 0;
}
上述的HTTP服务器代码,我们可以用同一个局域网内的浏览器访问,具体如下图:
首先,让服务器先跑起来:
然后,用浏览器访问该服务器,访问格式即【主机号:端口号】:
同时,服务器端也会受到网页发来的请求: