【Hello Network】协议

这篇具有很好参考价值的文章主要介绍了【Hello Network】协议。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

作者:@小萌新
专栏:@网络
作者简介:大二学生 希望能和大家一起进步
本篇博客简介:简单介绍下协议并且设计一个简单的网络服务器

协议的概念

协议 网络协议的简称 网络协议是通信计算机双方必须共同遵从的一组约定 比如怎么建立连接、怎么互相识别等

就像我们在之前的博客 网络基础 里面写的一样 协议的本质其实就是一种约定

结构化数据传输

通信双方在进行网络通信的时候:

  • 如果要传输的数据是一个字符串 那么通信双方直接发送即可
  • 如果要传输的数据是一些结构体 此时就不能将这些数码一个个发送到网络中

比如说我们现在要实现一个网络版本的计算器 那么客户端每次发送的请求就需要包括左操作数 右操作数 和对应的操作 那么此时客户端要发送的就不是一个简单的字符串 而是一个结构体

如果客户端将这些结构化的数据单独一个个发送到网络中 那么服务端也只能一个个的接受 但是这样子传输容易导致数据错乱

所以说最好的方案是客户端将这些结构化的数据统一打包发送到网络中 此时服务端接受的就是一个完整的请求了 客户端常见的打包方式有下面两种

将结构化的数据组合成一个字符串

约定方案一:

  • 客户端发送一个形如“1+1”的字符串
  • 这个字符串中有两个操作数 都是整型
  • 两个数字之间会有一个字符是运算符
  • 数字和运算符之间没有空格

客户端可以按某种方式将这些结构化的数据组合成一个字符串 然后将这个字符串发送到网络当中 此时服务端每次从网络当中获取到的就是这样一个字符串 然后服务端再以相同的方式对这个字符串进行解析 此时服务端就能够从这个字符串当中提取出这些结构化的数据

定制结构体+序列化和反序列化

约定方案二:

  • 定制结构体来表示我们想要传递的信息
  • 发送数据时将这个结构体按照一个规则转换成网络标准数据格式 接收数据时再按照相同的规则把接收到的数据转化为结构体
  • 这个过程我们就叫做序列化和反序列化

客户端可以定制一个结构体 将需要交互的信息定义到这个结构体当中

客户端发送数据时先对数据进行序列化 服务端接收到数据后再对其进行反序列化 此时服务端就能得到客户端发送过来的结构体 进而从该结构体当中提取出对应的信息

序列化和反序列化

序列化和反序列化

  • 序列化就是将对象的状态信息转化为字节序的过程
  • 反序列化就是将字节序恢复为对象的过程

OSI七层模型中表示层的作用就是 实现设备固有数据格式和网络标准数据格式的转换

其中设备固有的数据格式指的是数据在应用层上的格式 而网络标准数据格式则指的是序列化之后可以进行网络传输的数据格式

序列化和反序列化的目的

  • 在网络传输时 序列化目的是为了方便网络数据的发送和接收 无论是何种类型的数据 经过序列化后都变成了二进制序列 此时底层在进行网络数据传输时看到的统一都是二进制序列
  • 序列化后的二进制序列只有在网络传输时能够被底层识别 上层应用是无法识别序列化后的二进制序列的 因此需要将从网络中获取到的数据进行反序列化 将二进制序列的数据转换成应用层能够识别的数据格式

我们可以认为网络通信和业务处理处于不同的层级 在进行网络通信时底层看到的都是二进制序列的数据 而在进行业务处理时看得到则是可被上层识别的数据 如果数据需要在业务处理和网络通信之间进行转换 则需要对数据进行对应的序列化或反序列化操作

【Hello Network】协议

网络版计算机

服务端代码

首先我们需要对服务器进行初始化:

  • 调用socket函数,创建套接字
  • 调用bind函数,为服务端绑定一个端口号
  • 调用listen函数,将套接字设置为监听状态

初始化完服务器后就可以启动服务器了 服务器启动后要做的就是不断调用accept函数 从监听套接字当中获取新连接 每当获取到一个新连接后就创建一个新线程 让这个新线程为该客户端提供计算服务

  #include <iostream>    
  #include <string>    
  #include <cstring>    
  #include <sys/socket.h>    
  #include <sys/types.h>    
  #include <netinet/in.h>    
  #include <arpa/inet.h>    
  #include <unistd.h>    
  using namespace std;    
      
  int main(int argc , char* argv[])    
  {    
    if (argc != 2)    
    {    
      cout << "usage error" << endl;    
      exit(1);    
    }    
      
    // port socket                     
    int port = atoi(argv[1]);    
    int listen_sock = socket(AF_INET , SOCK_STREAM , 0);    
    if (listen_sock < 0)    
    {    
      cout << "socket error" << endl ;    
      exit(2);                                                                                                                                                                                                                                                                                                                                                      
    }    
    struct sockaddr_in local;    
    memset(&local , 0 , sizeof(local));    
    local.sin_family = AF_INET;    
    local.sin_port = htons(port);    
    local.sin_addr.s_addr = htonl(INADDR_ANY);    
      
      
    if (bind(listen_sock , (struct sockaddr*)&local , sizeof(local)) < 0)    
    {    
      cout << "bind error" << endl;    
      exit(3);    
    }    
      
    if (listen(listen_sock , 5) < 0)    
    {    
      cout << "listen error" << endl;    
      exit(4);    
    }    
      
      
    struct sockaddr_in peer;    
    memset(&peer , '\0' , sizeof(peer));    
    socklen_t len;    
    for(;;)    
    {    
      int sock = accept(listen_sock , (struct sockaddr*)&peer , &len);    
      if (sock < 0)    
      {    
        cout << "accept error" << endl;    
        continue; // do not stop     
      }    
      
      pthread_t tid;    
      int* p = new int(sock);    
E>    pthread_create(&tid , nullptr , Rontinue , (void*) p)    
    }    
      
    return 0;    
  }  

说明一下:

  • 当前服务器采用的是多线程的方案 你也可以选择采用多进程的方案或是将线程池接入到多线程当中
  • 服务端创建新线程时 需要将调用accept获取到套接字作为参数传递给该线程 为了避免该套接字被下一次获取到的套接字覆盖 最好在堆区开辟空间存储该文件描述符的值

协议定制

要实现一个网络版的计算器 就必须保证通信双方能够遵守某种协议约定 因此我们需要设计一套简单的约定 数据可以分为请求数据和响应数据 因此我们分别需要对请求数据和响应数据进行约定

在实现时可以采用C++当中的类来实现 也可以直接采用结构体来实现 这里就使用结构体来实现 此时就需要一个请求结构体和一个响应结构体

  • 请求结构体中需要包括两个操作数 以及对应需要进行的操作
  • 响应结构体中需要包括一个计算结果 除此之外 响应结构体中还需要包括一个状态字段 表示本次计算的状态 因为客户端发来的计算请求可能是无意义的 比如说除0操作等

规定状态字段对应的含义:

  • 状态字段为0 表示计算成功
  • 状态字段为1 表示非法计算
  typedef struct request    
  {    
    int left;    
    int right;    
    char op;    
  }request_t;    
      
  typedef struct response    
  {    
    int code;    
    int result;                                                                                                                          
  }response_t;   

要注意的是作为一种约定 它必须要被通信的双方所知晓 也就是说 要么我们将这个协议写在一个头文件中并同时包含在客户端和服务端中 要么在客户端和服务端都写上这么一段相同的代码

客户端代码

客户端首先也需要进行初始化:

  • 调用socket函数 创建套接字

客户端初始化完毕后需要调用connect函数连接服务端 当连接服务端成功后 客户端就可以向服务端发起计算请求了 这里可以让用户输入两个操作数和一个操作符构建一个计算请求 然后将该请求发送给服务端 而当服务端处理完该计算请求后 会对客户端进行响应 因此客户端发送完请求后还需要读取服务端发来的响应数据

int main(int argc , char* argv[])    
{    
  if (argc != 3)    
  {    
    cerr << "usage error" << endl;    
    exit(1);    
  }    
    
  string ip = argv[1];    
  int port = atoi(argv[2]);    
    
    
  int sockfd = socket(AF_INET , SOCK_STREAM , 0);    
  if (sockfd < 0)    
  {    
    cerr << "socket error" << endl;    
    exit(2);    
  }    
    
    
  struct sockaddr_in peer;    
  memset(&peer , '\0' , sizeof(peer));    
    
  peer.sin_family = AF_INET;    
  peer.sin_port = htons(port);    
  peer.sin_addr.s_addr = inet_addr(ip.c_str());    
    
  // connect     
  if (connect(sockfd , (struct sockaddr*)&peer , sizeof(peer)) < 0)    
  {    
    cerr << "connect error" << endl;    
    exit(3);    
  }    
    
  while(true)    
  {    
    request_t rq;    
    cout << "请输入左操作数#" ;    
    cin >> rq.left;    
    cout << "请输出右操作数#" ;    
    cin >> rq.right;    
    cout << "请输出操作符#" ;    
    cin >> rq.op;    
    write(sockfd , &rq , sizeof(rq));    
    
    
    response_t rp;    
    read(sockfd , &rp , sizeof(rp)) ;    
    cout << "code: " << rp.code << endl;    
    cout << "result:" << rp.result << endl;    
  }    
  return 0;    
}    

服务线程执行例程

当服务端调用accept函数获取到新连接并创建新线程后 该线程就需要为该客户端提供计算服务 此时该线程需要先读取客户端发来的计算请求 然后进行对应的计算操作

    
void* Routine(void* arg)    
{                      
  pthread_detach(pthread_self()); //分离线程    
  int sock = *(int*)arg;    
  delete (int*)arg;    
                                                      
  while (true){       
    request_t rq;                  
    ssize_t size = recv(sock, &rq, sizeof(rq), 0);    
    if (size > 0){    
      response_t rp = { 0, 0 };            
      switch (rq.op){    
      case '+':    
        rp.result = rq.left + rq.right;    
        break;    
      case '-':    
        rp.result = rq.left - rq.right;    
        break;    
      case '*':    
        rp.result = rq.left * rq.right;    
        break;                      
      case '/':    
        if (rq.right == 0){    
          rp.code = 1; //除0错误             
        }    
        else{     
          rp.result = rq.left / rq.right;    
        }                      
        break;                      
      case '%':    
        if (rq.right == 0){    
          rp.code = 2; //模0错误             
        }    
        else{     
          rp.result = rq.left % rq.right;    
        }                          
        break;    
      default:    
        rp.code = 3; //非法运算          
        break;    
      }                     
      send(sock, &rp, sizeof(rp), 0);    
    }           
    else if (size == 0){    
      cout << "service done" << endl;    
      break;                           
    }           
    else{    
      cerr << "read error" << endl;                                                                                         
      break;      
    }                
  }    
  close(sock);    
  return nullptr;    
}  

存在的问题

  • 如果客户端和服务器分别在不同的平台下运行 在这两个平台下计算出请求结构体和响应结构体的大小可能会不同 此时就可能会出现一些问题
  • 在发送和接收数据时没有进行对应的序列化和反序列化操作 正常情况下是需要进行的

虽然当前代码存在很多潜在的问题 但这个代码能够很直观的告诉我们什么是约定 这里将其当作一份示意性代码

代码测试

我们开始运行代码
【Hello Network】协议

如果是正常的计算 我们的计算器就能正常运行

如果涉及到除0 模0操作 该服务器就会返回我们一个错误码文章来源地址https://www.toymoban.com/news/detail-426665.html

到了这里,关于【Hello Network】协议的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Hello Network】DNS协议 NAT技术 代理服务器

    本篇博客简介:介绍DNS协议 NAT技术和代理服务器 DNS是一整套从域名映射到IP的系统 为什么要有域名 其实作为我们程序员来说 使用域名还是IP地址是无所谓的 但是站在商业公司和用户的角度就不这么认为了 商业公司希望用户能够快速的记住自己公司的网址 而用户也希望自己

    2024年02月11日
    浏览(40)
  • 【Hello Network】网络编程套接字(三)

    作者:@小萌新 专栏:@网络 作者简介:大二学生 希望能和大家一起进步 本篇博客简介:简单介绍下各种类型的Tcp协议 我们在前面的网络编程套接字(二)中写出了一个单执行流的服务器 我们再来回顾下它的运行 我们首先启动服务器 之后启动客户端1 最后启动客户端2 我们

    2023年04月22日
    浏览(44)
  • 【Hello Network】网络编程套接字(一)

    作者:@小萌新 专栏:@网络 作者简介:大二学生 希望能和大家一起进步 本篇博客简介:简单介绍网络的基础概念 在互联网中每一台主机都有唯一标识的公网IP地址 在互联网中数据传输的时候报文中需要加上源ip和目的ip 源ip和目的ip本质上解决的是从哪里来到哪里去的问题

    2023年04月17日
    浏览(98)
  • 【Hello Network】网络编程套接字(二)

    作者:@小萌新 专栏:@网络 作者简介:大二学生 希望能和大家一起进步 本篇博客简介:简单介绍网络的基础概念 我们使用一个类来封装服务端 当我们定义一个服务器对象之后马上就进行初始化 初始化TCP服务器第一时间就要创建套接字 我们在使用TCP服务的时候用socket函数创

    2023年04月23日
    浏览(42)
  • 物联网协议Coap之Core和NetWork简介

    目录 前言 一、Coap的Core包 1、Coap对象 2、Message对象 3、Request对象 4、Response对象 二、Coap的NetWork调试 1、UDP运行模式  2、Network消息接收 3、Sender线程发送数据  三、总结         在之前的博文中,对Californium中Coap的实现进行了简要的介绍,分别从Server和Client两端进行了基础

    2024年01月21日
    浏览(45)
  • 【网络协议】NTP(Network Time Protocol)协议详解

    NTP(Network Time Protocol)是一种用于在分布式网络中的不同设备之间保持精确时间同步的互联网协议。 它允许一台机器与其他机器或权威的时间源建立联系,并根据这些联系来调整自己的时间,以确保整个网络中的所有设备共享一致的时间基准。 NTP通过精确测量时间偏差、补

    2024年02月04日
    浏览(41)
  • NoC (Network on chip) 基础 (1) : 片上网络的简介

    本系列的文章是我在学习NoC经典书籍:Principles and Practices of Interconnection Networks 以及相关的论文过程中所作的总结和归纳。在敦促自己建立更全面知识体系的同时,希望也能够帮助到对这一领域想作快速了解的同学。 随着数字电路规模的不断增大,传统的总线型数据交换方式

    2024年01月16日
    浏览(33)
  • TCP/IP协议专栏——分片报文详解——网络入门和工程维护必看

    一个链路层数据报能承载的最大数据量称为最大传送单元(MTU)。 因为IP数据报(IP头+DATA)被封装在链路层数据报中,故链路层的MTU严格地限制着IP数据报的长度, 而且在IP数据报的源与目的地路径上的各段链路可能使用不同的链路层协议,有不同的MTU. 例如,以太网的MTU为15

    2024年01月19日
    浏览(64)
  • TCP/IP协议专栏——以太帧结构 详解——网络入门和工程维护必看

    以太网帧发送数据时都是从8个字节的前导码开始的。前导码是1和0的交互。 在以太网中,数据通信的基本单位是 以太网帧 ( frame ),由 头部 ( header )、数据 ( data )以及 校验和 ( checksum )三部分构成: 头部 以太网帧头部包含 3 个字段,依次是: 1、目的地址:长度是 6 字节,用

    2023年04月18日
    浏览(45)
  • 【欢迎您的到来】这里是开源库get_local_info作者的付费专栏

    您好,         我是带剑书生,开源库get_local_info的作者,欢迎您的到来,这里是我的付费专栏,在上一个付费专栏里,用简洁的语言,通俗的话语,帮助您更好的学习了Rust,现在将用本专栏来为您解决学习和工作中遇到的《疑难杂症》,让您带飞项目,反击暴击~    

    2024年01月19日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包