喜欢乙醇的四氯化碳

喜欢乙醇的四氯化碳

引言

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

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

网路版的计算器

注释

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

protocol.hpp

#include <iostream>

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)
    {

    }
  };

}

server.hpp

#include<iostream>
#include <unistd.h>
#include<sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#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);
        }
      }
  };


}

clinet.hpp

#include<iostream>
#include <unistd.h>
#include<sys/types.h>
#include <sys/socket.h>
#include<netinet/in.h>
#include <strings.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <stdlib.h>
#include "protocol.hpp"
#include <string>

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);
        }
      }

  };

}

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;
}

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;
}

运行效果

理解网络协议里的协议-LMLPHP

思考

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

#include <iostream>

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)
    {

    }
  };

}

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

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

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

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

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

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

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

10-12 07:43