【Linux网络编程】- 认识 ‘’协议‘‘ | 网络版本计算器

news2024/12/26 11:32:04


目录

一、“协议” 的概念

二、结构化数据的传输

三、序列化和反序列化

序列化和反序列化的目的

 四、网络版本计算器

服务端(server)

协议定制(protocal)

客户端(client)

服务器处理请求逻辑(Routine)

存在的问题('bug')

代码测试(test)


一、“协议” 的概念

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

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


二、结构化数据的传输

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

• 如果需要传输的数据时一个字符串,那么可以直接将这一字符串发送到网络中,此时对端也能从网络当中获取到这个字符串。

• 如果需要传输的是一些结构化的数据,此时就不能将这些数据一个一个发送到网络中了。

客户端最好把这些结构化的数据打包成一个整体后发送到网络中,服务器每次从网络中获取的数据就是一个完整的请求数据了。

比如实现一个网络版本计算器,需要客户端把要计算的两个数据,以及操作符发送过去,然后由服务器进行计算,最后将计算结果返回给客户端:

约定方案一:

• 客户端发送形如 ‘‘1+1’’的字符串。

• 这个字符串中有两个操作数,都是整型。

• 两个数字之间会有一个字符是运算符。

• 数字和运算符之间没有空格。

此时服务器再以相同方式对这个字符串进行解析,就可以从字符串中提取这些结构化数据。

约定方案二:

• 定制结构体来表示需要交换的信息。

• 发送数据时将这个结构体按照一个规则转换成网络标准数据格式,接受数据时再按照相同的规则把接受到的数据转化为结构体。

• 这个过程叫做 “序列化” 和 “反序列化”。

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


三、序列化和反序列化

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

• 反序列化是把字节序列恢复为对象的过程。

序列化和反序列化的目的

•  在网络传输时,序列化的目的是为了方便网络数据的发送和接收,无论是何种类型的数据,经过序列化之后都会变成二进制序列,此时底层在进行网络数据传输时看到的都是统一的二进制序列。

•  序列化或的二进制序列只有在网络传输时能被底层识别,上层应用是无法识别序列化后的二进制序列的,因此需要将从网络中获取的数据进行反序列化,将二进制序列的数据转换成应用层能够识别的数据格式。


 四、网络版本计算器

服务端(server)

首先对服务器进行初始化:

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

• 调用 bind 函数,对服务器进行绑定端口号等。

• 调用 listen 函数,将套接字设置成监听状态。

其次对服务器进行启动:

不断调用 accept 函数,从套接字中不断获取新连接,这里采用多线程版本,每当获取到一个新的连接后,就创建一个新线程,让新线程对客户端发来的数据进行计算服务。

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <cstring>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
using namespace std;
#define backlog 5

// ./server 8080
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        cerr << "Usage: " << argv[0] << "port" << endl;
        exit(1);
    }
    int port = atoi(argv[1]);
    // 1、创建套接字
    int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_sock < 0)
    {
        cerr << "listen_sock error!" << endl;
        exit(2);
    }
    // 2、绑定
    struct sockaddr_in local;
    memset(&local, 0, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_addr.s_addr = htonl(INADDR_ANY); // 主机转网络
    local.sin_port = htons(port);

    if (bind(listen_sock, (struct sockaddr *)&local, sizeof(local)) < 0)
    {
        cerr << "bind fail!" << endl;
        exit(3);
    }

    // 3、设置监听状态
    if (listen(listen_sock, backlog) < 0)
    {
        cerr << "listen fail!" << endl;
        exit(4);
    }
    // 4、启动服务器
    struct sockaddr_in peer;
    memset(&peer, 0, sizeof(peer));
    for (;;)
    {
        socklen_t socklen = sizeof(peer);
        //一直获取新连接
        int sock = accept(listen_sock, (struct sockaddr *)&peer, &socklen);
        if(sock < 0)
        {
            cerr<<"accept fail!"<<endl;
            continue;//继续获取新连接
        }
        //创建新线程
        pthread_t tid = 0;
        int *p = new int(sock);
        pthread_create(&tid, nullptr, Rountine, p);//回调函数,将sock作为参数传递
    }

    return 0;
}

说明:

• 为了避免创建出来的套接字被下一次创建的套接字覆盖,采用在堆上开辟空间存储的形式存储该文件描述符。

协议定制(protocal)

数据可以分为请求数据和响应数据进行定制协议,采用结构体的方式来实现:

• 请求结构体:需要包含两个操作数,以及所所对应的操作符。

• 响应结构体:需要包含一个计算结果,和一个状态结果,用来标识本次计算的状态,因为可以本次计算出现异常(除0等)。

状态码规定:

• 状态码为0:表示计算成功,无异常现象。

• 状态码为1:表示出现除 0 异常。

• 状态码为2:表示出现模 0 异常。

• 状态码为3:表示其他非法计算,如输入错误的操作符等。

#pragma once

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

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

客户端(client)

首先对客户端进行初始化:

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

• 初始化完毕后,调用 connect 函数进行对服务器的连接,其次将请求发送给服务器。

• 客户端等待服务器处理完毕,发送回来结果后,还需要读取服务端的响应数据。

 

发送数据时:使用 write 或者 send 函数,这些函数的本质都是拷贝函数。

接收数据时:使用 read 或者 recv 函数,本质也是拷贝函数。

#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <cstring>
#include <string>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
using namespace std;

#include "protocal.h"

// ./client 127.0.0.1 8080
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        cerr << "Usage: " << argv[0] << " server_ip  server_port " << endl;
        exit(1);
    }

    std::string server_ip = argv[1];
    int server_port = atoi(argv[2]);

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        cerr << "sock 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()); // 网络转主机 inet_addr

    if (connect(sock, (struct sockaddr *)&peer, sizeof(peer)) < 0)
    {
        cerr << "connect fail!" << endl;
        exit(3);
    }

    // 发送请求
    while (1)
    {
        request req;
        cout << "请输入左操作数# ";
        cin >> req.x;
        cout << "请输入右操作数# ";
        cin >> req.y;
        cout << "请输入操作符[+-*/%]# ";
        cin >> req.op;
        send(sock, &req, sizeof(req), 0);

        // 接受数据
        response res;
        recv(sock, &res, sizeof(res), 0); // 阻塞式
        cout << "status: " << res.code << endl;
        cout << req.x << req.op << req.y << " = " << res.result << endl;
    }
    return 0;
}

服务器处理请求逻辑(Routine)

创建出来的新线程,需要对客户端发送到计算请求进行读取,然后进行计算操作,如果在计算过程中,出现除0等情况,只需要对 response 结构体填充进对应的状态码即可。

void *Rountine(void *arg)
{
    // 线程分离,不需要再 wait
    pthread_detach(pthread_self());
    int sock = *(int *)arg;
    delete (int *)arg;

    while (1)
    {
        request req;
        ssize_t n = recv(sock, &req, sizeof(req), 0);
        if (n > 0)
        {
            // 进行计算任务
            response res = {0, 0};
            switch (req.op)
            {
            case '+':
                res.result = req.x + req.y;
                break;
            case '-':
                res.result = req.x - req.y;
                break;
            case '*':
                res.result = req.x * req.y;
                break;
            case '/':
                if (req.y == 0)
                {
                    res.code = 1;
                }
                else
                {
                    res.result = req.x / req.y;
                }
                break;
            case '%':
                if (req.y == 0)
                {
                    res.code = 2;
                }
                else
                {
                    res.result = req.x % req.y;
                }
                break;
            default:
                res.code = 3;
                break;
            }

            // 将结果发送回客户端
            send(sock, &res, sizeof(res), 0);
        }
        else if (n == 0)
        {
            // 对端停止发送数据,退出了
            cout << "Client quit,me too!" << endl;
            break;
        }
        else
        {
            cerr << "recv error!" << endl;
            break;
        }
    }
    close(sock);
    return nullptr; // 返回结果
}

存在的问题('bug')

• 在发送和接收数据时没有进行对数据的序列化以及反序列化。

代码测试(test)

先运行服务器,./ server 8080 绑定端口号,再运行客户端 ./client 127.0.0.1 8080,然后进行发送数据,让服务器进行计算:


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

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

相关文章

【Unity3D】地面网格特效

1 前言 本文实现了地面网格特效&#xff0c;包含以下两种模式&#xff1a; 实时模式&#xff1a;网格线宽度和间距随相机的高度实时变化&#xff1b;分段模式&#xff1a;将相机高度分段&#xff0c;网格线宽度和间距在每段中对应一个值。 本文完整资源见→Unity3D地面网格特效…

C++ —— 类与对象(上)

前言 由于C在C语言的基础上移植了新的编程理念&#xff0c;所以我们先回顾一下C语言所遵循的旧的理念。一般来说&#xff0c;计算机语言要处理两个概念——数据和算法。数据是程序使用和处理的信息&#xff0c;而算法是程序使用的方法。C语言与当前最主流的语言一样&#xff0…

关于 Qt中的QString内容存在\u0000使用QChart(0x00)消除 的解决方法

若该文为原创文章&#xff0c;转载请注明原文出处 本文章博客地址&#xff1a;https://hpzwl.blog.csdn.net/article/details/131860574 红胖子(红模仿)的博文大全&#xff1a;开发技术集合&#xff08;包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软…

Maven 基础之简介,基础配置

Maven 基本概念 Maven 是基于项目对象模型&#xff08;Project Object Model&#xff09;&#xff0c;可以通过一小段描述信息来管理项目的构建&#xff0c;报告和文档的软件项目管理工具。 Maven 主要有 2 个功能&#xff1a;「项目构建」和「依赖管理」。 &#x1f58b; 说…

Godot 4 源码分析 - 增加管道通信

学习研究Godot 4&#xff0c;很爽&#xff0c;虽然很庞杂&#xff0c;但相对于自己的水平来说&#xff0c;很强大&#xff0c;尤其是vulkan这块直接打包可用&#xff0c;省得自己从头琢磨。 一点一点地消化、优化与完善&#xff0c;最终才能成为自己的。 这段时间就在Godot的…

POSIX线程编程

死在山野的风里&#xff0c;活在自由的梦里 本专栏参考教材是四川轻化工大学陈年老师的linux实验指导手册&#xff08;含学习通的一些程序笔记&#xff09;。 POSIX线程编程 1.线程是什么2.创建线程创建一个用户级的线程&#xff0c;实现在线程中更改进程&#xff08;主线程&a…

Bean小结

Bean是Spring框架中最核心的两个概念之一&#xff08;另一个是面向切面编程AOP&#xff09;。 Bean 定义 Spring 官方文档对 bean 的解释是&#xff1a; In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC contain…

从Vue2到Vue3【三】——Composition API(第三章)

系列文章目录 内容链接从Vue2到Vue3【零】Vue3简介从Vue2到Vue3【一】Composition API&#xff08;第一章&#xff09;从Vue2到Vue3【二】Composition API&#xff08;第二章&#xff09;从Vue2到Vue3【三】Composition API&#xff08;第三章&#xff09;从Vue2到Vue3【四】C…

webrtc QOS方法二.4(flexfec 实现可优化点)

一、冗余报文和媒体报文组织结构优化点 以单帧10个媒体报文&#xff0c;冗余度20%为例。这里webrtc输出要有10个媒体包2个冗余包。webrtc输出的报文序列如下&#xff1a; 代码实现如下&#xff1a; UlpfecGenerator::AddPacketAndGenerateFec&#xff1a;攒够足够的帧 Forwar…

使用springboot进行后端开发100问

properties和yaml文件怎么互转 安装插件 properties文件和yaml文件区别 properties 文件通过“.”和“”赋值&#xff0c;值前不加空格&#xff0c;yaml通过“:”赋值&#xff0c;值前面加一个空格&#xff1b;yaml文件缩进用空格&#xff1b; properties只支持键值对&#x…

C++实现LRU(逐句讲解)

使用双向链表解决此问题&#xff0c;因为双向链表可以很容易的获取到头结点和尾结点。题目要求 get 和 put 要在O(1)的时间复杂度下运行&#xff0c;很显然要用set或map。根据题意&#xff0c;应使用map。 unordered_map<int,Node*> cache; map->first为Node中的key&a…

查看RabbitMQ日志---trace插件的使用

我的RabbitMQ是安装在docker里面的 所以我以下的方法都是根据这个路径去操作的 如果RabbitMQ安装在其他地方 请自行百度 1. 显示正在运行的RabbitMQ容器的名称或ID&#xff1a; docker ps这将启动所有正在运行的 Docker 容器&#xff0c;并包含 RabbitMQ 容器的信息。 使用…

【Docker】Docker的数据管理

目录 一、Docker 的数据管理1.1数据卷1.2 数据卷容器1.3端口映射1.4容器互联&#xff08;使用centos镜像&#xff09; 二、Docker镜像的创建2.1基于现有镜像创建2.2&#xff0e;基于本地模板创建2.3 基于Dockerfile 创建联合文件系统&#xff08;UnionFS&#xff09;镜像加载原…

【云原生】Docker网络及Cgroup资源控制

一、Docker网络 1.docker网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默认网关。…

【Linux网络】 网络套接字(二)socket编程_UDP网络程序

目录 socket 编程接口socket 常见的APIsockaddr结构 UDP网络程序简单例子服务端代码编写服务端创建套接字服务端绑定运行服务器测试启动服务端 客户端代码编写客户端创建套接字启动客户端本地测试INADDR_ANY服务端接收信息发回到客户端如何进行网络测试 socket 编程接口 socke…

【机器学习】吃瓜教程 | 西瓜书 + 南瓜书 (1)

文章目录 一、绪论1、什么是机器学习&#xff1f;2、基本术语3、假设空间4、归纳偏好5、发展历程 二、模型评估与选择A、一种训练集一种算法2.1 经验误差 与 过拟合2.2 评估方法a) 留出法b) 交叉验证法c) 自助法d) 调参与最终模型 2.3 性能度量a) 错误率与精度b) 查准率、查全率…

RT-Thread快速入门-内核移植

1RT-Thread快速入门-内核移植 RT-Thread 快速入门系列前面的文章介绍了内核相关的知识&#xff0c;以及内核提供的接口函数和如何使用。 本篇文章主要介绍如何将 RT-Thread 内核移植到某个硬件平台之上。移植分为两部分&#xff1a; CPU 架构移植 BSP 移植 也就是将 RT-Th…

MySQL第二课表的增删插改

&#x1f49b; 后端进行的表的操作增删查改 现在是建了一个成绩表&#xff0c;注意哈。 decimal(2,1). 2是M表示有两个有效数字长度&#xff0c;1是D的长度&#xff0c;即小数点后有一位(10分制) &#x1f493;开始 1.增加&#xff1a; insert into 表名 values(值&#xff0…

安装VS Code 和 MiKTeX开发环境

下载&#xff1a; Getting MiKTeX 然后以管理员方式运行安装。 配置VS Code 之后配置VS Code&#xff0c;选择扩展&#xff08;两个位置都可以&#xff09;&#xff0c;然后搜索Latex&#xff1a; 然后打开设置&#xff1a; 这样就打开了setting.json文件&#xff0c; 然后…

SQL注入之Oracle环境搭建

SQL注入之Oracle环境搭建 前言 Oracle Database&#xff0c;又名Oracle RDBMS&#xff0c;或简称Oracle。是甲骨文公司的一款关系数据库管理系统。它是在数据库领域一直处于领先地位的产品。可以说Oracle数据库系统是世界上流行的关系数据库管理系统&#xff0c;系统可移植性…