【Linux】序列化与反序列化{服客编程/守护进程/JSON}

news2024/11/24 0:08:33

文章目录

  • 1.引入
  • 2. 静态成员函数
  • 3.TCP:传输控制协议
  • 4.守护进程
    • 4.0前台进程
    • 4.1介绍
    • 4.2认识
    • 4.3会话
    • 4.3ps axj
    • 4.4理解
    • 4.5/dev/null
    • 4.6守护进程和孤儿进程
  • 5.JSON
  • 6.完整代码
    • 6.1Makefile
    • 6.2Socket.hpp
    • 6.3Protocol.hpp
    • 6.4Log.hpp
    • 6.5Daemon.hpp
    • 6.6TcpServer.hpp
    • 6.7Client.cc
    • 6.8Server.cc

1.引入

  1. 前面所讲的udp/tcp编程属于应用层开发
  2. 之前讲的套接字没有涉及到协议定制的问题 只是用套接字用来数据的收发
    结构体协议:
    可扩展性差:修该op可能导致整个协议得跟着变
    平台不一致:大小端/内存对齐 不同平台对struct的定义不同 截取二进制时易乱码
    结构化的数据 – 序列化的数据 – 字节流
    tcp/udp:内核级的协议传的是结构化的数据 :条件编译识别平台 对大小端/内存对齐都做了约定
    序列化:减少通信成本 降低应用层编码难度 结构化的数据一般给上层业务使用 字节流比较适合网络传输 ==》 业务逻辑和网络通信的解耦!
    字段本身就是协议的一部分!:字段代表什么含义

2. 静态成员函数

独立于任何特定的对象实例:静态成员函数不依赖于特定的对象实例,因此可以直接通过类名来调用,而无需创建类的对象实例。

无法访问非静态成员变量或函数:静态成员函数只能访问静态成员变量和静态成员函数,无法直接访问类的非静态成员变量或函数。这是因为非静态成员变量或函数的访问需要特定的对象实例,而静态成员函数没有与之关联的实例。

可以直接访问类的静态成员变量和函数:静态成员函数可以直接访问类的静态成员变量和函数,因为它们都属于类而不是对象实例。

不具有this指针:静态成员函数不具有隐含的this指针,因为它们不与特定的对象实例相关联。因此,在静态成员函数中不能使用this指针来访问对象的成员变量或函数。

用于实现与类相关的全局函数或工具函数:静态成员函数常用于实现与类相关的全局函数或工具函数,这些函数不依赖于任何特定的对象实例,而只与类本身相关。

总的来说,静态成员函数是类的一部分,但它们与特定的对象实例无关,并且通常用于执行与类相关的操作而不是特定对象的操作

3.TCP:传输控制协议

  1. 调用的send/recv并不是直接把数据发到/接收 网络或主机
  2. 什么时候发/发多少/出错了怎么办 由TCP控制
  3. 客户端把发送的信息存入自己定义的clientBuf,然后调用send,实际上是把clientBuf的数据拷贝到一个发送缓冲区,recv也一样,把数据从接收缓冲区拷贝到服务端定义的serverBuf==》IO接口本质是拷贝函数
  4. 发送的次数和接收的次数无关。若clientBuf大发送缓冲区小,clientBuf要向发送缓冲区拷贝多次才能是一个完整的数据。服务端同。
  5. 缓冲区什么时候向网络发:这是TCP控制的!

之前写的tcp代码都是不完善的!

  1. UDP面向数据报,而TCP面向字节流,并没有对【发送的数据格式和接收的数据格式】做控制。udp即便发很多,读的时候也是一个一个读。tcp不会。
  2. 如何保证读到的数据是以一个完整的数据?_left _op _right\r\n 这样能保证吗?识别到特殊字符即为一个数据,可以吗?不可以!如果正文里就含有特殊字符怎么办?length\r\n_left _op _right\r\n:length是一个整数,他绝对不会包含\r\n,length告知了一个完整的数据有多长,读数据时读”这么长“的数据即可。length后的\r\n用来区分【协议报头和有效载荷】;_right后的\r\n用来增强数据可读性,不加也可以。length不包括\r\n。
  3. 有了数据格式的规定,再来谈数据的读取。一次只读一个完整数据读不到就等待:不采取,效率低。一次把缓冲区的数据都都进来之后按照规定好的格式去解析读取:采取,效率高。

4.守护进程

在这里插入图片描述

4.0前台进程

Linux下什么叫前台进程,与后台进程有什么区别

命令行下能处理你输入命令的进程叫前台进程。BASH就是。

在Linux系统中,前台进程是指在终端活动和用户输入下运行的进程,用户可以在终端中输入命令并直接与之交互。具体来说,前台进程是在前台运行的程序,用户可以看到程序的输出,并且可以通过键盘输入与程序进行交互。

与此相对,后台进程则是在后台默默运行的程序,不受用户当前操作的影响。后台进程也叫守护进程(Daemon),通常不受终端控制,也不需要终端的交互。Linux的大多数服务器就是使用守护进程实现的,如Web服务器的httpd等。这些进程即便在用户关闭终端后也会继续运行

总的来说,前台进程和后台进程的主要区别在于是否与用户直接交互以及是否受终端的控制。前台进程直接与用户交互并受终端控制,而后台进程(守护进程)则独立运行,不受用户当前操作和终端的控制。

此外,前台进程和后台进程可以通过一些命令进行互相转换。例如,在Linux终端中执行的命令后加上“&”符号,可以把该命令放到后台执行。同样,也可以使用“fg”命令将后台中的命令调至前台继续运行。

请注意,对于需要长时间运行的任务或者不希望被打断的任务,通常会将其放到后台运行。而对于需要实时查看输出或者需要与用户交互的任务,则通常在前台运行。

4.1介绍

在Linux系统中,守护进程(Daemon)是一种在后台运行并提供某种服务的进程。它们通常在系统启动时开始运行,并持续运行直到系统关闭。守护进程是系统的重要组成部分,负责处理系统任务、监听网络请求、管理硬件资源等。

一、守护进程的特点

后台运行:守护进程在后台运行,不占用前台终端窗口,因此用户通常看不到它们的运行过程。
持续运行:守护进程在系统运行时一直存在,除非被显式停止或系统关闭
提供服务:守护进程通常负责提供某种服务,如Web服务、数据库服务、文件共享服务等。
响应请求:守护进程能够监听并响应来自客户端的请求,如网络请求、文件操作请求等。

二、守护进程的创建与管理

  1. 创建守护进程
    在Linux中,可以通过多种方式创建守护进程,例如使用系统服务管理工具(如systemd、init.d等)或编写自定义的守护进程程序。自定义守护进程程序通常涉及以下步骤:

(1)在程序中调用fork()创建一个子进程,并让父进程立即退出。这样,子进程将成为一个孤儿进程,被init进程(PID为1的进程)接管。

(2)在子进程中调用setsid()创建一个新的会话,并成为会话的领头进程。这将使子进程成为一个新的进程组的领头进程,并且没有控制终端。

(3)关闭不必要的文件描述符,重定向标准输入、输出和错误输出到/dev/null或指定的日志文件。

(4)更改当前工作目录到一个合适的路径,如“/”或“/tmp”,以防止守护进程占用的挂载点被卸载而导致问题。

(5)设置文件权限掩码,以确保守护进程创建的文件具有正确的权限。

(6)进入无限循环,执行守护进程的主要任务。

  1. 管理守护进程
    Linux提供了多种工具来管理守护进程,如systemctl、service、init.d脚本等。这些工具可以启动、停止、重启和查询守护进程的状态。例如,使用systemctl命令可以方便地管理systemd管理的服务:

(1)启动服务:systemctl start 服务名

(2)停止服务:systemctl stop 服务名

(3)重启服务:systemctl restart 服务名

(4)查看服务状态:systemctl status 服务名

此外,还可以使用ps、top等命令查看系统中运行的守护进程及其状态。

三、守护进程的安全性

由于守护进程在系统后台运行并具有特定的权限,因此其安全性至关重要。以下是一些提高守护进程安全性的建议:

使用非root用户运行守护进程,以减少潜在的安全风险。
限制守护进程对网络、文件系统和其他资源的访问权限。
对守护进程的输入进行验证和过滤,以防止注入攻击等安全漏洞。
定期更新和修补守护进程及其依赖的库和工具,以修复已知的安全漏洞。
总之,Linux下的守护进程是系统的重要组成部分,负责提供各种服务并处理系统任务。了解守护进程的特点、创建与管理方法以及安全性建议,有助于更好地使用和管理Linux系统。

setsid

在这里插入图片描述

daemon

在这里插入图片描述
在这里插入图片描述

为什么程序员不使用daemon而自己编写代码来让进程变成守护进程

程序员选择自己编写代码来让进程变成守护进程,而不是直接使用系统提供的daemon功能,主要有以下几个原因:

自定义和灵活性:不同的应用程序和服务可能需要不同的守护进程行为。自己编写代码可以更精确地控制守护进程的启动、运行和退出行为,以满足特定需求。同时,通过编程,可以方便地集成其他功能,如日志记录、性能监控等。

跨平台兼容性:不同的操作系统和平台可能具有不同的daemon或守护进程实现方式。自己编写代码可以确保跨平台的兼容性,使得程序可以在多个操作系统上运行,而无需对守护进程部分进行大量修改。

控制和监控:通过自己编写代码,程序员可以更好地控制和监控守护进程的状态和行为。他们可以添加自定义的监控机制,以便在守护进程出现问题时及时得到通知,并采取相应的措施。

学习和成长:自己编写守护进程代码对于程序员来说是一个很好的学习和成长机会。通过深入了解守护进程的工作原理和编程技术,他们可以提升自己的技能水平,并更好地应对未来的挑战。

需要注意的是,虽然自己编写代码可以带来更高的灵活性和自定义程度,但同时也增加了开发和维护的复杂度。在选择是否自己编写守护进程代码时,程序员需要权衡这些因素,并根据项目的实际需求做出决策。此外,对于一些常见的守护进程需求,使用现有的系统工具或库也是一个很好的选择,可以节省开发时间和提高代码质量。

怎么干掉守护进程?

  1. 设置信号捕捉函数,如发送2号信号就杀死他。
  2. 直接kill -9

4.2认识

  1. 前台进程: 和终端关联的进程。
  2. 任何xshell 登陆,只允许一个前台进程和多个后台进程
  3. 进程除了有自己的pid,ppid,还有一个组ID:GID
  4. 在命令行中,同时用管道启动多个进程,多个进程是兄弟关系,父进程都是bash->可以用匿名管道来进行通信
  5. 同时被创建的多个进程可以成为一个进程组,组长一般是第一个进程
  6. 任何一次登陆,登陆的用户,需要有多个进程(组),来给这个用户提供服务的(bash),用户自己可以启动很多进程,或者进程组。给用户提供服务的进程或者用户自己启动的进程,整体属于“会话”。
  7. 守护进程不能直接向显示器打印消息 一旦打印,会被暂停/终止

4.3会话

Linux下的会话(Session)是指用户在Linux操作系统中与系统进行交互的一种方式。会话是用户登录到系统后启动的进程集合,可以包含一个或多个进程,这些进程又可以包含一个或多个作业。每个会话都有一个唯一的会话ID,用于标识该会话。

会话具有以下几个特点:

交互性:会话是用户与系统进行交互的平台,用户可以通过命令行或图形界面输入指令、操作文件等。
持久性:会话可以持续存在一段时间,用户可以在会话中多次执行指令,直到退出会话或系统重启。
独立性:每个会话是相互独立的,不会互相干扰。用户可以同时启动多个会话,分别执行不同的任务。
在Linux中,用户可以通过命令行终端、SSH远程登录等方式来创建会话。会话有两种类型:前台会话和后台会话。前台会话是用户正在操作的会话,用户可以通过命令行终端或图形界面与前台会话进行交互。后台会话是用户在执行某些任务时创建的会话,这些任务在后台运行,用户可以在后台会话执行任务的同时继续进行其他操作。

此外,Linux还提供了一些会话管理命令,如who命令来显示当前登录到系统的用户和会话信息,exit命令来退出当前会话等。同时,还有如Screen和Tmux这样的会话管理工具,可以帮助用户更方便地管理会话,例如断开与恢复会话、列出与切换会话等。

总的来说,Linux会话是Linux操作系统中非常重要的概念,它提供了用户与系统交互的一种方式,通过会话控制和共享,可以管理系统资源和进程,提高系统的利用率。

4.3ps axj

在这里插入图片描述

PPID (Parent Process ID):

表示创建当前进程的父进程的进程ID。每个进程都是由另一个进程创建的,这个创建者就是父进程。

PID (Process ID):

进程的唯一标识符。每个进程在系统中都有一个唯一的进程ID。

GID (Group ID):

进程所属的实际组ID。在Unix和Linux系统中,进程不仅属于一个用户,还属于一个或多个组。

SID (Session ID):

会话的标识符。会话是一组共享同一个控制终端的进程。会话的第一个进程(通常是登录shell)是会话领导者,其PID也是SID。

TTY (Teletype):

进程关联的控制终端的名称。这通常是进程与其交互的终端设备的名称。如果进程没有与任何终端关联(例如后台进程),则此字段可能显示为?

TPGID (TTY Process Group ID):

与进程关联的终端进程组ID。进程组是一组共享同一终端输入和输出的进程。TPGID标识了这个进程组中前台进程的PID。

STAT (Process Status):

进程的状态。常见的状态有:
R (运行): 正在运行或在运行队列中等待。
S (休眠): 休眠状态,等待某个条件成立。
Z (僵尸): 终止状态,父进程尚未回收其资源。
T (停止): 进程被停止。
D (不可中断的休眠): 通常是在进行I/O操作时。
以及其他状态…

UID (User ID):

运行进程的用户ID。每个进程都有一个与之关联的用户,该用户拥有运行该进程的所有权限。

TIME:

进程使用的总CPU时间,通常以分钟和秒的形式表示。这表示该进程自启动以来消耗的总CPU时间。

COMMAND:

启动进程的命令行。这通常是启动进程时使用的完整命令行。

这些字段之间的联系体现在它们共同描述了进程在系统中的状态、属性以及与其他进程和系统的交互方式。例如,PPID 和 PID 一起描述了进程之间的父子关系;GID 和 UID 描述了进程的安全上下文;TTY 和 TPGID 描述了进程与终端的交互方式;而 STAT 和 TIME 则提供了进程当前状态和资源使用情况的信息。

4.4理解

  1. xshell下可以创建多个会话。会话中的进程在退出登录时不一定结束:不同OS处理不同。
  2. 守护进程是孤儿进程的一种,孤儿进程属于原来环境下的会话,而守护进程属于一个独立的新的会话。
  3. 在Unix和Linux操作系统中,sid(Session ID,会话ID)是进程会话的标识符。会话是一个或多个进程组的集合,这些进程组共享一个控制终端。会话ID是与会话关联的唯一标识符,通常用于区分不同的会话。每个会话都有一个领头进程(session leader),这个进程通常是创建会话的进程。领头进程的进程ID(PID)通常也是会话ID(SID)。会话领头进程负责管理会话中的进程组,并控制它们对终端的访问。
  4. setsid是一个Unix命令,用于创建一个新的会话,并使调用进程成为会话的领头进程。调用setsid的进程会成为新会话的唯一进程组,并且没有控制终端。这通常用于编写守护进程(daemon),以确保守护进程不是任何终端的前台进程,从而避免终端关闭时守护进程也被终止。

4.5/dev/null

在Unix和Linux操作系统中,/dev/null 是一个特殊的设备文件,通常被称为空设备或空文件。它有两个主要的特性:

写入 /dev/null 的数据会被丢弃:当你将任何数据写入 /dev/null 时,这些数据都会被操作系统立即丢弃,就像它们被扔进了一个黑洞一样。这个特性使得 /dev/null 成为了一个方便的数据丢弃点。例如,如果你有一个命令的输出你不关心,你可以将其重定向到 /dev/null 以避免在终端上显示它。

bash
some_command > /dev/null
这条命令会执行 some_command,但是任何输出到标准输出的内容都会被丢弃,不会在终端上显示。

从 /dev/null 读取数据会立即返回文件结束:如果你尝试从 /dev/null 读取数据,操作系统会立即返回文件结束(EOF)标志,就像文件已经读取完毕一样。这意味着从 /dev/null 读取不会得到任何数据。

这两个特性使得 /dev/null 在Unix和Linux脚本编写和系统管理中非常有用。以下是一些常见的使用场景:

丢弃不需要的输出:当你运行一个命令,但不想看到其输出时,可以将输出重定向到 /dev/null。

bash
find / -name “*.log” > /dev/null
这条命令会查找系统中所有的 .log 文件,但不会显示任何输出。

忽略输入:当你编写一个程序或脚本,并且你不关心从某个文件或设备读取的输入时,可以将输入重定向自 /dev/null。

bash
some_command < /dev/null
这条命令会执行 some_command,并忽略任何尝试从标准输入读取的数据。

从技术和实现的角度来看,/dev/null 是一个由内核提供的特殊设备,它实现了上述的读写行为。在文件系统层次结构中,它通常位于 /dev 目录下,这个目录包含了所有设备文件,它们代表了系统的各种硬件设备和特殊文件。

4.6守护进程和孤儿进程

守护进程(Daemon)和孤儿进程(Orphan Process)在操作系统中各自扮演着不同的角色,它们之间存在明显的区别。

守护进程是在计算机操作系统中以后台方式运行的长期进程。它们通常在系统启动时启动,并在系统运行期间持续运行,负责执行一些特定的任务或提供某种服务。守护进程的设计理念是为了在系统启动后,持续提供服务或执行任务,而不需要交互式用户干预。守护进程具有后台运行、长期运行和无人值守的特点。它们是特意产生的,用于完成周期性的任务,如Web服务器、数据库服务等。

而孤儿进程则是指其父进程执行完或者被终止后仍然继续运行的进程。孤儿进程的一个主要特征是,它们的父进程ID(PPID)变为1号进程(通常是init进程或其后续版本,如systemd)。孤儿进程是意外产生的,可能由于父进程异常退出或结束而留下。它们通常不占据前台终端,并在后台运行。需要注意的是,虽然孤儿进程在技术上可能是一种资源泄漏,但通常情况下它们并不会对系统造成危害,因为系统会负责清理这些进程。

综上所述,守护进程和孤儿进程的主要区别在于它们的产生方式、运行目的以及在系统中的角色。守护进程是特意为提供服务或执行任务而创建的,而孤儿进程则是由于父进程退出而意外留下的。尽管它们在后台运行且不与用户交互,但它们的存在和行为在操作系统中有不同的意义和处理方式。

5.JSON

JSON(JavaScript Object Notation,JS对象简谱)是一种轻量级的数据交换格式
它基于ECMAScript的一个子集,采用完全独立于语言的文本格式来存储和表示数据。简单、清晰的层次结构使得JSON成为理想的数据交换语言。易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。

JSON建构于两种结构:

“名称/值”对的集合(A collection of name/value pairs)。不同的语言中,它被理解为对象(object),纪录(record),结构(struct),字典(dictionary),哈希表(hash table),有键列表(keyed list),或者关联数组(associative array)。
值的有序列表(An ordered list of values)。在多数语言中,它被理解为数组(array)。
这些结构可以被嵌套,例如,对象里可以包含数组,数组里可以包含对象,对象里还可以包含另一个对象,等等。

总的来说,JSON是一种非常灵活且易于使用的数据格式,它广泛应用于网络数据传输和配置文件等场景。

6.完整代码

6.1Makefile

.PHONY:all
all:client CalServer

client:CalClient.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp -DDEBUG_COMPILE
CalServer:CalServer.cc
	g++ -o $@ $^ -std=c++11 -ljsoncpp -lpthread -DDEBUG_COMPILE

.PHONY:clean
clean:
	rm -f client CalServer

6.2Socket.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <cassert>
#include <unistd.h>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <ctype.h>
#include "Log.hpp"

class Sock
{
public:
    Sock() {}

    int Socket()
    {
        int socketFd = socket(AF_INET, SOCK_STREAM, 0);
        if (socketFd < 0)
        {
            logMsg(FATAL, "create socket error, %d:%s", errno, strerror(errno));
            exit(2);
        }
        logMsg(NORMAL, "create socket success, listenSocket: %d", socketFd);
        return socketFd;
    }

    void Bind(int socket, uint16_t port, std::string ip = "0.0.0.0")
    {
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(port);

        if (bind(socket, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMsg(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }
    }

    void Listen(int socket)
    {
        if (listen(socket, g_backlog) < 0)
        {
            logMsg(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMsg(NORMAL, "listen success");
    }

    // const std::string&: 输入型参数
    // std::string*: 输出型参数
    // std::string&: 输入输出型参数
    //服务端接受客户端发来的连接请求 并 把来源客户端的信息存入对应字段
    int Accept(int listenSocket, std::string *clientIp, uint16_t *clientPort)
    {
        struct sockaddr_in src;
        socklen_t len = sizeof(src);

        int serviceSocket = accept(listenSocket, (struct sockaddr *)&src, &len);
        if (serviceSocket < 0)
        {
            logMsg(ERROR, "accept error, %d:%s", errno, strerror(errno));
            return -1;
        }

        if (clientPort != nullptr)
            *clientPort = ntohs(src.sin_port);
        if (clientIp != nullptr)
            *clientIp = inet_ntoa(src.sin_addr);

        return serviceSocket;
    }

    //客户端向服务端发起连接请求
    bool Connect(int socketFd, const std::string &serverIp, const uint16_t &serverPort)
    {
        struct sockaddr_in svr_sockAddr;
        memset(&svr_sockAddr, 0, sizeof(svr_sockAddr));
        svr_sockAddr.sin_family = AF_INET;
        svr_sockAddr.sin_addr.s_addr = inet_addr(serverIp.c_str());
        svr_sockAddr.sin_port = htons(serverPort);

        if (connect(socketFd, (struct sockaddr *)&svr_sockAddr, sizeof(svr_sockAddr)) == 0)
            return true;
        else
            return false;
    }

    ~Sock() {}

private:
    static const int g_backlog = 20;
};

6.3Protocol.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <jsoncpp/json/json.h>

namespace Protocol
{
#define MYSELF 0

#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define SEP "\r\n"
#define SEP_LEN strlen(SEP)

#define DIVZERO 1
#define MODZERO 2
#define ILLEGAL 3

    class Request
    {
    public:
        Request()
        {
        }

        Request(int left, int right, char op)
            : _left(left),
              _right(right),
              _op(op)
        {
        }

        ~Request() {}

        std::string Serialize()
        {
#ifdef MYSELF
            //"left _op _right"
            std::string str;
            str = std::to_string(_left);
            str += SPACE;
            str += _op;
            str += SPACE;
            str += std::to_string(_right);
            return str;
#else
            //创建并序列化一个JSON对象
            Json::Value root;
            root["left"] = _left;
            root["right"] = _right;
            root["op"] = _op;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }

        bool Deserialize(const std::string &str)
        {
#ifdef MYSELF
            //"left _op _right"
            std::size_t firstSpace = str.find(SPACE);
            if (firstSpace == std::string::npos)
                return false;

            std::size_t right = str.rfind(SPACE);
            if (right == std::string::npos)
                return false;

            _left = atoi(str.substr(0, firstSpace).c_str());
            _right = atoi(str.substr(right + SPACE_LEN).c_str());

           if (firstSpace + SPACE_LEN > str.size())
                return false;
            else
                _op = str[firstSpace + SPACE_LEN];

            return true;
#else
            //解析一个JSON字符串并提取值
            Json::Value root;
            Json::Reader reader;
            reader.parse(str, root);
            _left = root["left"].asInt();
            _right = root["right"].asInt();
            _op = root["op"].asInt();

            return true;
#endif
        }

    public:
        int _left;
        int _right;
        char _op;
    };

    class Response
    {
    public:
        Response()
        {
        }
        Response(int result, int code, int left, int right, char op)
            : _result(result),
              _code(code),
              _left(left),
              _right(right),
              _op(op)
        {
        }
        ~Response() {}

        // "_code result_"
        std::string Serialize()
        {
#ifdef MYSELF
            std::string s;
            s = std::to_string(_code);
            s += SPACE;
            s += std::to_string(_result);

            return s;
#else
            Json::Value root;
            root["code"] = _code;
            root["result"] = _result;
            root["xx"] = _left;
            root["yy"] = _right;
            root["zz"] = _op;
            Json::FastWriter writer;
            return writer.write(root);
#endif
        }
        // "_code result_"
        bool Deserialize(const std::string &s)
        {
#ifdef MYSELF
            std::size_t pos = s.find(SPACE);
            if (pos == std::string::npos)
                return false;

            _code = atoi(s.substr(0, pos).c_str());
            _result = atoi(s.substr(pos + SPACE_LEN).c_str());
            return true;
#else
            Json::Value root;
            Json::Reader reader;
            reader.parse(s, root);
            _code = root["code"].asInt();
            _result = root["result"].asInt();
            _left = root["xx"].asInt();
            _right = root["yy"].asInt();
            _op = root["zz"].asInt();
            return true;
#endif
        }

    public:
        int _result;
        int _code;

        //一下成员变量实际上不用 此处为了测试JSON比自定义协议更容易扩展
        int _left;
        int _right;
        char _op;
    };

    bool Recv(int socket, std::string *out)
    {
        // UDP是面向数据报, TCP 面向字节流的
        // recv不能保证读到的inbuffer是一个完整的请求 对协议进一步定制

        char buffer[1024];
        ssize_t s = recv(socket, buffer, sizeof(buffer) - 1, 0);
        if (s > 0)
        {
            buffer[s] = 0;
            *out += buffer;
        }
        else if (s == 0)
        {
            // std::cout << "client quit" << std::endl;
            return false;
        }
        else
        {
            // std::cout << "recv error" << std::endl;
            return false;
        }
        return true;
    }

    void Send(int socket, const std::string str)
    {
        // std::cout << "sent in" << std::endl;
        int sendBytes = send(socket, str.c_str(), str.size(), 0);
        // if (sendBytes < 0)
        //    std::cout << "send error" << std::endl;
    }

    // "length\r\nXXXXXX\r\n"
    std::string Encode(std::string &s)
    {
        std::string new_package = std::to_string(s.size());
        new_package += SEP;
        new_package += s;
        new_package += SEP;
        return new_package;
    }

    // "length\r\n_left _op _right\r\n"
    std::string Decode(std::string &buffer)
    {
        std::size_t pos = buffer.find(SEP);
        if (pos == std::string::npos)
            return "";

        int length = atoi(buffer.substr(0, pos).c_str());
        int surplus = buffer.size() - pos - 2 * SEP_LEN;

        if (surplus < length)
            return "";

        // 至少具有一个完整的报文
        buffer.erase(0, pos + SEP_LEN);
        std::string msg = buffer.substr(0, length);
        buffer.erase(0, length + SEP_LEN);

        return msg;
    }
}

6.4Log.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>
#include <ctime>
#include <string>

// 日志级别
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *gLevelMap[] = {
    " DEBUG ",
    " NORMAL",
    "WARNING",
    " ERROR ",
    " FATAL "};

#define LOGFILE "./Calculator.log"

// 日志功能: 日志等级 时间 用户自定义(日志内容/文件名/文件行) 等
void logMsg(int level, const char *format, ...)
{
#ifndef DEBUG_COMPILE // 非调试编译下 不输出DEBUG信息
    if (level == DEBUG)
        return;
#endif

    // 1.标准日志内容
    char stdBuf[1024];
    // 1.1获取时间戳
    time_t timestamp = time(nullptr);
    if (timestamp == std::time_t(-1))
    {
        std::cerr << "获取时间失败" << std::endl;
        exit(1);
    }
    // 1.2获取格式化时间
    struct tm *CLK = std::localtime(&timestamp); // tm *localtime(const time_t *__timer)
    // 1.3将日志信息输出到日志文件
    //  snprintf(stdBuf, sizeof stdBuf, "[%s] [%ld] ", gLevelMap[level], timestamp);
    snprintf(stdBuf, sizeof stdBuf, "[%s] [%d/%d/%d %d:%d:%d ", gLevelMap[level],
             1900 + CLK->tm_year, 1 + CLK->tm_mon, CLK->tm_mday, CLK->tm_hour, CLK->tm_min, CLK->tm_sec);

    // 2.用户自定义内容
    va_list args;
    va_start(args, format);
    char logBuf[1024];
    // int vsnprintf(char *str, size_t size, const char *format, va_list ap);
    vsnprintf(logBuf, sizeof logBuf, format, args);
    va_end(args);

    //fprintf(stdout, "%s%s\n", stdBuf, logBuf);

    FILE *fp = fopen(LOGFILE, "a");
    fprintf(fp, "%s%s\n", stdBuf, logBuf);
    
    fclose(fp);
}

6.5Daemon.hpp

#pragma once

#include <iostream>
#include <cassert>
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

void MyDaemon()
{
    // 1. 忽略信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);

    // 2. 不是进程组-组长的进程才能成功调用setsid
    pid_t id = fork();
    assert(id >= 0);

    if (id > 0)
        exit(0);

    if (0 == id)
    {
        // 3. 调用setsid
        setsid();

        // 4. 重定向:守护进程不能直接向显示器打印消息
        int dev_null = open("/dev/null", O_RDONLY | O_WRONLY);
        if (dev_null > 0)
        {
            dup2(0, dev_null);
            dup2(1, dev_null);
            dup2(2, dev_null);
            close(dev_null);
        }
    }
}

6.6TcpServer.hpp

#pragma once

#include <vector>
#include <functional>
#include <pthread.h>
#include "Socket.hpp"

namespace Tcpserver
{
    using func_t = std::function<void(int)>;

    class TcpServer;
    class ThreadInfo
    {
    public:
        ThreadInfo(int socket, TcpServer *server)
            : _socket(socket),
              _ptrServer(server)
        {
        }

        ~ThreadInfo() {}

    public:
        int _socket;
        TcpServer *_ptrServer;
    };

    class TcpServer
    {
    private:
        static void *ThreadRoutine(void *args)
        {
            pthread_detach(pthread_self());

            ThreadInfo *threadinfo = static_cast<ThreadInfo *>(args);
            threadinfo->_ptrServer->Excute(threadinfo->_socket);
            
            close(threadinfo->_socket);
            delete threadinfo;
            
            return nullptr;
        }
    public:
        TcpServer(const uint16_t &port, const std::string &ip = "0.0.0.0")
        {
            _listenSocket = _socket.Socket();
            _socket.Bind(_listenSocket, port, ip);
            _socket.Listen(_listenSocket);
        }

        void Start()
        {
            while (true)
            {
                std::string clientIp;
                uint16_t clientPort;

                int serviceSocket = _socket.Accept(_listenSocket, &clientIp, &clientPort);
                if (serviceSocket == -1)
                    continue;

                logMsg(NORMAL, "create new link success, sock: %d", serviceSocket);
                pthread_t tid;
                ThreadInfo *threadinfo = new ThreadInfo(serviceSocket, this);
                pthread_create(&tid, nullptr, ThreadRoutine, threadinfo);
            }
        }

        void AddService(func_t func)
        {
            _func.push_back(func);
        }

        void Excute(int socket)
        {
            for (auto &fun : _func)
            {
                fun(socket);
            }
        }

        ~TcpServer()
        {
            if (_listenSocket >= 0)
                close(_listenSocket);
        }

    private:
        int _listenSocket;
        Sock _socket;
        std::vector<func_t> _func;
    };
}

6.7Client.cc

#include <iostream>
#include "Socket.hpp"
#include "Protocol.hpp"

using namespace Protocol;

static void usage(const std::string &process)
{
    std::cout << std::endl
              << "Usage: " << process << " serverIp serverPort" << std::endl
              << std::endl;
}

// ./client serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        usage(argv[0]);
        exit(1);
    }

    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    Sock sock;
    int socketFd = sock.Socket();
    if (!sock.Connect(socketFd, serverIp, serverPort))
    {
        std::cerr << "Connect error" << std::endl;
        exit(2);
    }

    bool quit = false;
    std::string buffer;
    // 客户端和服务端默认遵守协议:输入的就是"1 + 1"
    while (!quit)
    {
        // 1. 获取需求
        Request req;
        std::cout << "Please Enter # ";
        std::cin >> req._left >> req._op >> req._right;

        // 2. 序列化
        std::string str = req.Serialize();

        // 3. 添加长度报头
        str = Encode(str);

        // 4. 发送给服务端
        Send(socketFd, str);

        // 5. 读取结果
        while (true)
        {
            bool recvOk = Recv(socketFd, &buffer);
            if (!recvOk)
            {
                quit = true;
                break;
            }

            std::string package = Decode(buffer);
            if (package.empty())
                continue;

            Response resp;
            resp.Deserialize(package);
            std::string err;
            switch (resp._code)
            {
            case DIVZERO:
                err = "除0错误";
                break;
            case MODZERO:
                err = "模0错误";
                break;
            case ILLEGAL:
                err = "非法操作";
                break;
            default:
                // 该输出只在JSON协议下有用 因为自定义协议的response没有对输出信息做反序列化
                std::cout << resp._left << resp._op << resp._right << " = " << resp._result << " [success]" << std::endl;
                break;
            }
            if (!err.empty())
                std::cerr << err << std::endl;

            break;
        }
    }
    close(socketFd);
    return 0;
}

6.8Server.cc

#include "TcpServer.hpp"
#include "Protocol.hpp"
#include "Daemon.hpp"
#include <memory>
#include <signal.h>

using namespace Tcpserver;
using namespace Protocol;

static void usage(const std::string &process)
{
    std::cout << std::endl
              << "Usage: " << process << " port" << std::endl
              << std::endl;
}

static Response calculate(const Request &req)
{
    Response resp(0, 0, req._left, req._right, req._op);
    switch (req._op)
    {
    case '+':
        resp._result = req._left + req._right;
        break;
    case '-':
        resp._result = req._left - req._right;
        break;
    case '*':
        resp._result = req._left * req._right;
        break;
    case '/':
        if (0 == req._right)
            resp._code = DIVZERO;
        else
            resp._result = req._left / req._right;
        break;
    case '%':
        if (0 == req._right)
            resp._code = MODZERO;
        else
            resp._result = req._left % req._right;
        break;
    default:
        resp._code = ILLEGAL;
        break;
    }
    return resp;
}

void calculateService(int serviceSocket)
{
    std::string inbuffer;
    while (true)
    {
        // 1. 读取客户端发的信息
        bool recvOk = Recv(serviceSocket, &inbuffer);
        if (!recvOk)
            break;

        // 2. 协议解析 得到一个完整的报文
        std::string package = Decode(inbuffer);
        if (package.empty())
            continue;

        logMsg(NORMAL, "package: %s", package.c_str());

        // 3. 反序列化 字节流 -> 结构化
        Request req;
        req.Deserialize(package);

        // 5. 业务逻辑
        Response resp = calculate(req);

        // 6. 对计算结果进行序列化
        std::string respString = resp.Serialize(); 
        
        // 7. 添加长度信息 "length\r\ncode result\r\n"
        respString = Encode(respString);

        Send(serviceSocket, respString);
    }
}

// ./CalServer port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        exit(1);
    }

    // server的编写要有较为严谨的判断逻辑
    // 一般服务器都要忽略SIGPIPE信号 防止在运行中出现非法写入导致服务端终止的问题
    // signal(SIGPIPE, SIG_IGN);

    //MyDaemon();
    std::unique_ptr<TcpServer> server(new TcpServer(atoi(argv[1])));

    server->AddService(calculateService);
    server->Start();

    return 0;
}

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

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

相关文章

PBXAI:将疾病预测转为沿知识图谱的随机游走

PBXAI&#xff1a;将疾病预测转为沿知识图谱的随机游走 PBXAI 知识图谱构建 病人特征与知识图谱连接 强化学习 疾病发展路径的生成PBXAI 流程PBXAI 算法设计 论文&#xff1a; https://arxiv.org/ftp/arxiv/papers/2010/2010.08300.pdf 代码&#xff1a;https://github.co…

NLP_知识图谱_介绍、构建、问答知识

文章目录 知识图谱的介绍图图能做些什么任务基于图的推荐系统图的一些基本概念与表述有向图与无向图节点的度节点的边的数量有向图又分为入度和出度 什么是知识图谱知识图谱属于异质图知识图谱的schema知识图谱的应用场景 知识图谱的构建三元组如何构建知识图谱构建知识图谱通常…

基于springboot+vue+Mysql的简历系统

开发语言&#xff1a;Java框架&#xff1a;springbootJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包&#xff1a;…

C语言输出不同颜色的字体

本文章在Linux进行演示&#xff01;&#xff01;&#xff01; 使用C语言输出不同颜色字体和背景 格式&#xff1a; printf("\033[字体背景颜色;字体颜色m字符串\033[0m"); 上边的 \033 也可以用 \e 来代替。 字体颜色与字符的对应关系 字符颜色30黑色31红…

小白学视觉 | 各种各样神奇的自注意力机制(Self-attention) 建议收藏!

本文来源公众号“小白学视觉”&#xff0c;仅用于学术分享&#xff0c;侵权删&#xff0c;干货满满。 原文链接&#xff1a;收藏&#xff01;各种各样神奇的自注意力机制&#xff08;Self-attention&#xff09; 编者荐语 文章总结了关于李宏毅老师在 2022 年春季机器学习课…

MAC(M1芯片)编译Java项目慢且发热严重问题解决方案

目录 一、背景二、排查三、解决四、效果以及结果展示五、总结 一、背景 使用idea编译项目等操作&#xff0c;经常性发热严重&#xff0c;并且时间慢。直到昨天编译一个项目用时30分钟&#xff0c;电脑温度很高&#xff0c;并且有烧灼的味道&#xff0c;于是有了此篇文章。 二、…

关于Jar包提示找不到主类 找不到或无法加载主类

关于Jar包提示找不到主类 找不到或无法加载主类 当时看到教程打包一个正常的小型增删改查为50MB&#xff0c;也就是几十MB&#xff0c;可我打包得到的Jar包只有几MB&#xff0c;一直提示找不到主类application。经检查 根据方法&#xff0c;只需使用mvn clean 和 mvn packa…

大米自动化生产线设备:现代粮食加工的核心力量

随着科技的不断进步和粮食加工行业的快速发展&#xff0c;大米自动化生产线设备在现代粮食加工中的地位愈发重要。这些设备不仅大大提高了生产效率&#xff0c;还保证了产品的质量和安全&#xff0c;成为了现代粮食加工行业不可或缺的核心力量。 一、自动化生产线设备助力效率提…

厂房起火3D消防灭火安全救援模拟演练

深圳VR公司华锐视点依托前沿的VR虚拟现实制作、三维仿真和图形图像渲染技术&#xff0c;将参训者带入栩栩如生的火灾现场。佩戴VR头盔&#xff0c;参训者将真切体验火势蔓延的紧张与危机&#xff0c;身临其境地感受火灾的恐怖。 并且消防安全VR虚拟现实演练系统精心模拟了住宅、…

基于单链表实现通讯管理系统!(有完整源码!)

​ 个人主页&#xff1a;秋风起&#xff0c;再归来~ 文章专栏&#xff1a;C语言实战项目 个人格言&#xff1a;悟已往之不谏&#xff0c;知来者犹可追 克心守己&#xff0c;律己则安&#xff01; 1、前言 友友们&#xff0c;这篇文章是基于单链…

005Node.js模块URL的使用

引入 URL 模块 要使用 URL 模块&#xff0c;首先需要在代码中引入它。可以使用以下代码将 URL 模块导入到你的脚本中&#xff1a; const url require(url);实例代码 const urlrequire(url); var apihttp://www.baidu.com?nameshixiaobin&age20; console.log(url.parse(…

Android应用和开发环境

&#x1f308;个人主页&#xff1a;小新_- &#x1f388;个人座右铭&#xff1a;“成功者不是从不失败的人&#xff0c;而是从不放弃的人&#xff01;”&#x1f388; &#x1f381;欢迎各位→点赞&#x1f44d; 收藏⭐️ 留言&#x1f4dd; &#x1f3c6;所属专栏&#xff1…

70 个常用的GIS Python 库

由于其多功能性、广泛的库生态系统和用户友好的语法&#xff0c;Python 已成为地理信息系统 (GIS) 和遥感领域的主导语言。这个 70 个地理空间 Python 库的汇编展示了可用于 GIS 和遥感数据处理和分析的丰富工具包。 Python 在 GIS 中的重要性源于它处理复杂地理空间数据的能力…

rancher踩坑日志:prometheus访问kubelet 10250端口提示鉴权失败

该原因是因为kubectl禁止了非授权用户访问10250端口来获取node的数据。 解决思路&#xff1a; 添加prometheus访问kubelet时带上证书进行验证匹配 --> 由于我的prometheus是rancher安装的&#xff0c;不知道要怎么修改所以研究了一会没研究明白就放弃了。设置prometheus访问…

运动听歌哪款耳机靠谱?精选五款热门开放式耳机

随着人们对运动健康的重视&#xff0c;越来越多的运动爱好者开始关注如何在运动中享受音乐。开放式蓝牙耳机凭借其独特的设计&#xff0c;成为了户外运动的理想选择。它不仅让你在运动时能够清晰听到周围环境的声音&#xff0c;保持警觉&#xff0c;还能让你在需要时与他人轻松…

gym界面修改

资料&#xff1a;https://blog.csdn.net/weixin_46178278/article/details/135962782 在gym环境中使用mujoco的时候&#xff0c;有一个很难受的地方&#xff0c;界面上没有实时显示动作空间和状态空间状态的地方。 gym自己原始带的环境是用pygame画的图&#xff0c;所以在定义…

【爬虫+数据清洗+可视化分析】Python文本分析《狂飙》电视剧的哔哩哔哩评论

一、背景介绍 把《狂飙》换成其他影视剧&#xff0c;套用代码即可得分析结论&#xff01; 2023《狂飙》热播剧引发全民追剧&#xff0c;不仅全员演技在线&#xff0c;且符合主旋律&#xff0c;创下多个收视记录&#xff01; 基于此热门事件&#xff0c;我用python抓取了B站上千…

【SpringBoot】获取参数

获取参数 传递单个参数传递多个参数传递对象后端参数重命名传递数组传递 json 数据获取 URL 中参数上传文件获取 cookie 和 session获取cookie获取session 传递单个参数 RequestMapping("/user") RestController public class UserController {// 传递单个参数Reque…

力扣 | 160. 相交链表

import ListNodeInfo.ListNode;import java.util.HashSet; import java.util.Set;public class Problem_160_IntersectionOfTwoLinkedList {//双指针方法 public ListNode getIntersectionListNode(ListNode headA, ListNode headB){if(headA null || headB null) return nul…

S32K324 CANFD报文接收超限分析

文章目录 前言问题描述原因分析问题处理总结 前言 随着汽车软件复杂度越来越高&#xff0c;传输的数据越来越多&#xff0c;CAN总线到CANFD总线已经是发展的必然了。CANFD总线中单个报文ID可以传递至多64byte数据&#xff0c;对CAN Driver来说&#xff0c;所需的MCU资源也将变…