HTTP协议及TCP通讯流程

1. TCP协议通讯流程

在这里插入图片描述
服务器初始化:

  • 调用socket, 创建文件描述符;
  • 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  • 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
  • 调用accecpt, 并阻塞, 等待客户端连接过来;

1.1 简单初识三次握手和四次挥手

建立连接的过程:

  • 调用socket, 创建文件描述符;
  • 调用connect, 向服务器发起连接请求;
  • connect会发出SYN段并阻塞等待服务器应答; (第一次)
  • 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
  • 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次)
    这个建立连接的过程, 通常称为三次握手;

举个例子来形象的阐述三次握手的概念:
男:我希望你做我女朋友
女:什么时候
男:就是现在

数据传输的过程

  • 建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
  • 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
  • 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
  • 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;
  • 客户端收到后从read()返回, 发送下一条请求,如此循环下去

断开连接的过程:

  • 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
  • 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
  • read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
  • 客户端收到FIN, 再返回一个ACK给服务器; (第四次)

这个断开连接的过程, 通常称为 四次挥手

TCP 和 UDP 对比

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流(类比于自来水,想要多少就要多少) vs 数据报(类比于取快递,要么取一个,不可能取半个快递的概念)

2. 网络版计算器

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

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

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

Protocol.hpp 定义通信的结构体

#ifndef __PROTOCOL_HPP__
#define __PROTOCOL_HPP__ 

#include<iostream>

typedef struct Request{
  int x;//左操作数
  int y;//右操作数
  char op;//操作符
}Request;

typedef struct Response{
  int code;//表示结果值是否可被信任,为0的时候,说明结果可以信任
  int result;//结果
}Response;

#endif 

无论我们采用方案一, 还是方案二, 还是其他的方案, 只要保证, 一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是应用层协议

Server.hpp

#ifndef __SERVER_HPP__
#define __SERVER_HPP__

#include <iostream>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include "Protocol.hpp"

using namespace std;

class server{
    private:
        int port;
        int lsock;
    public:
        server(int _p):port(_p),lsock(-1)
        {}
        void initServer()
        {
            lsock = socket(AF_INET, SOCK_STREAM, 0);
            if( lsock < 0 )
            {
                cerr << "socket error " << endl;
                exit(2);
            }
            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){
                cerr << "bind error!" << endl;
                exit(3);
            }

            if(listen(lsock, 5) < 0){
                cerr << "listen error!\n" << endl;
            }
        }

        void cal(int sock)
        {
            //短连接来完成对应的计算!
            Request  rq;
            Response rsp={4, 0};
            ssize_t s = recv(sock, &rq, sizeof(rq), 0); //BUG
            if(s > 0){
                rsp.code = 0;
                switch(rq.op){
                    case '+':
                        rsp.result = rq.x + rq.y;
                        break;
                    case '-':
                        rsp.result = rq.x - rq.y;
                        break;
                    case '*':
                        rsp.result = rq.x * rq.y;
                        break;
                    case '/':
                        if(rq.y != 0){
                            rsp.result = rq.x / rq.y;
                        }
                        else{
                            rsp.code = 1;
                        }
                        break;
                    case '%':
                        if(rq.y != 0){
                            rsp.result = rq.x + rq.y;
                        }
                        else{
                            rsp.code = 2;
                        }
                        break;
                    default:
                        rsp.code = 3;
                        break;
                }
            }
            send(sock, &rsp, sizeof(rsp), 0);
            close(sock);
        }
        void start()
        {
            struct sockaddr_in peer;
            for(;;){
                socklen_t len = sizeof(peer);
                int sock = accept(lsock, (struct sockaddr*)&peer,&len);
                if( sock < 0 ){
                    cerr << "accept error!" << endl;
                    continue;
                }
                if(fork() == 0){
                    if(fork() > 0){
                        exit(0);
                    }
                    //孙子
                    close(lsock);
                    //todo
                    cal(sock);
                    exit(0);
                }
                close(sock);
                waitpid(-1, nullptr, 0);
            }
        }
        ~server()
        {
            close(lsock);
        }
};

#endif

Client.hpp

#ifndef __CLIENT_HPP__
#define __CLIENT_HPP__ 

#include<iostream>
#include<unistd.h>
#include<cstdlib>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/wait.h>
#include"Protocol.hpp"
class client{
  private:
    std::string ip;
    int port;
    int sock;
  public:
    client(std::string _ip,int _port)
      :ip(_ip),port(_port),sock(-1)
    {}

    void initclient()
    {
      sock = socket(AF_INET,SOCK_STREAM,0);
      if(sock < 0){
        std::cerr<<"socket error!"<<std::endl;
        exit(2);
      }

    }

    void start()
    {
      struct sockaddr_in server;
      server.sin_family = AF_INET;
      server.sin_port = htons(port);
      server.sin_addr.s_addr = inet_addr(ip.c_str());

      if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){
          std::cerr << "connect error"<<std::endl;
          exit(3);  
        }

      //建立的是短链接,所以只需要C发过去,S返回回来,那么一次通信就算结束了
      Request rq;
      Response rsp;
      std::cout << "date1# ";
      std::cin >> rq.x;
      std::cout << "date2# ";
      std::cin >> rq.y;
      std::cout << "op# ";
      std::cin >> rq.op;

      send(sock,&rq,sizeof(rq),0);
      ssize_t s = recv(sock,&rsp,sizeof(rsp),0);
      if(s > 0){
        std:: cout <<"code : "<<  rsp.code <<std::endl;
        std::cout <<"result : "<<rsp.result << std::endl;
      }
    }


    ~client()
    {
      close(sock);
    }
};

#endif 

在这里插入图片描述

2.1 tcpdump

这是一个抓包命令tcpdump -i any -nn tcp port 8080

参数解释:
-i:表示想要抓那个网络
-nn:第一个n表示能把主机名用数字表示的都用数字表示,第二个n表示把更多的信息能用数字表示的用数字表示。
tcp:表示想要抓的协议

三次挥手
在这里插入图片描述
四次挥手
在这里插入图片描述

3. 初识HTTP协议

HTTP(超文本传输协议),是一个应用层的协议。

基本特征

  1. 无连接:TCP建立连接和http无关,http直接向对方发送http request即可
  2. 无状态:http本身是无状态的,并不会记录任何用户信息,但是我们有的时候会发现,我刚才登录过的网站,第二次不再需要输入账号+密码就可以直接的进行访问,其实真正记录你的基本信息的技术是cookie+session
  3. 简单快速

3.1 HTTP协议格式

使用Fiddler工具进行网络抓包

Request

在这里插入图片描述

  • 首行(请求行): [请求方法] + [url] + [版本]
  • Header(请求报头): 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body(请求正文): 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

Response

在这里插入图片描述

  • 首行(响应行): [版本号] + [状态码] + [状态码解释]
  • Header(响应报头): 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
  • Body(响应正文): 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.

在这里插入图片描述

3.2 HTTP的请求方法

HTTP/1.1协议中共定义了八种方法(有时也叫“动作”),来表明Request-URL指定的资源不同的操作方式

  • HTTP1.0定义了三种请求方法: GET, POST 和 HEAD方法。
  • HTTP1.1新增了五种请求方法:OPTIONS, PUT, DELETE, TRACE 和 CONNECT 方法

在这里插入图片描述
最常使用的是GET和POST请求方法。

  • GET:url传参(参数的长度是有限的),你的账号和密码可能会在url中直接的携带,相当不安全。
  • POST:正文传参(参数可以是无限长的),除非对正文进行加密,不然这种方式也是不安全的,但相较于GET方法又好一些。

3.3 HTTP的状态码

HTTP状态码由三个十进制数字组成,第一个十进制数字定义了状态码的类型,后两个数字没有分类的作用。HTTP状态码共分为5种类型:
在这里插入图片描述

下面是常见的HTTP状态码:

  • 200 - 请求成功
  • 301 - 资源(网页等)被永久转移到其它URL
  • 404 - 请求的资源(网页等)不存在
  • 500 - 内部服务器错误

3.4 HTTP常见Header

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

3.5 cookie和session

http是无状态的(无法保存记录),但是http是给用户使用的,用户的体验感会非常差,为了解决使用了cookie技术。如下图所示:如果没有cookie技术,那么在访问完资源一以后,在访问资源二的时候就又需要输入name和passwd。
在这里插入图片描述
在这里插入图片描述

但是我们的电脑或者手机中了病毒,那么黑客也是可以拿到我们的cookie文件(在这个文件中保存着name和passwd),黑客是不需要知道cookie里面是具体的内容,他只需要拿着这个cookie文件和我访问同样的资源,比如说:QQ,那么他就可以进行一系列的操作,就如同我在使用一样。非常的不安全。所以为了能够相对安全一些引入了session技术
在这里插入图片描述
对比cookie和session:

  • cookie敏感(name && passwd)信息在本地
  • cookie + session敏感信息在server端,但也不是绝对安全,但是比cookie要好很多,对于本地机来说,你的防火墙只能是360或者XX,但是对于session来说,是一大堆的集群以及专业的维护团队。

cookie 和session 的区别

安全性:

  1. cookie数据存放在客户的浏览器上,session数据放在服务器上。

  2. cookie不是很安全,别人可以分析存放在本地的COOKIE并进行COOKIE欺骗,所以session相对安全些。

占用空间:

  1. session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。考虑到减轻服务器性能方面,应当使用COOKIE。

  2. 单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。

数据类型

  1. cookie只能存储 int float string。Session还可以存储 数组、对象

所以个人建议

  1. 将登陆信息等重要信息存放为SESSION
  2. 其他信息如果需要保留,可以放在COOKIE中

参考:

cookie和session详解链接: link.

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值