【Linux】认识协议

news2024/11/26 12:00:09

🎇Linux:


  • 博客主页:一起去看日落吗
  • 分享博主的在Linux中学习到的知识和遇到的问题
  • 博主的能力有限,出现错误希望大家不吝赐教
  • 分享给大家一句我很喜欢的话: 看似不起波澜的日复一日,一定会在某一天让你看见坚持的意义,祝我们都能在鸡零狗碎里找到闪闪的快乐🌿🌞🐾。

在这里插入图片描述

目录

  • 🍁1. 协议的概念
  • 🍁2. 结构化数据的传输
  • 🍁3. 序列化和反序列化
  • 🍁4. 网络版计算器
    • 🍂4.1 服务端
    • 🍂4.2 协议定制
    • 🍂4.3 客户端
    • 🍂4.4 服务线程执行历程
    • 🍂4.6 代码测试

🍁1. 协议的概念

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

为了使数据在网络上能够从源到达目的,网络通信的参与方必须遵循相同的规则,我们将这套规则称为协议(protocol),而协议最终都需要通过计算机语言的方式表示出来。只有通信计算机双方都遵守相同的协议,计算机之间才能互相通信交流。


🍁2. 结构化数据的传输

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

  • 如果需要传输的数据是一个字符串,那么直接将这一个字符串发送到网络当中,此时对端也能从网络当中获取到这个字符串。
  • 但如果需要传输的是一些结构化的数据,此时就不能将这些数据一个个发送到网络当中。

比如现在要实现一个网络版的计算器,那么客户端每次给服务端发送的请求数据当中,就需要包括左操作数、右操作数以及对应需要进行的操作,此时客户端要发送的就不是一个简单的字符串,而是一组结构化的数据。

如果客户端将这些结构化的数据单独一个个的发送到网络当中,那么服务端从网络当中获取这些数据时也只能一个个获取,此时服务端还需要纠结如何将接收到的数据进行组合。因此客户端最好把这些结构化的数据打包后统一发送到网络当中,此时服务端每次从网络当中获取到的就是一个完整的请求数据,客户端常见的“打包”方式有以下两种。

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

约定方案一:

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

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

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

约定方案二:

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

客户端可以定制一个结构体,将需要交互的信息定义到这个结构体当中。客户端发送数据时先对数据进行序列化,服务端接收到数据后再对其进行反序列化,此时服务端就能得到客户端发送过来的结构体,进而从该结构体当中提取出对应的信息。


🍁3. 序列化和反序列化

序列化和反序列化:

  • 序列化是将对象的状态信息转换为可以存储或传输的形式(字节序列)的过程。
  • 反序列化是把字节序列恢复为对象的过程。

OSI七层模型中表示层的作用就是,实现设备固有数据格式和网络标准数据格式的转换。其中设备固有的数据格式指的是数据在应用层上的格式,而网络标准数据格式则指的是序列化之后可以进行网络传输的数据格式。

序列化和反序列化的目的

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

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

在这里插入图片描述


🍁4. 网络版计算器

🍂4.1 服务端

#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>
#include "protocol.hpp"
using namespace std;

int main(int argc, char* argv[])
{
	if (argc != 2){
		cerr << "Usage: " << argv[0] << " port" << endl;
		exit(1);
	}
	int port = atoi(argv[1]);

	//创建套接字
	int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
	if (listen_sock < 0){
		cerr << "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){
		cerr << "bind error!" << endl;
		exit(3);
	}

	//监听
	if (listen(listen_sock, 5) < 0){
		cerr << "listen error!" << endl;
		exit(4);
	}

	//启动服务器
	struct sockaddr peer;
	memset(&peer, 0, sizeof(peer));
	for (;;){
		socklen_t len = sizeof(peer);
		int sock = accept(listen_sock, (struct sockaddr*)&peer, &len);
		if (sock < 0){
			cerr << "accept error!" << endl;
			continue;
		}
		pthread_t tid = 0;
		int* p = new int(sock);
		pthread_create(&tid, nullptr, Routine, p);
	}
	return 0;
}

说明:

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

🍂4.2 协议定制

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

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

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

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

  • 状态字段为0,表示计算成功。
  • 状态字段为1,表示出现除0错误。
  • 状态字段为2,表示出现模0错误。
  • 状态字段为3,表示非法计算。

此时我们就完成了协议的设计,但需要注意,只有当响应结构体当中的状态字段为0时,计算结果才是有意义的,否则计算结果无意义。

#pragma once

//请求
typedef struct request{
	int x; //左操作数
	int y; //右操作数
	char op; //操作符
}request_t;

//响应
typedef struct response{
	int code; //计算状态
	int result; //计算结果
}response_t;

注意: 协议定制好后必须要被客户端和服务端同时看到,这样它们才能遵守这个约定,如果我们将这份代码写到一个头文件中,那么客户端和服务端都应该包含这个头文件。


🍂4.3 客户端

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

  • 调用socket函数,创建套接字。

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

客户端在向服务端发送或接收数据时,可以使用write或read函数进行发送或接收,也可以使用send或recv函数对应进行发送或接收。

send函数

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数说明:

  • sockfd:特定的文件描述符,表示将数据写入该文件描述符对应的套接字。
  • buf:需要发送的数据。
  • len:需要发送数据的字节个数。
  • flags:发送的方式,一般设置为0,表示阻塞式发送。

返回值说明:

  • 写入成功返回实际写入的字节数,写入失败返回-1,同时错误码会被设置。

recv函数

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数说明:

  • sockfd:特定的文件描述符,表示从该文件描述符中读取数据。
  • buf:数据的存储位置,表示将读取到的数据存储到该位置。
  • len:数据的个数,表示从该文件描述符中读取数据的字节数。
  • flags:读取的方式,一般设置为0,表示阻塞式读取。

返回值说明:

  • 如果返回值大于0,则表示本次实际读取到的字节个数。
  • 如果返回值等于0,则表示对端已经把连接关闭了。
  • 如果返回值小于0,则表示读取时遇到了错误。
#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>
#include "protocol.hpp"

using namespace std;

int main(int argc, char* argv[])
{
	if (argc != 3){
		cerr << "Usage: " << argv[0] << " server_ip server_port" << endl;
		exit(1);
	}
	string server_ip = argv[1];
	int server_port = atoi(argv[2]);

	//创建套接字
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	if (sock < 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(server_port);
	peer.sin_addr.s_addr = inet_addr(server_ip.c_str());
	if (connect(sock, (struct sockaddr*)&peer, sizeof(peer)) < 0){
		cerr << "connect failed!" << endl;
		exit(3);
	}

	//发起请求
	while (true){
		//构建请求
		request_t rq;
		cout << "请输入左操作数# ";
		cin >> rq.x;
		cout << "请输入右操作数# ";
		cin >> rq.y;
		cout << "请输入需要进行的操作[+-*/%]# ";
		cin >> rq.op;
		send(sock, &rq, sizeof(rq), 0);
		
		//接收请求响应
		response_t rp;
		recv(sock, &rp, sizeof(rp), 0);
		cout << "status: " << rp.code << endl;
		cout << rq.x << rq.op << rq.y << "=" << rp.result << endl;
	}
	return 0;
}

🍂4.4 服务线程执行历程

当服务端调用accept函数获取到新连接并创建新线程后,该线程就需要为该客户端提供计算服务,此时该线程需要先读取客户端发来的计算请求,然后进行对应的计算操作,如果客户端发来的计算请求存在除0、模0、非法运算等问题,就将响应结构体当中的状态字段对应设置为1、2、3即可。

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.x + rq.y;
				break;
			case '-':
				rp.result = rq.x - rq.y;
				break;
			case '*':
				rp.result = rq.x * rq.y;
				break;
			case '/':
				if (rq.y == 0){
					rp.code = 1; //除0错误
				}
				else{
					rp.result = rq.x / rq.y;
				}
				break;
			case '%':
				if (rq.y == 0){
					rp.code = 2; //模0错误
				}
				else{
					rp.result = rq.x % rq.y;
				}
				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;
}

🍂4.6 代码测试

运行服务端后再让客户端连接服务端,此时服务端就会对客户端发来的计算请求进行处理,并会将计算后的结果响应给客户端。

而如果客户端要进行除0、模0、非法运算,在服务端识别后就会按照约定对应将响应数据的状态码设置为1、2、3,此时响应状态码为非零,因此在客户端打印出来的计算结果就是没有意义的。

在这里插入图片描述


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/429265.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Java 编程语言】——JDK 安装

JDK 安装 文章目录JDK 安装一、JDK的选择与下载1.JDK的选择2.JDK的下载二、Java环境变量的配置一、JDK的选择与下载 1.JDK的选择 目前的JDK的版本更新很快&#xff0c;已经到了JDK20了。但是对于普通的开发或者学习人员来说&#xff0c;选择较为稳定的JDK是更为合适的选择。当…

干货丨AI常见问题及处理方法

AI软件在运行时经常会容易报错或者操作不成功&#xff0c;问题及处理方法分享给大家 01 当AI中色板里面没有颜色可选 原因&#xff1a;将图片素材直接以新窗口打开&#xff0c;所以显示的是位图文件。 解决办法&#xff1a;重新新建文件&#xff0c;然后将图片拖入新建文件中…

让Ai来告诉你Linux应该怎么学

今天在slack上添加了Claude&#xff0c;他属于ChatGPT的最强竞品&#xff0c;支持中文&#xff0c;体验非常舒适&#xff0c;也并不像国内某些自建AI那样弱智。 至于Linux要怎么学&#xff0c;就让Claude来回答吧。 你能告诉我Liunx应该怎么学吗&#xff1f; 学习Linux,我有…

Elasticsearch:使用 Elastic APM 监控 Android 应用程序

作者&#xff1a;Alexander Wert, Cesar Munoz 人们通过私人和专业的移动应用程序在智能手机上处理越来越多的事情。 拥有成千上万甚至数百万的用户&#xff0c;确保出色的性能和可靠性是移动应用程序和相关后端服务的提供商和运营商面临的主要挑战。 了解移动应用程序的行为、…

【Mysql系列】——详细剖析数据库中的存储引擎

【Mysql系列】——详细剖析数据库中的存储引擎&#x1f60e;前言&#x1f64c;存储引擎什么是存储引擎&#xff1f;Mysql的体系结构&#xff1a;Mysql的体系结构分为四层&#xff1a;连接层服务层引擎层存储层存储引擎的查看存储引擎的指定存储引擎的特点InnoDB介绍InnoDB特点I…

论文浅尝 | 大语言模型在in-context learning中的不同表现

笔记整理&#xff1a;毕祯&#xff0c;浙江大学博士&#xff0c;研究方向为知识图谱、自然语言处理链接&#xff1a;https://arxiv.org/pdf/2303.03846.pd本文是谷歌等机构最新发表的论文&#xff0c;旨在研究大模型上下文学习的能力。这篇论文研究了语言模型中的上下文学习是如…

数影周报:现代汽车发生数据泄露事件;淘宝天猫集团完成组织调整

本周看点&#xff1a;现代汽车发生数据泄露事件&#xff1b;微软会议应用Teams 新功能可禁用/启用脏话过滤器&#xff1b;欧洲隐私监管机构创建ChatGPT工作组&#xff1b;淘宝天猫集团完成组织调整&#xff1b;阿里巴巴再向Lazada投资3.529亿美元...... 数据安全那些事 现代汽车…

C语言数据结构-队列的知识总结归纳

队列的知识总结归纳一.队列的基本概念二.循环队列的顺序存储常见的基本操作以及详细图解1.队列的顺序存储结构类型定义2.初始化队列初始化队列示意图3.判断队空4.判断队列是否满的三种方法图示5.入队或进队入队的示意图6出队或退队出队的图示三. 队列的链式存储结构四. 链式队列…

AutoGPT自主人工智能用法和使用案例

介绍 AutoGPT是什么&#xff1a;自主人工智能&#xff0c;不需要人为的干预&#xff0c;自己完成思考和决策【比如最近比较热门的用AutoGPT创业&#xff0c;做项目–>就是比较消耗token】 AI 自己上网、自己使用第三方工具、自己思考、自己操作你的电脑【就是操作你的电脑…

缺省函数,函数重载,引用简单介绍的补充说明

TIPS 命名空间域的作用实际上相当于把部分变量的名称给他隔离起来&#xff0c;这样的话就可以减少变量名的冲突。命名空间是对全局域当中的这些变量啊&#xff0c;函数啊&#xff0c;类型啊进行一个封装与隔离&#xff0c;可以防止你和我之间的冲突&#xff0c;也可以防止与库…

leetcode:各位相加(数学办法详解)

前言&#xff1a;内容包括&#xff1a;题目&#xff0c;代码实现&#xff0c;大致思路 目录 题目&#xff1a; 代码实现&#xff1a; 大致思路&#xff1a; 题目&#xff1a; 给定一个非负整数 num&#xff0c;反复将各个位上的数字相加&#xff0c;直到结果为一位数。返回…

【云原生Docker】11-Docker镜像仓库

【云原生|Docker】11-Docker Registry(官方仓库) 文章目录【云原生|Docker】11-Docker Registry(官方仓库)前言docker registry简介操作示例hyper/docker-registry-web前言 ​ 前面我们所有的docker操作&#xff0c;使用的镜像都是在docker官方的镜像仓库下载&#xff0c;当然这…

总结825

学习目标&#xff1a; 4月&#xff08;复习完高数18讲内容&#xff0c;背诵21篇短文&#xff0c;熟词僻义300词基础词&#xff09; 今日复习&#xff1a; 手绘高数第11讲思维导图&#xff0c;回顾线性代数第一讲 学习内容&#xff1a; 第12讲二重积分视频&#xff0c;纠正11讲…

手势控制的机器人手臂

将向你展示如何构建机械手臂并使用手势和计算机视觉来控制它。下面有一个在开发阶段的机械手臂的演示视频。展示开发中的手臂的演示视频&#xff1a;https://youtu.be/KwiwetZGv0s如图所示&#xff0c;该过程首先用摄像头捕捉我的手及其标志。通过跟踪特定的界标&#xff0c;例…

300到400的蓝牙耳机有哪些推荐?2023年值得入手的性价比蓝牙耳机

今年依旧是真无线蓝牙耳机快速发展的一年&#xff0c;市面上都有着各式各样的蓝牙耳机&#xff0c;一时间难以辨认哪些款式更适合自己&#xff0c;今天给大家介绍的是300元左右的蓝牙耳机&#xff0c;那这个价位的耳机到底怎么样呢&#xff1f;其实&#xff0c;300左右的蓝牙耳…

Qt 窗口置顶

文章目录一、前言二、示例代码三、补充说明四、窗口透明五、参考一、前言 我们使用QT进行界面开发时&#xff0c;可能会遇到需要将窗口置顶的情况。最常见的就是&#xff0c;需要制作一个悬浮工具栏&#xff0c;悬浮菜单&#xff0c;甚至是悬浮的画板。这就意味这我们需要将这个…

Javascript40行代码实现基础MVC原理。

参考文章 M数据层 V视图 C控制器 先来一个dom结构&#xff0c;一个p标签&#xff0c;用来展示输入的内容&#xff0c;一个input标签&#xff0c;用来输入内容⬇️ <p id"mvcp"></p> <input id"mvc"></input>创建Model类&#x…

第二部分——长难句——第一章——并列句

conjunction(and,but,if,when(while)) 想把两个句子&#xff08;多件事&#xff09;连在一块&#xff0c;就必须加上连词。 所以长难句到底是啥&#xff1f; 所以长难句&#xff08;直白表达&#xff0c;并不是语法表述&#xff09;就是几个简单句多家上几个连接词就齐活了&am…

一文读懂Profibus/Profinet/Ethernet的区别

Ethernet(以太网络)是大家很熟悉的一种网络了&#xff0c;由Xerox公司创建并由Xerox、Intel和DEC公司联合开发的基带局域网规范&#xff0c;是当今现有局域网采用的最通用的通信协议标准&#xff0c;包括标准的以太网&#xff08;10Mbit/s)、快速以太网&#xff08;100Mbit/s&a…

Python 自动化测试框架环境怎么搭建?这篇文章给你讲的明明白白

目录 Python 自动化测试框架环境搭建 第一步&#xff1a;安装 Python 第二步&#xff1a;安装 PyCharm 第三步&#xff1a;安装 Selenium WebDriver 第四步&#xff1a;安装浏览器驱动 第五步&#xff1a;创建测试用例 第六步&#xff1a;集成持续集成平台 总结 Python …