• 理解网络协议里的协议


    引言

    理解网络协议里的协议二字,协议的本质就是一种约定.

    下面我们自己定制协议,实现一个网络版本的计算器来帮助理解协议.

    网路版的计算器

    注释

    文件作用
    Makefile自动编译
    protocol.hpp描述我们自己定制的协议
    server.hppsocket编程,省去了序列化的过程,直接recv和send
    clinet.hppsocket编程,省去了反序列化的过程,直接recv和send
    server.ccmain函数
    client.ccmain函数

    socket编程就不详细解释了,有点套路化,具体的可以看这篇👉socket编程_

    Makefile

    .PHONY:all
    all: client server
    
    client:client.cc
    	g++ -o $@ $^ -std=c++11
    
    server:server.cc
    	g++ -o $@ $^ -std=c++11 -lpthread
    
    .PHONY:clean
    clean:
    	rm -f client server
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    protocol.hpp

    #include 
    
    namespace  ns_protocol
    {
      struct Request
      {
        int x;
        int y;
        char op;//"+-*/%"
    
        Request()
          :x(0)
          ,y(0)
          ,op('+')
        {
    
        }
      };
    
      struct Response
      {
        int code;//状态码 0表示成功,其余表示错误
        int result;//计算后的结果
    
        Response()
          :code(0)
          ,result(-1)
        {
    
        }
      };
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    server.hpp

    #include
    #include 
    #include
    #include 
    #include
    #include 
    #include 
    #include 
    #include 
    #include "protocol.hpp"
    
    namespace ns_server
    {
      class Server
      {
        private:
          uint16_t port;
          int listen_sock;
    
        public:
          Server(uint16_t _port)
            :port(_port)
            ,listen_sock(-1)
          {
    
          }
          void InitServer()
          {
            //创建套接字
            listen_sock=socket(AF_INET,SOCK_STREAM,0);
            if(listen_sock<0)
            {
              std::cerr<<"listen_sock err\n";
              exit(2);
            }
            //绑定端口
            sockaddr_in local;
            socklen_t len=sizeof(local);
            bzero(&local,len);
            local.sin_family=AF_INET;
            local.sin_port=htons(port);
            local.sin_addr.s_addr=INADDR_ANY;
            if(bind(listen_sock,(struct sockaddr*)&local,len)<0)
            {
              std::cerr<<"bind err\n";
              exit(3);
            }
    
            //监听,创建链接
            if(listen(listen_sock,5)<0)
            {
              std::cerr<<"listen err\n";
              exit(5);
            }
            
    
          }
    
          static void* calc(void* args)
          {
            pthread_detach(pthread_self());
            int sock=*(int*)args;
            delete (int*)args;
            while(true)
            {
              ns_protocol::Request request;
              ssize_t s=recv(sock,&request,sizeof(request),0);
              if(s<0)
              {
                std::cerr<<"recv err,client quit,me too\n";
                break;
              }
              else if(s==0)//关闭客户端就走这里
              {
                std::cout<<"recv end,client quit,me too\n";
                break;
              }
              else 
              {
                //拿到request
                ns_protocol::Response resp;
                switch(request.op)
                {
                  case '+':
                    resp.result=request.x+request.y;
                    break;
                  case '-':
                    resp.result=request.x-request.y;
                    break;
                  case '*':
                    resp.result=request.x*request.y;
                    break;
                  case '/':
                    if(request.y==0)
                    {
                      resp.code=2;
                    }
                    else 
                    {
                      resp.result=request.x/request.y;
                    }
                    break;
                  case '%':
                    if(request.y==0)
                    {
                      resp.code=3;
                    }
                    else 
                    {
                     resp.result=request.x%request.y;
                    }
                    break;
                  default:
                    resp.code=-1;//操作失误
                    break;
                }
                send(sock,&resp,sizeof(resp),0);
              }
    
            }
          close(sock);
           return nullptr;
          }
          void Loop()
          {
        
            while(true)
            {
              
              struct sockaddr_in peer;
              socklen_t len_peer=sizeof(peer);//不能是nullptr
              bzero(&peer,sizeof(peer));
      
              //获取链接
              int sock=accept(listen_sock,(struct sockaddr*)&peer,&len_peer);
              if(sock<0)
              {
                std::cerr<<"accept err\n";
                // exit(6);//客户端没连上应该continue而不是服务器退出,因为链接很频繁
                continue;
              }
              //拿到sock后创建线程去处理
              pthread_t tid;
              int* p=new int(sock);
              pthread_create(&tid,nullptr,calc,p);
    
    
            }
          }
    
          ~Server()
          {
            if(listen_sock>=0)
            {
              close(listen_sock);
            }
          }
      };
    
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    clinet.hpp

    #include
    #include 
    #include
    #include 
    #include
    #include 
    #include 
    #include 
    #include 
    #include "protocol.hpp"
    #include 
    
    namespace ns_client
    {
      class Client
      {
        private:
          int sock;
          uint16_t port;
          std::string ip;
    
        public:
          Client(std::string _ip,uint16_t _port)
            :sock(-1)
            ,port(_port)
            ,ip(_ip)
          {
            
          }
          void InitClient()
          {
            sock=socket(AF_INET,SOCK_STREAM,0);
            if(sock<0)
            {
              std::cerr<<"sock err\n";
              exit(2);
            }
            //不需要绑定,直接链接服务器
          }
          
          void Run()
          {
    
              //connect
    
              struct sockaddr_in peer;
              socklen_t len=sizeof(peer);
              bzero(&peer,len);
              peer.sin_family=AF_INET;
              peer.sin_port=htons(port);
              peer.sin_addr.s_addr=inet_addr(ip.c_str());
               
              if(connect(sock,(sockaddr*)&peer,len)<0)
              {
                std::cerr<<"connect err\n";
                exit(3);//没连接上直接退出
              }
    
              while(true)
              {
                ns_protocol::Request request;
                std::cout<<"请输入第一个数->";
                std::cin>>(request.x);
                std::cout<<"请输入第二个数->";
                std::cin>>(request.y);
                std::cout<<"请输入操作符(+-*/%)->";
                std::cin>>(request.op);
                send(sock,&request,sizeof(request),0);
    
    
                ns_protocol::Response resp;
                ssize_t s=recv(sock,&resp,sizeof(resp),0);
                if(s>0)
                {
                  std::cout<<"code: "<<resp.code<<"\n";
                  std::cout<<"result: "<<resp.result<<"\n";
                }
              }
              
          }
          ~Client()
          {
            if(sock>=0)
            {
              close(sock);
            }
          }
    
      };
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91

    server.cc

    #include "server.hpp"
    
    void Usage(char* proc)
    {
      std::cout<<"Usage:\n\t"<<proc<<" local_port\n"; 
    }
    
    int main(int argc,char* argv[])
    {
      if(argc!=2)
      {
        Usage(argv[0]);
        return 2;
      }
      ns_server::Server svr(atoi(argv[1]));
      svr.InitServer();
      svr.Loop();
    
      return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    client.cc

    #include "client.hpp"
    
    void Usage(char* proc)
    {
      std::cout<<"Usage\n\t"<<proc<<" peer_ip peer_port\n";
    }
    int main(int argc,char* argv[])
    {
      if(argc!=3)
      {
        Usage(argv[0]);
        return 1;
      }
    
      ns_client::Client cli(argv[1],atoi(argv[2]));
      cli.InitClient();
      cli.Run();
    
      return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行效果

    在这里插入图片描述

    XShell:ctrl+shift+T隐藏窗口标签

    思考

    我们先看一下protocol.hpp的内容

    #include 
    
    namespace  ns_protocol
    {
      struct Request
      {
        int x;
        int y;
        char op;//"+-*/%"
    
        Request()
          :x(0)
          ,y(0)
          ,op('+')
        {}
      };
    
      struct Response
      {
        int code;//状态码 0表示成功,其余表示错误
        int result;//计算后的结果
    
        Response()
          :code(0)
          ,result(-1)
        {
    
        }
      };
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    struct Request里的x是什么?y是什么?op是什么?

    假如我们输入的op是’/',为什么一定是x/y?不能是y/x?

    struct Response里的code又是啥?result又是啥?各自代表什么含义?

    我们能正常使用这个网络版的计算器,因为这是我们自己定制的协议,我们规定一定是x/y,规定状态码code为0就表示成功.

    网络计算器里的协议是我针对具体的场景设计的协议,可扩展性和健壮性不足.

    在互联网里面有一些高频场景,所以有很多大佬已经给我们做好了很多应用层的协议,我们就不用自己设计协议了,只需要学习了解他们定制的协议就好了.比如http协议,所以之后我们的任务就是学习了解一些常用协议,然后自己可以编写小部分协议.

    当然不排除一些特殊场景需要我们自己写协议,比如游戏领域.

  • 相关阅读:
    【网络协议】聊聊ICMP与ping是如何测试网络联通性
    Django安装与创建项目
    倾斜摄影三维模型根节点合并效率提升的技术方法分析
    nodejs+vue+elementui网上书城 图书销售商城网站express
    【学习笔记之我要C】预处理
    C++基础——基础语法
    安装GPT 学术优化 (GPT Academic)@FreeBSD
    【Lilishop商城】No2-1.确定项目结构和数据结构(用户、商品、订单、促销等模块)
    算法训练Day49 | Leetcode121. 买卖股票的最佳时机(只能买卖一次);LeetCode122. 买卖股票的最佳时机II(可以买卖多次)
    Hive3.1.2分区与排序(内置函数)
  • 原文地址:https://blog.csdn.net/m0_53005929/article/details/127267547