目录
介绍url
UrlEncode和UrlDecode
http request的基本格式
对request进行反序列化
http response的基本格式
telnet指令
对response进行序列化
之前我们写的几个小业务都属于基于tcp和udp的系统调用而完成的应用层的开发。当然,应用层协议是我们自己定的,那么此时就会有一些大佬也会去定义一些好用的应用层协议,供我们直接使用,其中HTTP(Hyper Text Transfer Protocol),超文本传输协议(说人话就是不止可以传文本,还能传视频,音频等一些二进制文件)就是其中之一
HTTP协议是客户端和服务器之间通信的基础,客户端通过HTTP协议向服务器发送请求,服务器收到请求后处理并返回响应。
HTTP协议是一个无连接、无状态的协议,即每次请求都需要建立新的连接,且服务器不会保存客户端的状态信息
HTTP是基于TCP的,可是TCP不是有连接的吗?是的,但是HTTP在TCP上层,它拿到的不过是一个文件描述符,HTTP无连接跟TCP有连接有什么关系呢?
介绍url
url全称Uniform Resource Locator(统一资源定位符),其实就是我们常说的“链接”,“网址”,比如这有一个“链接”:
https://mbd.baidu.com/newspage/data/landingsuper?context=%7B%22nid%22%3A%22news_10226129278580651477%22%7D&n_type=-1&p_from=-1
我们可以给它大致分为三部分,那么如何理解呢?
我们知道访问一个网站,实际上就是去服务器上获取资源,资源其实就是文件,那么如何找到这个文件(互联网中唯一的一个文件)呢?我们需要服务器的ip和文件在服务器上的路径即可
所以上面的三部分就是http协议,ip,文件路径
其实这个文件路径并不是说在根目录下的某个路径,而是跟进程有关的,我们叫做web路径,那其实就需要端口号,那为什么不体现在url中呢?其实port在不同的协议下是固定的:
http:80 https:443
UrlEncode和UrlDecode
比如我们搜abcd,abcd其实是作为关键字显示在url上的
那我们搜个汉字呢?比如“服务器”
https://cn.bing.com/search?q=%E6%9C%8D%E5%8A%A1%E5%99%A8&qs=n&form=QBRE&sp=-1&lq=0&pq=%E6%9C%8D%E5%8A%A1%E5%99%A8&sc=10-3&sk=&cvid=87223900320C42F5975DEF92CBECC3F5&ghsh=0&ghacc=0&ghpl=
汉字会变成%+十六进制的形式,这其实就是对汉字进行encode了,并且用的是utf-8标准的,我们可以把这些编码后的内容在解码成汉字
我们可以看到在url中是有一些特殊字符的,比如:?&//,这些字符用作分割用,正常内容中不能出现这些符号,所以也是需要编码的
我们写Http服务时,只需要写服务器就可以了,因为浏览器默认就是Http协议的,可以作为天然的客户端,那我们就简单的写一个Http服务,并且用浏览器去请求,看看浏览器会向我们的服务器发送什么
我们还是用之前封装的socket套接字,TcpServer的外部服务就是我们下面写的http服务:
class HttpServer { public: std::string HandlerHttpRequest(std::string req) {std::cout<<"-----------------------------"<<std::endl;std::cout<<req;std::string response="HTTP/1.0 200 OK\r\n";response+="\r\n";response+="<html><body><h1>hello world!!!</h1></body></html>";return response; } private: };
我们可以运行代码,用浏览器ip:port的形式访问,我们可以得到如下的内容:
我们通过浏览器访问一次网站实际上浏览器向服务器发送了两次请求,一次是请求web根目录,一次是请求网站图标(favicon)
http request的基本格式
上面我们简单的看了看 浏览器发给服务器的请求的大致样子,下面我们就来详细的介绍一下并且用代码对发来的请求进行反序列化处理:
请求方法比如GET、POST、HEAD等等
URI(Uniform Resource Identifier)其实是统一资源标识符,用来指明指定服务器上的什么资源,其实URL是URI的一个子集,因为URL包含的内容更多,我们可以简单的认为URI其实就是URL中的路径部分
HTTP版本其实是client的版本,比如http/1.0 http/1.1 http/2.0
回车符+换行符其实就是"\r\n"
请求报头其实包含若干个kv结构,比如如何有请求正文(可以没有)的话就有Content-Length: xxx
并且空行可以保证我们把报头和有效载荷分离
对request进行反序列化
我们需要将客户端发过来的一整个大字符串进行整理,整理成一个个的有用信息
至于如何整理,其实就是字符串的分割工作,非常简单,在后面的代码中会有
关键就是谈uri,这个uri我们可以从前面的request中看到,它是'/',难道这个指的是根目录吗?其实理论上它可以是任何目录,我们一般把这个目录放到与后端代码同级下,名字叫做wwwroot,一些前端文件,图片等就放在这个目录下
所以当我们找'/'时,其实是"wwwroot/",我们要在代码中加上wwwroot前缀,并且wwwroot下有很多文件,如果只是访问'/',我们一般就是提供"index.html"这个文件,所以'/'其实就是访问"wwwroot/index.html"这个文件
http response的基本格式
这里的HTTP版本是服务端的版本
状态码和状态码描述:比如200是OK,404是NOT FOUND
并且响应正文一般是要有的
下面大致解释一下为什么请求和响应都要加上自己的版本,就像我们平时使用的app一样,app会不断更新,增加新的功能,此时不仅客户端要更新,服务端也要更新,那如果是老版本的客户端发来请求,就得是老版本的服务端去处理。要保证它们的版本要相同。
telnet指令
这个指令我们可以用来访问远程网络服务,比如我们访问一下百度
然后发起请求
然后我们看看百度的应答
这个格式跟我们上面说的格式是一样的
对response进行序列化
我们作为服务器,是要对结构化的数据进行按上面的规则进行序列化然后发回给客户端的,基本的结构化字段有这么几个
我们需要把这些字段转化为字符串即可
最后,我们把客户端发过来的字符串进行反序列化后拿到路径,然后再把该路径下的文件以二进制的形式作为响应正文再添加报头发回给客户端即可
其实我们在一个网站中访问网页,进行页面跳转,每一次访问,都是一次http请求
网站站在程序员视角,其实就是一对特定目录和文件构成的目录结构
所以我们写的代码其实就是负责把特定目录下的特定资源发给client的IO程序
一张网页中可能有图片,所以浏览器会发起二次或多次请求
code