网络基础(二)

讲述具体的各层协议,自顶向下讲述。

应用层

我们程序员写的满足日常需求的网络程序,都是在应用层。

再谈协议

协议是一种"约定",socketapi的接口,在读写数据时,都是按比特位的方式来发送接收的。如果我们要传输一些"结构化的数据"怎么办呢?

结构化数据直接发不好发,结构化数据本质是多条数据,我们可以将多条数据转成一条完整数据统一发出去。对端接收之后再把一条消息转成多条消息。

#include<iostream>

struct stu{
    char name[100];
    char sex;
    int age;
    int high;
    int weight;
};

image-20220207151030992

网络版计算器

概念

例如,我们需要实现一个服务器版的加法器,我们需要客户端把要计算的两个加数发过去。然后由服务器进行计算,最后再把结果返回给客户端。

约定方案一: 客户端发送一个形如"1+1"的字符串; 这个字符串中有两个操作数,都是整形;两个数字之间会有一个字符是运算符, 运算符只能是+;数字和运算符之间没有空格; …

约定方案二: 定义结构体来表示我们需要交互的信息; 发送数据时将这个结构体按照一个规则转换成字符串, 接收到数据的时候再按照相同的规则把字符串转化回结构体; 这个过程叫做"序列化"和"反序列化”。

注意:方案二也存在问题,因为不同环境的结构体内存对齐是不同的。现在demo是演示为主。并且进行基于短连接(服务周期完直接断开,服务端先断开)完成对应的计算。

代码实现

制定协议:

基于应用层的自定义协议代码

#ifndef __PROTOCOL_HPP__
#define __PROTOCOL_HPP__

#include<iostream>

typedef struct request{
    int x;//left
    int y;//right;
    char op//+-*/%
}request_t;

typedef struct response{
  	int code; //0,1,2,3
    int result;
}response_t;

#endif

server.hpp

#ifndef __SERVER_HPP
#define __SERVER_HPP

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string>
#include<map>
#include<stdlib.h>
#include<functional>
#include<unistd.h>
#include"Protocol.hpp"
#include<sys/wait.h>
#define NUM 5
#endif

class Server{
    private:
      int _port;
      int _lsock;
      std::map<char,std::function< void(struct request&,struct response&)>> opMap=
      {
        {'+',[&](struct request& requ,struct response& resp ){ resp.result = requ.x+requ.y; }},
        {'-',[&](struct request& requ,struct response& resp ){ resp.result = requ.x-requ.y; }},
        {'*',[&](struct request& requ,struct response& resp ){ resp.result = requ.x*requ.y; }},
        {'/',[&](struct request& requ,struct response& resp ){
            if(requ.y==0) resp.code = 1;               
            else resp.result = requ.x/requ.y; 
            }
        }
      };//声明时给缺省
    public:
      Server(int port)
      :_port(port),
       _lsock(-1)
  {


  }
      void ServerInit()
      {
          _lsock = socket(AF_INET, SOCK_STREAM, 0);
          
          struct sockaddr_in local;
          local.sin_family = AF_INET;
          local.sin_port = htons(_port);
          local.sin_addr.s_addr = INADDR_ANY; 

          if( bind(_lsock ,(struct sockaddr*)&local,sizeof(local)  ) < 0 )
          {
              std::cerr << "bind error "<< std::endl;
              exit(1);
          }

          if( listen(_lsock,NUM) < 0 )
          {
              std::cerr << "listen error "<<std::endl;
              exit(2);
          }
      }
      void service(int sock)
      {
           /*短连接*/  
           struct request requ;
        
           recv( sock , &requ, sizeof(requ), 0 );
            
           struct response resp; 
           resp.code = 0; //默认为0
           std::cout<<"get a task:"<<std::endl; 
           std::cout<<requ.x << " "<<requ.op << " "<<requ.y <<std::endl;

           std::cout<<resp.code<<" "<<resp.result<<std::endl;
           if(!opMap.count((requ.op)))  resp.code = 3;//输入的符号不存在
           else{
               auto res = opMap[requ.op];
           	   res(requ,resp);
           } 
          //auto func= [&](struct request& requ,struct response& resp ){ resp.result = requ.x+requ.y; };
          // func(requ,resp);
          // std::cout<<resp.code<<" "<<resp.result<<std::endl;
           send( sock, &resp , sizeof(resp), 0 );
           close(sock);
      }
      void start()
      {
          struct sockaddr_in end_point;
          socklen_t len = sizeof(end_point);
      
          while(true)
          {
            
              int sock = accept( _lsock, (struct sockaddr*)&end_point,&len );

              if( sock < 0 )
              {
                  std::cerr <<" accept false " <<std::endl;
                  continue;
              }
              std::cout <<" get a link ..."<<std::endl;
              if( fork() == 0 )
              {
                  if( fork() > 0 )
                  {
                     exit(0);
                  }
                  close(_lsock);
                  //孙子进程处理逻辑
                  service(sock);
                  exit(0);
              } 
              close(sock);
              waitpid( -1 ,nullptr,0);
          }
      }
      ~Server()
      {
         close(_lsock);
      }
};

Client.hpp

#ifndef __CLIENT_HPP
#define __CLIENT_HPP

#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<string>
#include<stdlib.h>
#include<functional>
#include<unistd.h>
#include"Protocol.hpp"
#include<sys/wait.h>
#define NUM 5
#endif

class Client{
    private:
      std::string _ip;
      int _port;
      int _sock;
    public:
      Client(std::string ip,int port)
        :_ip(ip),
         _port(port),
        _sock(-1)
      {}
      void ClientInit()
      {
           _sock = socket(AF_INET, SOCK_STREAM,0 );
      }
      void service(int sock)
      {
           /*短连接*/  
           struct request requ;
        
           recv( sock , &requ, sizeof(requ), 0 );
            
           struct response resp; 
           resp.code = 0; //默认为0
           
           send( sock, &resp , sizeof(resp), 0 );
      }
      void start()
      {
          struct sockaddr_in local;
          socklen_t len = sizeof(local);
          local.sin_family = AF_INET; 
          local.sin_port = htons(_port);
          local.sin_addr.s_addr = inet_addr(_ip.c_str());
          if(connect(_sock,(struct sockaddr*)&local , sizeof(local) ) < 0 )
          {
              std::cerr<<" connect error "<<std::endl;
              exit(4);
          }
          struct request requ;
          std::cout <<"x:";fflush(stdout);
          std::cin>>requ.x;
          std::cout<<"op:";fflush(stdout);
          std::cin>>requ.op;
          std::cout<<"y:";fflush(stdout);
          std::cin>>requ.y;

          send(_sock , &requ, sizeof(requ),0);

          std::cout<<"#############"<<std::endl;
          struct response resp;
          recv(_sock , &resp, sizeof(resp),0);

          if(resp.code != 0 )
          {
             std::cout<<"exit code is "<<resp.code<<std::endl;
          }
          else std::cout<<"result :"<<resp.result<<std::endl;
      }
      ~Client()
      {
         close(_sock);
      }
};
小结

我们有没有定协议

我们写的代码本质是基于应用层的一个自定义协议的代码。

如何体现序列化与反序列化的?

看到代码并没有做序列化但是对端直接就通过结构体拿出x和y了。原因是我们send过去的是一个结构体request,通过二进制流发送。当client收到request的时候可以看成二进制流,只不过这个二进制流的大小和空间布局和request结构体的结构化数据刚好吻合,碰巧可以直接使用。

因此没有严格意义上手动做序列化和反序列化。

但是这种写法是严重不推荐的。比如qq服务器在linux64位编写,而我们的客户端如果是win32下编译生成的。如果结构体中包含数组,指针等就会导致对齐方式,结构体大小不同,直接反序列就会出问题。

实际中我们一般使用json或者xml进行序列化,或者自定义协议比如说接下来的http。

传输层抓包——tcpdump

tcpdump:传输层的基本都可以抓

参数:

-i:只抓某个端口的数据

any:发送到当前主机的都抓

-n:将主机名等显示成数字

-nn:将更多的信息显示成数字

port:指定端口

tcp/ucp/icmp:指定抓包的协议

sudo tcpdump -i any tcp port 8080
sudo tcpdump -i any -nn tcp port 8080

这里可以演示出三次握手和四次挥手,以及传输的过程。

就是上一份笔记中的tcp传输协议图。

三次握手:

image-20220208162838646

四次挥手:

image-20220208162933432

HTTP协议

HTTP是应用层协议,由我们自己定。

虽然在之前的小结部分讲到应用层协议是我们程序员自己定的,但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用。HTTP(超文本传输协议)就是其中之一。

认识URL

平时我们俗称的"网址"其实就是说的URL。

https://sports.qq.com/a/20220207/007059.htm

https为协议名:http默认绑定端口为80;https默认绑定端口为443。相比http多了加密机制。

blog.csdn.net为域名:对应IP地址。

/a/20220207/007059.htm:第一个 / / /基本不是根目录,是web根目录,可以是linux服务器上的任何一个路径,web服务部署在哪个路径下一般该 / / /就是web根目录。直接请求 / / /其他什么都没有就是获取web根目录的index.html首页。

htm和html是网页:网页是文件。

上述URL本质是把远端服务器上的文件数据拿到本地浏览器,让浏览器解释。我们之前的实现将数据从服务器拿过。文件传输过程就是客户端传入路径,服务器打开对应文件,读取文件,将文件内容写到套接字里,send给客户端

http://user:pass@www.example.jp:80/dir/index.htm?uid=1#ch1 

image-20220207183834624

一般情况下请求服务器端口号绝对不能省略,我们平时用的省略了是因为浏览器知道我们使用的协议,而知名协议比如http和https是强绑定的,文字和数字之间已经形成等价关系了。

互联网行为:

  1. 把服务器的数据拿下来。
  2. 把自己的数据上传到服务器。

?后跟的是这次URL请求的参数。用于客户端数据提交使用。在搜索引擎中搜索的关键词为wd,即参数。采用key=value形式传参。

image-20220207184146031

urlencode和urldecode

/ ? : 等这样的字符,已经被url当做特殊意义理解了。因此这些字符不能随意出现。比如,某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。

转义的规则如下:将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。

image-20220207225606966

客户端发送的时候将对普通符号直接编码,特殊符号转义编码,这个动作叫做urlencode

服务器端对接收的符号解释回来,如果是特殊符号则转换回来,这个动作叫做urldecode

例如:"+“被转义成了”%2B"。

image-20220207230044758

要理解的是,如果我们向服务器提交某些关键字时,倘若该关键字内部包含了某些特殊符号,我们是需要对该特殊符号进行转化的,这个转化叫urlencode,到了服务器端再转回来叫urldecode

urlencode工具

HTTP协议格式

HTTP协议叫做超文本传输协议。

基本特征

总的基本状态:

  1. 无连接
  2. 无状态
  3. 简单快速
无连接

HTTP底层是基于TCP,TCP本身是面向连接的。HTTP是建立在TCP之上的,HTTP本身并不关心TCP通信相关的所有细节,TCP面向连接和HTTP无关,HTTP只关心数据能可靠地发送过去。

  1. HTTP本身的无连接性和TCP的连接性两者没关系。
  2. HTTP的无连接性重点体现在一旦TCP连接建立,不需要HTTP在应用层再互相建立连接。TCP连接建立好了,HTTP的请求直接发。因此不用协商HTTP通信相关细节。

**TCP建立连接和http无关,http直接向对象发送http request即可。**之前的网络计算器代码也是这样体现的,上层只有request和response的交互。

具体细节会问,在抓包工具中的抓取https协议中提及。TCP本身的打开和关闭由我们手动close控制。http无连接重点体现在客户端向服务器发requset,服务器回response,但是底层的tcp连接关闭与否取决于http协议。http协议为了更好地复用连接,可以选择短链接或者长连接。keep-alive(长连接),close(短连接)。

无状态

TCP协议有状态,比如发送SYN,同步发送,发送ACK等状态。(在TCP协议图上有)

应用层的HTTP使用的时候并不关心TCP的状态,只关心TCP能不能用。

HTTP无状态最典型的特征是HTTP是给用户用的,服务器和客户端双方在用户实际访问网站时并不记得用户曾经来过,并不关心用户有哪些数据上传,当前是什么状态。HTTP协议只关心用户发送了request,服务器就应该构建response,解释出来就可以。

而实际生活中注册用户,登录后在网站中随意跳转,网站总是认识我的。体现在如果是一个非注册用户打开知乎网页是打不开的,而注册用户可以打开。并且点开浏览痕迹等发现网站是记住我的。这个功能并不是HTTP做的。

HTTP本身是无状态的,并不会记录任何用户信息,关心request<->response,记录用户的基本信息的技术是cookie+session

简单快速

简单的体现:

HTTP超文本传输协议是基于短链接进行文本(html,img,css,js…)传输,所谓短链接就是客户端一请求服务器响应请求处理请求,服务器响应完response之后服务器把连接关掉,一来一回就结束了。[http/1.0:短连接]

[http/1.1:长连接]

快速的体现:

底层用的tcp协议来进行数据本身的发送,本身传送的内容相当一部分可以包含文本资源,音频资源等。

特征小结

cookie部分在最后对应章节。

image-20220208150732034

HTTP构成

request和response是数据,这两份数据底层交互采用的是tcp。服务器拿到request要进行解析,所有的协议解析本质是进行数据分析,而数据分析的前提是数据可靠完整地发送。而tcp本身是面向字节流的,意味着上层从tcp的缓冲区时读取数据时怎么知道该http请求已经发送完了,response也是如此。

image-20220208094819592

互联网行为:

  1. 把服务器的数据拿下来。
  2. 把自己的数据上传到服务器。

两者行为对应HTTP请求方法的GET和POST。

所有的协议都要解决报头和有效载荷分离的问题,以及向上交付的问题。而HTTP属于应用层不必再交付。其解决报头和有效载荷分离的方法如下:

空行为HTTP协议报头的分离,Content-Length为请求正文的分离。

按行读取,一直读读到空行说明HTTP请求协议报头读完了,空行之后为请求正文,请求报头中的Content-Length指明了请求正文的长度。因此空行之后为请求正文起点,正文长度也知道因此可以读取整个HTTP报文。

Server解析Client的Request报头如此解析,Client解析Server的Response也是如此解析。

HTTP抓包工具——Fiddler

简单原理

image-20220208100312400

抓取https协议

注意,默认下载的是只能抓取http协议的,解决方案

GET方法
请求

GET 请求方法

https://www.baidu.com/:域名+默认端口号,直接请求 / / /其他什么都没有就是获取web根目录的index.html首页。

HTTP/1.1:HTTP版本号

host——请求的主机

Connection:这次https采取的连接规则,长连接。TCP本身的打开和关闭由我们手动close控制。http无连接重点体现在客户端向服务器发requset,服务器回response,但是底层的tcp连接关闭与否取决于http协议。http协议为了更好地复用连接,可以选择短链接或者长连接。keep-alive(长连接),close(短连接)。

User-Agent:浏览器本身的版本,用户计算机的平台相关信息等。

Accept-Encoding:接收的编码方案。

Accpet-Language:接收的语言

Cookie:浏览器默认在发起请求时自动会提交在本地的一些cookie信息。客户端给服务器。

img

响应

version:HTTP/1.1

状态码:200

状态码描述:ok

Connection:keep-alive。浏览器发出的Conection是告诉服务器自身支持长连接,如果服务器本身响应的也是keep-alive,为服务器告诉浏览器自身也支持,双方之后通信用长连接。

Content-Encoding:gzip,内容编码压缩方案

Content-Type:text/html;charset=uft-8。给予的资源类型,使用的编码。

Server:BWS/1.1 使用的特定web服务器。

Set-Cookie:服务器向客户端写入cookie信息。

Transfer-Encoding:chunked,服务器本身支持的传输数据的方案,按块传输。

底下的正文由于HTTPS采用了加密所以看不懂。

image-20220208114407689

PUT方法
请求

其实可以发现很多网站的账号和密码是在请求正文中是裸露的。

因此在同一个局域网让别人登录一些网站是可以用Fiddler直接抓包拿到用户和密码的。

image-20220208132846192

响应

这里的返回码状态描述有些网站其实是没有带的。越靠近HTTP端的协议约束力越弱,

image-20220208133325399

总结

GET方法通过URL传参,POST方法通过正文传参。GET不安全,POST方法也不安全。只能说POST比GET更私密。因为POST方法传参不会把数据回显到URL中,直接把数据暴露出来看,但是也要以正文方式传参,除非对数据进行加密。

URL统一资源定位符的长度是有上限的。正文部分理论上长度是没有限制的。

  1. GET方法不安全,POST方法比GET方法更私密
  2. GET方法传参,参数长度受限;POST方法传参,理论上可以传无限的数据。

telnet直接请求HTTP

image-20220208115324140

一张网页背后就是左下方的代码。html,css,js本质上是被浏览器解释过才形成我们平时看到的网页。而且我们获得网页信息比浏览器获得的乱得多,因为线上的高频网页会被压缩处理。

image-20220208115554727

可以看到Content-Length此时是9508字符,如果要求排版,带更多的回车和空格,那么Content-Length更长传输效率就会更低,所以一般来说高频使用的网页都会这么压缩处理。

image-20220208120229711

爬虫的简单原理

套接字+connect连接对应服务器,构建request,发送给服务器,服务器自动会把response响应回来。这个动作就叫爬虫。用浏览器获取叫网页请求。用自己写的程序客户端请求就叫爬虫。c++要写一定代码,而py提供了很多线程的http库帮助进行向服务器发送request。

linux下也有现成的工具,比如wget。底层用的就是创建套接字,connect服务器,构建requset,send出去,再拿resv把response读取过来写到文件里。

image-20220208142007331

image-20220208142157032

而现在很多网站为了避免频繁被爬是通过HTTP常见header中的User-Agent(其中包含了浏览器版本信息),如果是一个用户通过浏览器访问就携带User-Agent服务器就知道这个是用户,如果是直接用爬虫就不会包含User-Agent就可能被服务器拒绝。但是这种操作可以直接加上User-Agent骗过反爬。但是机器爬的一定会有痕迹,比如有规律性,周期性,ip地址访问次数。

HTTP的方法

方法说明支持的HTTP协议版本
GET获取资源。资源:文本(html/css/js),图片,音频,视频…。通过HTTP获得资源具体获得什么资源和网站提供的服务有关。1.0,1.1
POST传输实体主体1.0,1.1
PUT传输文件(用户向服务器中发送资源)。大部分服务器把PUT禁掉,不允许随意往服务器放资源,而百度云盘对应的就是PUT方法。1.0,1.1
HEAD获得报文首部,不要正文 image-202202081355280291.0,1.1
DELETE删除文件。一般也被禁止。1.0,1.1
OPTIONS询问支持的方法。1.1
TRACE追踪路径。1.1
CONNECT要求用隧道协议连接代理。1.1
LINK建立和资源之间的联系。1.0
UNLINE断开连接关系。1.0

HTTP的状态码

类别原因短语
1XXInformation(信息型状态码)接受的请求正在处理
2XXSuccess(成功状态码)。200——ok。请求正常处理完毕
3XXRedirection(重定向状态码)。比如刚登上A网站就被跳转至B网站。需要进行附加操作以完成请求。
4XXClient Error(客户端错误状态码)。404——客户端请求的资源不在服务器上,保证客户端请求的资源为合法资源。403——权限相关码。服务器无法处理请求
5XXServer Error(服务器错误状态码)。500——服务器错误,创建线程错误,没有资源响应。服务器处理请求出错

最常见的状态码, 比如200(OK),404(Not Found),403(Forbidden),302(Redirect,重定向),504(Bad Gateway)。

HTTP常见Header

常见Header

这部分内容出现在请求报头和响应报头。

key含义
Content-Type数据类型(text/html)
Content-LengthBody的长度
Host客户端告知服务器,所请求的资源是在哪个主机的哪个端口上
User-Agent声明用户的操作系统和浏览器版本信息。能提供判定用户身份的相关信息。
referer当前页面是从哪个页面跳转过来的
location搭配3xx状态码使用,告诉客户端接下来要去哪里访问。通常会被写入response告诉客户端下次请求别访问自己去访问其他网站。
Cookie用于在客户端存储少量信息. 通常用于实现会话(session)的功能
Cookie

image-20220208150919737

但是将用户名和密码保存到本地浏览器,万一中毒了浏览器的浏览痕迹和cookie被窃取,账号密码和信息就被窃取了,对方访问同样的资源就不用登录了。

光光一个Cookie是不够安全的,补充的办法就是Session。

img

登录完成:

image-20220208154111822

此时点击相关资源都是直接访问。清除所有cookie。

image-20220208154152936

此时刷新发现用户已经退出。要重新访问。

image-20220208154243663

image-20220208154301741

Session

单单使用cookie,敏感信息保存在本地;session的敏感信息存在server。

单单使用cookie,存储的是个人账号密码;使用session时cookie存储是sid。

  1. 不一定知道sid是哪个网站的,若知道是哪个网页那也最多只能访问一样的网页,个人信息和账号密码没有泄露。
  2. 敏感信息在服务器上存放,服务器安全级别更高。

因此使用cookie+session相对更安全。

image-20220208152231806

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值