Linux网络基础--应用层详解

Posted 蓝乐

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Linux网络基础--应用层详解相关的知识,希望对你有一定的参考价值。

网络基础--应用层

本文将重点介绍TCP/IP四层结构模型中的应用层。

⏰应用层

应用层是我们在之前的学习中,接触的最多的一层结构,我们所写的应用程序都是运行在应用层的。

🕛再谈“协议”

我们之前简单的认识过了协议,并说过协议本质是一种“约定”。而在之前关于套接字编程中的接口介绍中,我们都是以字符串为形式来进行传输的,那么如果我们要传输的是结构化的数据呢?下面我们通过一个网络简易计算器的例子来具体介绍一下有关结构化数据传输的协议。

🕒简易网络计算器

如果我们将实现一个简易的加法器服务端,那么就需要客户端将两个操作数及运算符发送到服务端,经由服务器计算再返回给客户端。那么客户端应该以什么方式将数据传输过来呢?
第一种协议制定可以是:
客户端以字符串的方式发送类似"3+2"的字符串;而这个字符串中的两个操作数均为整型;运算符为字符'+',而且作为加法器,中间的字符只能是'+';数字与字符之间没有空格…
第二种协议制定方案:
定义结构体来表示我们要传输的数据;发送数据时将结构体按照某种规则转换成字符串,接受时再按照同样的规则转换为结构体;这个过程叫做序列化与反序列化。
无论我们采用方案一, 还是方案二, 抑或是其他的方案, 只要保证 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是没有问题的. 这种约定, 就是 应用层协议。

//协议制定
namespace ns_protocal

    struct Request
    
        int x;
        int y;
        char op;
        Request()
        :x(0)
        ,y(0)
        ,op('+')
        
    ;
    struct Response
    
        int ans;
        int status;
        Response()
        :ans(0)
        ,status(0)
        
    ;


具体代码可以参考:网络版简易计算器实现

🕛HTTP协议

虽然说应用层协议是程序员自己定的,但其实已经有计算机界的大佬制定了一些现成的、并且非常好用的应用层协议,可以供我们直接参考使用。HTTP(超文本传输协议)就是其中之一;接下来我们将介绍HTTP协议。

🕒HTTP协议格式

在正式介绍HTTP协议之前,我们先来认识一下url。

🕕认识URL

平时我们俗称的网址其实就是url(Uniform Resource Locator,统一资源定位符),比如我们所熟知的www.baidu.com就是一个url。以https://www.baidu.com/index.html这个网址为例,url的组成有:

  • 1.https:// 为协议方案名,此处代表超文本传输安全协议。
  • 2.www 代表一个Web(万维网)服务器,通常在访问时都可以省略。
  • 3.baidu.com/ 服务器域名,或站点服务器的名称。
  • 4.index.html 为文件路径,此处为网站服务器根目录下的一个html文件。

🕕urlencode与urldecode

实际上,在url中,像/ ? :等这样的字符已经被url当作特殊意义处理了,因此这些字符不能随意出现。如果某个参数中带有这些特殊字符就需要对这些特殊字符进行转义。
转义的规则为:将需要转码的字符转为16进制,然后从右到左,取4位,(不足4位直接处理),每2位作为一位,前面加上%,转换为%XY的格式。

上述过程即为urlencode,而urldecode则是urlencode的逆过程。
urlencode工具

🕒HTTP协议格式

  • HTTP请求
    我们通过TCP的socket编程来探究一下HTTP请求的格式,HTTP的服务器套接字编程与之前的套接字并无区别,其中我们直接将浏览器的访问请求打印出来:
     static void* HandlerRequest(void* args)
     
         int sock = *(int*)args;
         delete (int*)args;
         cout << "###################  begin:  " << sock << "  #######" << endl;
         char buf[4096];
         ssize_t s = recv(sock, buf, sizeof(buf), 0);
         if(s > 0)
         
             cout << buf;//打印出http请求
         

         cout << "###################  end:  " << sock << "  #######" << endl;

         close(sock);
         return nullptr;
     

我们使用浏览器访问自己的服务器ip,打印结果如下:

可见Http请求的格式为:
1)首行:方法+url+版本。
2)Header:请求的属性,形式为冒号分割的键值对,每组属性之间用\\n分隔,遇到空行\\n表示Header部分结束。
3)Body:空行后的部分为Body内容,即正文部分,Body允许为空字符串。如果Body存在,那么在Header中会有Content-Length的属性来标识Body的长度。
这其中,空行的存在是作为一个特殊符号,来表明HTTP请求报头的结束。我们知道,每一层协议都需要能够将自己的报头和有效载荷分离(即解包),那么这里的空行就是分离的特殊符号依据。
因此,所谓的HTTP协议本质就是对发过来的HTTP请求字符串进行文本解析

  • HTTP响应
    这里通过telnet工具发起HTTP请求,使用方法为:telnet + 服务器ip + 端口号,然后输入ctrl + ],再回车,发起GET / HTTP/1.0请求。

与HTTP请求类似,HTTP响应的格式为:
1)首行:版本号 状态码 状态码解释。
2)Header:请求的属性,形式为冒号分割的键值对,每组属性之间用\\n分隔,遇到空行\\n表示Header部分结束。
3)Body:空行后的部分为Body内容,即正文部分,Body允许为空字符串。如果Body存在,那么在Header中会有Content-Length的属性来标识Body的长度。如果服务器返回了一个html页面,那么html页面内容就在Body部分中。

🕒实现一个最简单的HTTP服务器

在了解了HTTP服务器的格式后,我们来搭建一个最简单的HTTP服务器,这里的服务器仅仅是用来学习HTTP协议的,不会考虑那么完善。
这里我们基于TCP来搭建HTTP服务器,除了处理请求的部分,其余代码与TCP的socket编程一模一样。

        static void* HandlerRequest(void* args)
        
            int sock = *(int*)args;
            delete (int*)args;
            //获取请求
            cout << "###################  begin:  " << sock << "  #######" << endl;
            char buf[4096];
            ssize_t s = recv(sock, buf, sizeof(buf), 0);
            if(s > 0)
            
                cout << buf;//打印出http请求
            

            cout << "###################  end:  " << sock << "  #######" << endl;

            //做出响应
            std::string body = "<html><head><meta charset=\\"utf-8\\"></head><body><h1>hello world</h1><p>欢迎来到我的测试服务器</p></body></html>";//正文内容
            std::string response = HTTP_VERSION;//版本号
            response += " 200 OK\\n";//状态码 + 状态码解释
            response += "Content-Type: text/html\\n";//后缀为.html则类型为text/html
            response += ("Content-Length: " + std::to_string(body.size()) + '\\n');//body的length
            response += '\\n';

            response += body;
            send(sock, response.c_str(), response.size(), 0);
            close(sock);//基于短链接
            return nullptr;
        

这里我们采用最简单的做出响应的方法,将response发给客户端,通过浏览器访问的结果如下:

这样,一个最简单的hello world服务器就搭建好了。
上面的代码是将body直接实现在函数中,实际上网页开发会创建一个web目录,并在该目录下写网页内容,具体的html网页可以参考:
w3schools的HTML教程
这里我们创建一个wwwroot根目录,并在该文件夹下创建index.html。

wwwroot:我们的默认web根目录
index.html: 我们的默认首页,如果请求没有携带对应的资源路径,默认返回首页信息

我们将处理请求的代码修改如下:

        static void* HandlerRequest(void* args)
        
            int sock = *(int*)args;
            delete (int*)args;
            //获取请求
            cout << "###################  begin:  " << sock << "  #######" << endl;
            char buf[4096];
            ssize_t s = recv(sock, buf, sizeof(buf), 0);
            if(s > 0)
            
                cout << buf;//打印出http请求
            

            cout << "###################  end:  " << sock << "  #######" << endl;

            //做出响应
            std::string target_page = HOME_PAGE;
            std::string body;
            std::string response;
            std::ifstream in(target_page.c_str(), std::ios::in | std::ios::binary);//打开wwwroot根目录下的index.html
            if(!in.is_open())
            
                //打开页面失败
                target_page = ERROR_PAGE;
                std::ifstream in(target_page);//打开wwwroot根目录下的404.html
                std::string line;
                while(std::getline(in, line))
                
                    body += line;
                
                response += HTTP_VERSION;
                response += "404 Not Found\\n";
                response += "Content-Type: text/html\\n";
                response += ("Content-Length: " + std::to_string(body.size()) + '\\n');//body的length
                response += '\\n';//空行
                response += body;//正文内容
            
            else
            
                //open successfully
                std::string line;
                while(std::getline(in, line))
                
                    body += line;
                
                response += HTTP_VERSION;//版本号
                response += " 200 OK\\n";//状态码 + 状态码解释
                response += "Content-Type: text/html\\n";
                response += ("Content-Length: " + std::to_string(body.size()) + '\\n');//body的length
                response += '\\n';//空行
                response += body;//正文内容
            

            in.close();

            send(sock, response.c_str(), response.size(), 0);
            close(sock);//基于短链接
            return nullptr;
        

在index.html中我们不仅可以显示文件,还可以插入图片等等,结果如下:

🕕简易HTTP服务器代码

上面的HTTP服务器参考代码如下:简易HTTP服务器

🕒HTTP请求方法

方法说明支持的HTTP协议版本
GET获取资源1.0、1.1
POST传输实体主题1.0、1.1
PUT传输文件1.0、1.1
HEAD获取报文首部1.0、1.1
DELETE删除文件1.0、1.1
OPTIONS询问支持的方法1.1
TRACE追踪路径1.1
CONNECT要求用隧道协议连接代理1.1
LINK建立和资源之间的联系1.0
UNLINE断开连接关系1.0

其中最常用的就是GET和POST方法。
在介绍GET和POST方法之前我们先在先前搭建的HTTP服务器中添加表单内容,分别实现GET和POST两种方法:

    <form action = "/home/desk" method="GET">
        搜索:<input type = "text" name = "content">
        <input type = "submit" value = "搜索一下,你就知道">
    </form>
<form action="/home/desk" method = "POST">
   Account:<input type="text" id="account" name="account"><br>
   Password:<input type="password" id="password" name="password"><br><br>
   <input type="submit" value="Submit">
 </form> 

先测试 GET方法,搜索框输入jock,点击提交按钮或直接回车,结果如下:

其实,百度搜索就是使用的GET方法。
其次我们来测试POST方法,在输入框中输入123456和123456,提交后,结果如下:

由此可见,无论是GET还是POST,在服务器端都能够看到用户提交的数据,因此对于服务器来说,两种方法提交的数据都是透明的,即两种方法都是不安全的,而如果要保证安全性,就需要进行加密。
POST相较于GET的优点在于:1.POST可以保证用户提交时数据的私密性,在提交时用户数据不会回显。2.POST提交的数据最终在服务器端的正文部分,因此提交的数据可以很大。
POST和GET两种方法相比:

  • 作为HTTP请求方法,GET和POST两种方法都不安全,因为二者默认情况下都没有加密。
  • GET通常用于网页等资源的获取,但也可以提交数据,比如百度搜索。
  • POST通常用于提交数据,并且能保证数据更加私密。且POST请求不会被缓存,并且对数据长度没有限制。

🕒HTTP的状态码

状态码类别原因
1xxInformational(信息性状态码)接收的请求正在处理
2xxSuccess(成功状态码)请求正常处理完毕
3xxRedirection(重定向状态码)需要进行附加操作以完成请求
4xxClient Error(客户端错误状态码)服务器无法处理请求
5xxServer Error(服务器错误状态码)服务器处理请求错误

🕕常见状态码详解

1)4xx 客户端错误状态码,404详解
我们经常会见到的404状态码,对于404状态码,我们在上面实现的服务器,将wwwroot根目录下的index.html文件移除,此时访问页面,发现:

其实当我们访问http://122.51.228.35:8080/这个网站时,虽然没有带上具体的页面,但其实请求的是wwwroot根目录下的默认index文件,而当我们移除这个文件后,此时客户端访问到的页面不存在,因此就会是404状态码,即未能找到相关页面。其实,这对于其他网站也是如此,访问baidu网站的a目录下的s文件,结果显示404,即请求的url未在服务端找到。

2)3xx重定向状态码
其次是关于3xx的状态码,通常作为一个中间态。其中像301, 308是永久重定向状态码;而302,303,307这些是临时重定向状态码。
重定向的本质是通过服务器返回的3xx状态码,浏览器识别后,提取location即新网址,然后访问新网址;也就是说,重定向的本质就是浏览器发出二次请求,进行网站的访问。
那么浏览器是怎么知道3xx状态码代表的是重定向的,而又要从location提取新网址,其实就是通过HTTP协议在客户端和服务端之间的约定来实现的。
这里我们模拟实现307和301两种重定向,将网址重定向为b站首页。首先将处理请求给出响应部分的代码替换为:

            //做出响应
            //模拟实现307(Temporary Redirect)临时重定向
            std::string status_line = HTTP_VERSION;//状态行
            status_line += " 307 Temporary Redirect\\n";//状态码 状态码解释
            
            std::string response_header = "Content-Type: text/html; charset=UTF-8\\n";
            response_header += "location: https://www.bilibili.com/\\n";
            response_header += "\\n";//空行

            send(sock, status_line.c_str(), status_line.size(), 0);
            send(sock, response_header.c_str(), response_header.size(), 0);

此时我们在浏览器中输入我们的ip和端口号,访问到的就是b站的官网。使用telnet本地环回访问我们的网址,得到的响应如下:可以看到临时重定向的网址。


但由于是临时重定向,因此如果我们修改了网址,或者改变重定向内容, 那么请求的资源也会更改。
而301(Moved Permanently),其模拟结果与307类似,表示请求的资源已经永久性的转移了。此时如果浏览器将其收藏为书签,那么会看到输入该网址后仍会看到重定向后网址的logo。

🕒HTTP常见Header

 - Content-Type: 数据类型(text/html等)
 - Content-Length: Body的长度
 - Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
 - User-Agent: 声明用户的操作系统和浏览器版本信息;
 - referer: 当前页面是从哪个页面跳转过来的;
 - location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
 - Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

🕒HTTP协议特点

1)HTTP协议本身是无连接的。
HTTP协议是基于TCP的,而TCP是基于连接的,为什么说HTTP是无连接的呢?这是因为TCP是传输层协议,而HTTP是应用层协议,在浏览器发起HTTP请求时,并不会在HTTP层建立连接,也就是说HTTP本身是无连接的。
2)HTTP协议本身是无状态的。
无状态的意思是HTTP协议不会保存HTTP请求的上下文数据,即HTTP协议对历史请求没有记忆能力。比如我们访问b站时,登录后关闭浏览器再次打开b站此时需要重新登录,这就是HTTP的无状态性。但是这与我们的常识是违背的,实际上我们登录某个网页后,往往只要不是很长一段没有登录,再次登录上去都无需重新登录。那么这是为什么呢?实际上和我们后面要介绍的cookie与session有关。
3)HTTP发起请求时,可以基于长链接,也可以基于短链接。
我们上面实现的简易http服务器就是基于短链接的,即服务器端的线程处理完一个来自客户端的请求后,该线程就被线程池回收,请求便被关闭了;基于长连接的http协议减少了请求和连接的时间,但是每次交互都是由客户端发起的,客户端发送消息,服务端才能返回客户端消息。因为客户端也不知道服务端什么时候会把结果准备好,所以客户端的很多请求是多余的,因此长链接其实弥补短链接多可能造成并发多的一种补足方式。

🕒cookie & session

我们刚刚说到HTTP协议是无状态的,即HTTP协议对历史请求没有任何记忆能力,但是这样给用户的体验是相当不方便的。那么为了能够让用户拥有更加舒适的体验,就需要cookie和session来让HTTP具有保持状态的能力,从而当我们再次来到网页时,可以不需要重新登录。
由于关于cookie和session的知识比较多,本文只是从浅显的角度来介绍一下。

🕕cookie相关

cookie是存储在用户本地终端上的数据,通常每个浏览器中都会有默认文件用于存储用户相关数据,这些文件就是cookie文件。

☂cookie的组成:

(1)Name/Value:设置Cookie的名称及相对应的值,对于认证Cookie,Value值包括Web服务器所提供的访问令牌。
(2)Expires属性:指定了coolie的生存期,默认情况下coolie是暂时存在的,他们存储的值只在浏览器会话期间存在,当用户推出浏览器后这些值也会丢失,如果想让cookie存在一段时间,就要为expires属性设置为未来的一个过期日期。现在已经被max-age属性所取代,max-age用秒来设置cookie的生存期。
(3)Path属性:Cookie的使用路径。如果设置为“/sessionWeb/”,则只有contextPath为“/sessionWeb”的程序可以访问该Cookie。如果设置为“/”,则本域名下contextPath都可以访问该Cookie。注意最后一个字符必须为“/”。
(4)Domain属性:可以访问该Cookie的域名。如果设置为“.google.com”,则所有以“google.com”结尾的域名都可以访问该Cookie。注意第一个字符必须为“.”。
(5)Secure属性:该Cookie是否仅被使用安全协议传输。安全协议。安全协议有HTTPS,SSL等,在网络上传输数据之前先将数据加密。默认为false。
(6)HTTPOnly 属性 :如果cookie中设置了HttpOnly属性,那么通过js脚本将无法读取到cookie信息,这样能有效的防止XSS攻击,窃取cookie内容,这样就增加了cookie的安全性,但不是绝对防止了攻击。

其实浏览器发起HTTP请求时,在请求中都会携带着浏览器保存的cookie信息,发送给服务器,而服务器也支持cookie技术,当然其中认证的过程,就不归HTTP管了,而是程序员自己去完成认证的。

浏览器根据配置,其cookie文件可能有内存级的(一般随浏览器关闭而消失,再次打开浏览器后网站依旧不认识用户);也可能是文件级别的,即不随浏览器关闭而消失,但往往长期不登陆也会过期。
我们打开b站,点击上方网址栏前面的锁,可以看到b站在浏览器存储的相关cookie,这些cookie就可以保证我们访问b站相关网页时无需反复登录,从而大大改善了用户的体验。

我们也可以在我们的服务器中添加Cookie功能,在响应报头中加上Cookie代码即可:

response += "Set-Cookie: sessionid=1234560abc\\n";

🕕session相关

如果浏览器只是保存cookie信息的话,会有较大的风险,因为cookie文件中往往保存着用户相关的私密信息,而且大概率是基于文件的,那么如果计算机被植入了木马病毒,就可能盗取这些cookie信息,从而被不法分子利用;比如qq的账号,密码被盗取从而用于诈骗相关犯罪行为。
那么既然只有cookie并不能保证安全性的问题,就需要用到session来配合使用来进行会话管理(绘画保持)。

加上session之后的区别就在于:用户的个人信息是保存在企业的服务端,而企业的服务端安全级别是远高于用户的,且网络黑客对企业的攻击也是需要考虑成本的;而在客户端的cookie中只是保留了session id,因此用户的个人信息是不会泄露的。

🕕加强cookie的安全性

  • 在设置cookie时,可以设置cookie的域名参数domain,标识cookie在特定站点的合法性。
  • 将cookie的HttpOnly或Secure设置为true。
  • 给cookie设置expire期限,让cookie具有有效期。
  • 对保证在cookie里面的敏感信息加密。
  • 给Cookies加个时间戳和IP戳,让Cookies在同个IP下多少时间内失效。

🕒HTTPS协议

HTTP虽然使用极为广泛, 但是却存在不小的安全缺陷。我们上面在介绍GET和POST请求方法时,已经知道了实际上两种方法都不安全,只是数据是否私密而已,而数据其实是在网络裸奔的,因此,HTTP协议是不具有安全性的。
而HTTPS协议(超文本传输安全协议)应运而生,HTTPS 协议是由 HTTP 加上 TLS(Transport Layer Security)/SSL(Secure Sockets Layer 安全套接层) 协议构建的可进行加密传输、身份认证的网络协议,主要通过数字证书、加密算法、非对称密钥等技术完成互联网数据传输加密,实现互联网传输安全保护。
总之,HTTPS协议需要申请CA协议,是加密的传输算法,通常使用443端口(HTTP协议通常使用80端口)

🕛总结

本文简单介绍了网络中TCP/IP四层结构模型中的应用层,总共介绍了协议的相关内容,详细介绍了HTTP协议并搭建了最简单的HTTP服务器,如果这篇文章对你有帮助的话,点一个小小的赞可以吗(≧︶≦))( ̄▽ ̄ )ゞ

以上是关于Linux网络基础--应用层详解的主要内容,如果未能解决你的问题,请参考以下文章

详解Linux中的cat文本输出命令用法

02-03根文件详解

Linux 学习笔记 2022-11-29---------Vim文本编辑器及其应用详解

Linux网络基础--应用层详解

Linux网络基础--应用层详解

Linux网络基础--应用层详解