「网络编程」第二讲:socket套接字(四 - 完结)_ Linux任务管理与守护进程 | TCP协议通讯流程

news2024/11/18 1:30:38

「前言」文章是关于网络编程的socket套接字方面的,上一篇是网络编程socket套接字(三),这篇续上篇文章的内容,下面开始讲解! 

「归属专栏」网络编程

「笔者」枫叶先生(fy)

「座右铭」前行路上修真我

「枫叶先生有点文青病」「句子分享」

Time goes on and on, never to an end but crossings.
时间一直走,没有尽头,只有路口。 
 ——克莱尔·麦克福《摆渡人》

目录

五、增加日志功能

六、Linux任务管理与守护进程

6.1 任务管理

6.1.1 进程组

6.1.2 作业概念

6.1.3 会话概念

6.1.4 操作

6.2 守护进程

6.2.1 概念

6.2.2 查看

6.2.3 创建守护进程

七、TCP协议通讯流程

7.1 三次握手

7.2 数据传输

7.3 四次挥手

7.4 TCP 和 UDP 对比


五、增加日志功能

文章续上篇文章的内容,给服务器增加日志功能,即把打印到显示台的内容,分等级打印到不同等级的文件里面

日志分为五个等级

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

 其中DEBUG、NORMAL、WARNING归类到一个文件里面,剩下的ERROR、FATAL归类到另一个文件里面

日志代码如下 

log.hpp 

#pragma once

#include <iostream>
#include <stdarg.h>
#include <ctime>
#include <unistd.h>

using namespace std;

#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"

#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4

const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "WARNING";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    default:
        return nullptr;
    }
}

void logMessage(int level, const char *format, ...)
{
    // [日志等级] [时间] [pid] [message]

#define NUM 1024

    time_t now = time(nullptr);      // 获取当前时间
    tm *localTime = localtime(&now); // 将时间转换为结构体
    char timeStr[NUM];
    strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S", localTime); // 格式化时间为指定格式
    char logprefix[NUM];
    // 使用格式化后的时间字符串组装日志前缀
    snprintf(logprefix, sizeof(logprefix), "[%s][%s][pid: %d]",
             to_levelstr(level), timeStr, getpid());

    char logcontent[NUM];
    va_list arg;
    va_start(arg, format);
    vsnprintf(logcontent, sizeof(logcontent), format, arg);

    FILE *log = fopen(LOG_NORMAL, "a");
    FILE *err = fopen(LOG_ERR, "a");
    if (log != nullptr && err != nullptr)
    {
        FILE *curr = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            curr = log;
        if (level == ERROR || level == FATAL)
            curr = err;
        if (curr)
            fprintf(curr, "%s%s\n", logprefix, logcontent);

        fclose(log);
        fclose(err);
    }
}

 其他代码就不贴了,上传到Gitee了

Gitee:code_linux/code_202306_16/2_tcp/4_tcpthpool · Maple_fylqh/code - 码云 - 开源中国 (gitee.com)

测试结果,服务器运行,就已经把日志打印到文件里面了

六、Linux任务管理与守护进程

关闭shell之后,我们运行的进程也跟着销毁了。即我们运行的服务器也随之销毁,这显然是不合理了,所以这并不是服务器真正运行的样子。所以,下面要解决的就是这个问题,顺便介绍Linux任务管理与守护进程

关闭第一个shell,该进程的信息已经查不到了,说明该进程已经销毁了

  

6.1 任务管理

6.1.1 进程组

每个进程除了有一个进程ID之外,还属于一个进程组,进程组是一个或多个进程的集合。

进程组(Process Group)是一组具有相同进程组ID(PGID)的进程的集合。每个进程组都有一个唯一的PGID,用于标识进程组。

进程组的主要作用是将一组相关的进程组织在一起,以便可以对它们进行集体操作。例如,可以向进程组发送信号,以便同时影响组内的所有进程。进程组还可以用于实现作业控制,其中一个进程组被分配为前台作业,其他进程组被分配为后台作业。 

在Linux系统中,进程组的ID是由内核分配的,进程组的ID范围为正整数。进程组的ID为0的特殊进程组被称为“无效进程组”,用于标识没有有效进程组的进程。

需要注意的是,只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。

例如,这里的 PGID 就是进程组

6.1.2 作业概念

在Linux中,作业(Job)是指在终端或终端仿真器中运行的一个或多个命令的集合

作业分前台作业和后台作业:

  • 前台作业(Foreground Job):在终端中直接运行的命令或程序,默认情况下,前台作业会占用终端的控制权,并且会将输出直接显示在终端上
  • 后台作业(Background Job):在命令的末尾添加&符号,可以将命令放到后台运行,不会占用终端的控制权,并且会将输出重定向到一个文件或/dev/null

注:一个前台作业可以由多个进程或线程组成,一个后台作业也可以由多个进程或线程组成

前台任务只能有一个,后台任务可以有多个或者没有 

默认情况下,我们登录 Xshell后,bash会默认占据前台任务也就是命令行解释器shell(即占用终端的控制权)

比如,我们随便运行一个不会退出的程序,比如上面的服务端程序

该进程任务自动切换为前台任务,shell自动切换为后台任务,我们输入的命令就无效了

Linux提供了一些作业控制命令来管理和控制作业:

  • jobs:查看当前终端中运行的作业列表。
  • fg:将后台作业切换到前台运行。
  • bg:将后台作业切换到后台继续运行。
  • kill:终止指定作业的运行。

作业与进程组的区别:

如果作业中的某个进程又创建了子进程,则子进程不属于作业。一旦作业运行结束,Shell就把自己提到前台,如果原来的前台进程还存在,也就是这个被创建的子进程还没有终止,那么它将自动变为后台进程组

6.1.3 会话概念

在Linux中,会话(Session)是指从用户登录到系统开始,到用户退出系统结束的整个时间段。

也就是说一个用户进行登录Linux,Linux系统就会分配一个会话给我们,直到我们主动退出这个会话。在一个会话中,用户可以与系统进行交互,执行命令、操作文件、启动程序等

6.1.4 操作

先创建几个进程组,为了方便直接用 sleep 代替应用程序 

在命令的末尾添加&符号,可以将命令放到后台运行,不会占用终端的控制权,此时命令行依旧生效

  

 查看一下进程信息,进程组的PGID相同代表的是在同一个进程组

使用 jobs:查看当前终端中运行的作业列表,例如

其中,前面的序号就是任务编号,用于辨别多个任务

  

使用 fg 命令可以将后台作业切换到前台运行,后面带上作业的编号

由于1号作业被提至前台运行,所以其运行状态也由S变成了S+,+ 就是代表是前台任务

注意:前台进程只能有一个,当一个进程变成前台进程后,bash会自动变为后台进程,此时bash就无法进行命令行解释了 

将一个前台进程放到后台运行可以使用Ctrl+Z,但使用Ctrl+Z后该进程就会处于停止状态(Stopped)

bg:将后台作业切换到后台继续运行。 可以让某个停止的作业在后台继续运行(Running)

6.2 守护进程

6.2.1 概念

在Linux系统中,守护进程(Daemon Process)是在后台运行的一种特殊类型的进程。它与用户交互的终端分离,通常在系统启动时自动启动,并在系统运行期间持续运行,直到系统关闭或手动停止。守护进程也称精灵进程,本质是孤儿进程的一种

 守护进程通常用于执行一些需要持续运行的任务,比如网络服务、系统监控、定时任务等。与普通进程不同,守护进程没有终端与之关联,也没有用户交互。它们在后台默默地运行,执行系统任务,并通过日志文件记录运行状态和输出信息。

6.2.2 查看

可以用 ps axj命令查看系统中的进程:

  • 参数a表示不仅列出当前用户的进程,也列出所有其他用户的进程。
  • 参数x表示不仅列出有控制终端的进程,也列出所有无控制终端的进程。
  • 参数j表示列出与作业控制相关的信息

TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程

6.2.3 创建守护进程

创建守护进程一般不喜欢使用系统提供的,因为有未定义行为,一般都是自己写

daemon函数是系统提供的

创建守护进程的过程可以分为以下几个步骤:

  1. 让调用进程忽略掉异常的信号

  2. 创建子进程:使用fork()系统调用创建一个子进程。

  3. 脱离终端(核心):使用setsid()系统调用使子进程脱离终端,成为一个新的会话组长。

  4. 关闭文件描述符:关闭所有文件描述符,以防止守护进程与终端或其他进程的关联。可以使用close()系统调用来关闭文件描述符。

  5. 重定向标准输入输出、错误:将标准输入、输出和错误重定向到/dev/null或日志文件中。可以使用dup2()系统调用来重定向文件描述符。

  6. 设置工作目录(可选):将工作目录切换到根目录,以防止守护进程运行时影响其他目录。可以使用chdir()系统调用来切换工作目录。

代码如下:

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

#define DEV "/dev/null"

void daemonSelf(const char *currPath = nullptr)
{
    // 1. 让调用进程忽略掉异常的信号
    signal(SIGPIPE, SIG_IGN);

    // 2. 创建子进程
    if (fork() > 0)
        exit(0);
    // 子进程 -- 守护进程,精灵进程,本质就是孤儿进程的一种!
    // 3.脱离终端:使用setsid()系统调用使子进程脱离终端,成为一个新的会话组长。
    pid_t n = setsid();
    assert(n != -1);

    // 4. 关闭文件描述符 或 重定向标准输入输出、错误
    int fd = open(DEV, O_RDWR);
    if (fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);

        close(fd);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }

    // 5. 可选:进程执行路径发生更改

    if (currPath)
        chdir(currPath);
}

/dev/null

/dev/null是Linux操作系统中的一个特殊文件,它会丢弃所有写入它的数据,并在从中读取时返回文件结束条件。它通常被用作丢弃不需要的输出或测试程序在遇到写入错误时的行为。/dev/null 形象称为黑洞,或文件黑洞

setsid函数

 

creates a session and sets the process group ID:创建会话并设置进程组ID 

返回值:函数调用成功后,将返回调用进程的(新)会话ID。出现错误时,返回(pid_t)-1,错误码被设置

如果调用进程不是进程组的组织,setsid()将创建一个新会话

如何让自己不是进程组组长??

创建子进程:使用fork()系统调用创建一个子进程,让父进程直接退出

注意:创建子进程成立新会话后,子进程自己就成了进程组,与终端设备无关

测试

给服务端加上该代码,进行测试

编译运行服务端

 

查看进程信息

发现该进程的TPGID为-1,代表的是守护进程,TTY显示的是,也就意味着该进程已经与终端去关联了 

PPID为1,说明OS领养了守护进程,守护进程本质是孤儿进程的一种

现在把自己的终端关掉,重新连接,该进程依旧可以查到,说明进程已经守护进程化了,这就是服务器正确的运行方式

七、TCP协议通讯流程

TCP协议通讯流程这里只是浅谈,后序再详谈,这里只有简单认识。

TCP协议的客户端/服务器程序的一般流程: 

 

7.1 三次握手

三次握手就是客户端向服务端发起连接的过程(简单了解,后序详谈)

 

 服务器初始化

  • 调用socket, 创建文件描述符;
  • 调用bind, 将当前的文件描述符和ip/port绑定在一起; 如果这个端口已经被其他进程占用了, 就会bind失败;
  • 调用listen, 声明当前这个文件描述符作为一个服务器的文件描述符, 为后面的accept做好准备;
  • 调用accecpt, 并阻塞, 等待客户端连接过来

 建立连接的过程

  • 调用socket, 创建文件描述符;
  • 调用connect, 向服务器发起连接请求;
  • connect会发出SYN段并阻塞等待服务器应答; (第一次)
  • 服务器收到客户端的SYN, 会应答一个SYN-ACK段表示"同意建立连接"; (第二次)
  • 客户端收到SYN-ACK后会从connect()返回, 同时应答一个ACK段; (第三次) 

这个建立连接的过程, 通常称为 三次握手

7.2 数据传输​​​​​​​

双方建立好连接之后就可以进行数据传输了

 数据传输的过程

  •  建立连接后,TCP协议提供全双工的通信服务; 所谓全双工的意思是, 在同一条连接中, 同一时刻, 通信双方
  • 可以同时写数据; 相对的概念叫做半双工, 同一条连接在同一时刻, 只能由一方来写数据;
  • 服务器从accept()返回后立刻调 用read(), 读socket就像读管道一样, 如果没有数据到达就阻塞等待;
  • 这时客户端调用write()发送请求给服务器, 服务器收到后从read()返回,对客户端的请求进行处理, 在此期间客户端调用read()阻塞等待服务器的应答;
  • 服务器调用write()将处理结果发回给客户端, 再次调用read()阻塞等待下一条请求;
  • 客户端收到后从read()返回, 发送下一条请求,如此循环下去

注意:用 read 读取数据是有问题的,你不能保证数据读取完了,或者数据只读取了一部分,又或者数据没有及时读取,这些问题后序再谈,这就是为什么说 TCP是面向字节流。

7.3 四次挥手

如果不想通信了,双方就要断开连接

断开连接的过程 

  • 如果客户端没有更多的请求了, 就调用close()关闭连接, 客户端会向服务器发送FIN段(第一次);
  • 此时服务器收到FIN后, 会回应一个ACK, 同时read会返回0 (第二次);
  • read返回之后, 服务器就知道客户端关闭了连接, 也调用close关闭连接, 这个时候服务器会向客户端发送一个FIN; (第三次)
  • 客户端收到FIN, 再返回一个ACK给服务器; (第四次) 

这个断开连接的过程, 通常称为 四次挥手

在学习socket API时要注意应用程序和TCP协议层是如何交互的 

  • 应用程序调用某个socket函数时TCP协议层完成什么动作,比如调用connect()会发出SYN段
  • 应用程序如何知道TCP协议层的状态变化,比如从某个阻塞的socket函数返回就表明TCP协议收到了某些段,再比如read()返回0就表明收到了FIN段 

注:以上概念先了解,后序再谈

7.4 TCP 和 UDP 对比

  • 可靠传输 vs 不可靠传输
  • 有连接 vs 无连接
  • 字节流 vs 数据报

到目前为止,我们通过代码知道 TCP是有连接 和 UDP是无连接,而可靠和不可靠传输、面向字节流和面向数据报暂时体会不到,后序谈原理的时候就可以理解了

--------------------- END ----------------------

「 作者 」 枫叶先生
「 更新 」 2023.6.23
「 声明 」 余之才疏学浅,故所撰文疏漏难免,
          或有谬误或不准确之处,敬请读者批评指正。

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

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

相关文章

linux之用户和用户组

在此之前我们需要先了解用户和用户组的区别 用户是我们可以登录的账号&#xff0c;而用户组是用户的小组&#xff0c;组也可以分为主组和附属组&#xff0c;主组是用户的主要组&#xff0c;附属而是用户的附加组 目录 1.添加新用户账号 2.用户口令的管理 3.用户组命令 1.添加…

被测系统架构与数据流分析

开源项目litemall系统架构(https://github.com/linlinjava/litemall) 角色与数据用户产品前端技术栈后端技术栈数据存储 开源项目Mall的系统架构(https://github.com/macrozheng/mall) 角色与数据用户产品前端技术栈后端技术栈服务治理技术栈监控技术栈大数据处理技术栈数据存…

校园视频AI分析预警系统 TesnorFlow

校园视频AI分析预警系统通过分布式TensorFlow模型训练&#xff0c;校园视频AI分析预警系统对学生的行为进行实时监测&#xff0c;当系统检测到学生出现打架、翻墙、倒地、抽烟等异常行为时&#xff0c;校园视频AI分析预警系统将自动发出警报提示相关人员及时采取措施。深度学习…

Elasticsearch 和数据架构:改进分析和存储的 4 个基本工具

作者&#xff1a;Emily McAlister 组织越来越依赖数据来做出有效的、基于证据的决策来推动业务成果。 无论是评估市场状况和改善客户体验、确保应用程序正常运行时间还是保护组织安全&#xff0c;来自多个来源&#xff08;包括消费者和内部系统&#xff09;的数据对于日常运营都…

开源URL短链接服务Shlink

最近一直有人在问&#xff0c;docker 查询注册表失败的问题&#xff0c;目前老苏验证可用的方法有 2 个&#xff1a; 方法一&#xff08;可用&#xff09;&#xff1a; 看过老苏以前文章的读者都知道&#xff0c;在遇到发布到 ghcr.io 的镜像时&#xff0c;老苏都会推荐用 do…

MacOS Ventura 13.4.1 (22F82) 带 OC 引导双分区黑苹果镜像

苹果今日向 Mac 电脑用户推送了 macOS 13.4.1 更新&#xff08;内部版本号&#xff1a;22F82&#xff09;&#xff0c;根据Apple的发布说明&#xff0c;该更新提供了重要的安全修复&#xff0c;并建议所有用户进行更新。Apple还为无法运行Ventura的用户发布了macOS 11.7.8和mac…

Git进阶系列 | 5. Rebase vs Merge

Git是最流行的代码版本控制系统&#xff0c;这一系列文章介绍了一些Git的高阶使用方式&#xff0c;从而帮助我们可以更好的利用Git的能力。本系列一共8篇文章&#xff0c;这是第5篇。原文&#xff1a;Rebase vs. Merge: Integrating Changes in Git[1] 大多数开发人员都理解在G…

【五子棋实战】第4章 部署五子棋计算接口到Window、Linux上

【五子棋实战】第4章 部署五子棋计算接口到Window、Linux上 python项目打包成exe可执行文件 ## 步骤一、安装pyinsatller ## 步骤二、使用pyinstaller打包Python程序 ## 操作演示 ## 注意事项&#xff01;&#xff01; python的Flask接口部署&#xff08;Linux&#xff09; ##…

二叉树-理论基础

文章目录 前言一、二叉搜索树平衡二叉搜索树 二、二叉树的存储方式二叉树的遍历方式二叉树的定义总结 前言 二叉树有两种主要的形式&#xff1a;满二叉树和完全二叉树。满二叉树&#xff1a;如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上…

16.vant Weapp

目录 1 使用npm 2 安装 vant 3 构建npm 4 去除 style:v2 5 使用 vant 6 样式变量 1 使用npm 微信小程序不支持下面三种包 不支持依赖 Node.js 内置库的包不支持依赖 浏览器内置对象 的包不支持依赖 C插件 的包 除去上面三种&#xff0c;能用的包就不多了&#…

面向AI的新编程范式

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 卷圈 运营 / SandLiu 卷圈 监制 / 姝琦 联合制作 / 声网 产品统筹 / bobo 录音间 / 声湃轩北京站 在这期播客节目中&#xff0c;我们将探讨AI与程序员如何共同进步。随着AI热潮席卷全球&#xff0c;许多程序员尝试着使…

python爬虫_python基础数据类型

文章目录 ⭐前言⭐python&#x1f496; Number&#x1f496; String&#x1f496; List&#x1f496; Tuple&#x1f496; Dict ⭐结束 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享关于python的基础数据类型&#xff0c;作为python爬虫专栏的基石。 ⭐python 发…

【Note8】网络管理

文章目录 1.MII介绍2.BMC MAC3.MDIO&MDC4. 1.MII介绍 SOC内部没有网络MAC外设&#xff1a;缺&#xff1a;网络效率不高&#xff0c;一般芯片内置的MAC会网络加速引擎&#xff0c;如网络专用DMA&#xff0c;网络处理效率会很高。 SOC内部集成网络MAC外设&#xff1a;MII/RM…

【MySQL】不就是子查询

前言 今天我们来学习多表查询的下一个模块——子查询&#xff0c;子查询包括了标量子查询、列子查询、行子查询、表子查询&#xff0c;话不多说我们开始学习。 目录 前言 目录 一、子查询 1. 子查询的概念 2. 子查询语法格式 2.1 根据子查询结果不同可以分为&#xff1a;…

C++——内联函数

目录 1. 概念 2.特性 3. 经典面试题 1. 概念 以inline修饰的函数叫做内联函数&#xff0c;编译时C编译器会在调用内联函数的地方展开&#xff0c;没有函数压栈的开销&#xff0c;内联函数提升程序运行的效率。 以Add函数为例&#xff1a; int Add(int x, int y) {int z x…

RabbitMQ基础与实操复习

RabbitMQ基础复习 1、MQ引言1.1 什么是MQ1.2 MQ有哪些1.3 不同MQ特点 2、RabbitMQ引言2.1 RabbitMQ2.2 RabbitMQ安装 3、RabbitMQ配置3.1 RabbitMQ命令行3.2 Web管理界面3.2.1 overview概览3.2.2 Admin用户和虚拟主机管理 4、RabbitMQ常用消息模型测试4.1 RabbitMQ支持的消息模…

RabbitMQ应用场景和集群搭建复习

RabbitMQ应用场景和集群搭建 1. MQ的应用场景1.1 异步处理1.2 应用解耦1.3 流量削峰 2、RabbitMQ集群搭建2.1 普通集群(副本集群)2.1.1 架构图2.1.2 集群搭建1、集群规划&#xff1a;这里用三台虚拟机测试2、克隆三台机器主机名和ip映射3、 在其他两台节点上安装rabbitmq4、后台…

Neurophotonics | HyperOptoNet:用于fNIRS超扫描脑间神经同步分析的MATLAB工具箱

导读 意义&#xff1a;本研究开发了一个基于MATLAB的工具箱&#xff0c;用于脑间同步(IBS)分析&#xff0c;并进行了实验研究以验证其性能。据所知&#xff0c;这是第一个基于功能近红外光谱(fNIRS)超扫描数据的IBS工具箱&#xff0c;可在两个三维(3D)头部模型上直观地显示结果…

Elasticsearch:analyzer

前奏 es的chinese、english、standard等分词器对中文分词十分不友好&#xff0c;几乎都是逐字分词&#xff0c;对英文分词比较友好。 在kibana的dev tools中测试分词&#xff1a; POST /_analyze {"analyzer": "standard","text": "你太…

用OpenCV进行透视变换

1. 引言 欢迎回来&#xff01;今天我们将焦点聚焦在我在图像处理中最喜欢的话题之一——透视变换。使用该技术&#xff0c;可以灵活方便的实现各种各样好玩的特效。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. 单应矩阵 我们首先展开对单应矩阵的深入研究。作为图…