Linux - 网络套接字

一、网络编程

1)地址结构

1. IP地址结构
  • struct in_addr:是用于表示 IPv4 地址 的结构体,定义在头文件 <netinet/in.h> 中。它的主要作用是存储一个 32 位的 IPv4 地址,通常与 struct sockaddr_in 一起使用。

    struct in_addr {uint32_t s_addr; // IP 地址,使用网络字节序存储
    };
    
    • s_addr
      • 类型:uint32_t(32 位无符号整数)。
      • 用于存储 IPv4 地址,以 网络字节序(大端序)表示。
      • 可以通过函数(如 inet_pton()inet_addr())将人类可读的字符串形式的 IP 地址(如 "192.168.1.1")转换为 s_addr
2. 套接字地址
  • struct sockaddr_in:是一个用于描述 IPv4 套接字地址的结构体,常用于网络编程中与套接字函数(如 bind()connect() 等)交互。它是 struct sockaddr 的具体实现之一,专用于 IPv4 协议族

    struct sockaddr_in {sa_family_t    sin_family;   // 地址族(Address Family),AF_INET 表示 IPv4in_port_t      sin_port;     // 16位的端口号,网络字节序struct in_addr sin_addr;     // IP 地址(网络字节序)unsigned char  sin_zero[8];  // 填充字段,保持与 struct sockaddr 的大小一致
    };
    
    • sin_family

      • 描述地址族(protocol family)。

      • 对于 IPv4 地址,sin_family 的值为 AF_INET

    • sin_port

      • 用于存储端口号(16 位)。

      • 必须是 网络字节序,可以通过 htons() 函数将主机字节序转换为网络字节序。

    • sin_addr

      • 表示 IPv4 地址。

      • 类型为 struct in_addr,实际上是一个 32 位整数。

      • 常用的值:

        • INADDR_ANY:绑定到本地所有可用接口。
        • INADDR_LOOPBACK:表示回环地址(127.0.0.1)。
    • sin_zero

      • 保留字段,不做实际使用。

      • 用于确保 struct sockaddr_in 的大小与 struct sockaddr 相同。

      • 通常用 memset() 将其置为 0。

2)地址转换

1.字符串转In_addr
  • inet_addr():是一个用于将 点分十进制的 IPv4 地址字符串 转换为 32 位网络字节序的整数in_addr_t 类型)的函数。

    in_addr_t inet_addr(const char *cp)

    in_addr_t addr = inet_addr("192.168.1.1");
    
    • cp:指向一个点分十进制表示的 IPv4 地址字符串(如 "192.168.1.1")。

    • 返回值:

      • 成功:返回转换后的 32 位网络字节序的 IPv4 地址(in_addr_t 类型)。

      • 失败:如果 cp 是无效的 IPv4 地址字符串,返回 INADDR_NONE(通常是 0xFFFFFFFF)。

  • inet_aton():将点分十进制格式的 IPv4 地址转换为网络字节顺序的二进制形式。

    int inet_aton(const char *cp, struct in_addr *inp)

    struct in_addr addr;
    inet_aton("192.168.1.1", &addr)
    
    • cp:指向以字符串形式表示的 IPv4 地址(如 "192.168.1.1")。

    • inp:指向 struct in_addr 的指针,struct in_addr 是一个包含 IPv4 地址的结构体。

    • 返回值:如果转换成功,返回 1;如果失败,返回 0。

  • inet_pton():处理 IPv4 和 IPv6 地址,将它们从点分十进制或标准文本形式转换为网络字节顺序的二进制表示。

    int inet_pton(int af, const char *src, void *dst)

    struct in_addr addr;
    inet_pton(AF_INET, "192.168.1.1", &addr)
    
    • af:地址族,通常是 AF_INET(IPv4)或 AF_INET6(IPv6)。
    • src:指向源 IP 地址的字符串(如 "192.168.1.1""::1")。
    • dst:指向目标内存区域的指针,用来存储转换后的二进制地址。对于 IPv4 是 struct in_addr,对于 IPv6 是 struct in6_addr
    • 返回值:成功时返回 1,失败时返回 0,传入无效地址族时返回 -1。
函数支持的协议输入格式返回类型错误返回值
inet_atonIPv4点分十进制 IP 地址int0(失败)
inet_addrIPv4点分十进制 IP 地址in_addr_tINADDR_NONE(失败)
inet_ptonIPv4、IPv6点分十进制或标准文本二进制地址0(失败),-1(错误)
2. In_addr转字符串
  • inet_ntoa():将网络字节顺序的 IPv4 地址(即 in_addr 类型)转换为点分十进制的字符串格式(例如:“192.168.1.1”)。

    char *inet_ntoa(struct in_addr in)

    struct in_addr addr;
    addr.s_addr = htonl(0xC0A80101); // 192.168.1.1
    char *ip = inet_ntoa(addr);
    printf("IP Address: %s\n", ip); // 输出 "192.168.1.1"
    
    • in:类型为 struct in_addr,它包含了一个网络字节顺序的 IPv4 地址。

    • **返回值:**返回一个指向字符数组的指针,字符数组中是点分十进制格式的字符串表示。需要注意的是,这个返回值是一个静态缓冲区的指针,因此在多线程环境中使用时需要小心。

  • inet_ntop():一个更通用的函数,支持同时处理 IPv4 和 IPv6 地址,并将它们转换为点分十进制或标准文本表示的字符串格式。

    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)

    struct in_addr addr;
    addr.s_addr = htonl(0xC0A80101); // 192.168.1.1
    char str[INET_ADDRSTRLEN];
    if (inet_ntop(AF_INET, &addr, str, sizeof(str))) {printf("IP Address: %s\n", str); // 输出 "192.168.1.1"
    }
    
    • af:地址族,指定地址的类型,通常为 AF_INET(IPv4)或 AF_INET6(IPv6)。
    • src:指向源 IP 地址的指针,它是以网络字节顺序存储的二进制地址。对于 IPv4 是 struct in_addr,对于 IPv6 是 struct in6_addr
    • dst:指向缓冲区的指针,存储转换后的字符串。
    • sizedst 缓冲区的大小,确保它足够容纳转换后的字符串(IPv4 地址需要 16 字节,IPv6 地址需要 46 字节)。
    • 返回值:成功时,返回指向目标缓冲区的指针;如果失败,返回 NULL
2. 字节序转换
  • htons():是一个用于将主机字节序(Host Byte Order)的 16 位整数 转换为网络字节序(Network Byte Order)的函数。

    uint16_t htons(uint16_t hostshort)

    • hostshort:要转换的 16 位主机字节序整数(一般是端口号)。

    • 返回值:返回 转换为网络字节序的 16 位整数

3)套接字接口

1. 创建套接字
  • int socket():是在网络编程中创建套接字(socket)的一种方式。具体来说,socket 是一个系统调用函数,用于创建一个新的套接字,用于在应用程序和操作系统的网络协议栈之间进行数据传输。

    int socket(int domain, int type, int protocol)

    // 创建一个流式套接字(使用 IPv4 协议)
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
    • domain(协议族):

      • AF_INET:IPv4 地址族。

      • AF_INET6:IPv6 地址族。

      • AF_UNIX:本地通信的 Unix 域套接字(用于进程间通信)。

    • type(套接字类型):

      • SOCK_STREAM:流式套接字,通常用于 TCP 协议,提供面向连接的可靠通信。

      • SOCK_DGRAM:数据报套接字,通常用于 UDP 协议,提供无连接的非可靠通信。

    • protocol(协议):

      • 一般情况下为 0,让操作系统自动选择与给定协议族和类型匹配的默认协议。对于 SOCK_STREAM,默认选择的是 TCP;对于 SOCK_DGRAM,默认选择的是 UDP

      • 如果指定具体的协议,则需要使用对应协议的编号,例如,IPPROTO_TCPIPPROTO_UDP

    • 返回值:

      • 成功时,socket() 返回一个整数值,表示新创建的套接字的文件描述符。

      • 失败时,返回 -1,并且可以通过 errno 获取错误码。

2. 绑定套接字
  • bind():是一个系统调用函数,用于将套接字(socket)与本地地址(如 IP 地址和端口号)进行绑定。在服务器端,通常会使用 bind() 来指定监听端口和 IP 地址,以便接收来自客户端的连接请求。

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    string ip = "192.168.0.1";
    struct sockaddr_in local;
    bzero(&local, sizeof(local));
    local.sin_family = AF_INET;
    local.sin_port = htons(port_);
    local.sin_addr.s_addr = inet_addr(ip.c_str())
    int n = bind(sockfd, (const struct sockaddr*)&local, sizeof(local));
    
    • sockfd:传入的套接字文件描述符,通常由 socket() 创建。

    • addr:一个指向 struct sockaddr 结构体的指针,表示要绑定的本地地址。具体的结构体类型(如 struct sockaddr_instruct sockaddr_in6)取决于协议族(如 IPv4 或 IPv6)。

    • addrlenaddr 结构体的长度。通常使用 sizeof(struct sockaddr_in) 来获取。

    • 返回值:

      • 成功时,返回 0

      • 失败时,返回 -1,并设置 errno,可以通过 perror()strerror() 来查看错误信息。

3. 监听
  • listen():用于在服务器端创建一个侦听套接字(listening socket),它让套接字处于等待客户端连接的状态。通常在网络编程中,服务器会使用 listen 来监听特定端口上的连接请求。

    int listen(int sockfd, int backlog)

     // 进入监听状态,最多 3 个连接请求排队
    int server_fd;
    listen(server_fd, 3)
    
    • sockfd:要进行监听的套接字描述符,它通常由 socket() 创建,类型是 SOCK_STREAM(TCP协议)或 SOCK_DGRAM(UDP协议)。在 TCP 的场景中,sockfd 是一个连接端点,用来监听客户端的连接请求。

    • backlog:最大连接请求队列的长度,指定当服务器应用程序无法及时接受客户端连接时,操作系统内核允许的最大排队连接数。如果这个队列满了,后续的连接请求会被拒绝,或者客户端会收到一个连接失败的错误。通常该值设置为 5 到 128 之间。

      • 当该连接队列已满,服务端会将连接放到一个临时的半连接队列中,并将连接设置为SYN_RCVD状态,这个半连接队列不会长时间存在,当一定时间没有响应,会直接丢弃,如果被丢弃后客户端发送了数据,那么客户端和服务端会先重新进行三次握手再发送数据。
  • 返回值:成功时返回 0。出错时返回 -1,并设置 errno 以指示错误。

4. 接受
  • accept():是服务器端套接字函数,用于接受一个客户端的连接请求。当客户端请求连接时,服务器通过调用 accept() 来接受连接,并返回一个新的套接字,用于与客户端进行数据交换。accept() 是一个阻塞调用,意味着服务器会在调用此函数时等待,直到有客户端连接到来。

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)

    // 等待客户端连接请求
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;  // 绑定所有接口
    address.sin_port = htons(8080);        // 监听端口 8080
    new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
    
    • sockfd:这是由 socket() 创建并通过 bind()listen() 函数设置为监听状态的套接字描述符。

    • addr:一个指向 struct sockaddr 结构体的指针,用于存储客户端的地址信息。通常会传入 struct sockaddr_in,它包含客户端的 IP 地址和端口号。

    • addrlen:这是一个指向 socklen_t 类型的指针,用于表示传入的 addr 缓存的大小。在调用 accept() 时,addrlen 会被更新为客户端地址结构的实际大小。

    • 返回值:

      • 成功时,返回一个新的套接字描述符(new_socket),用于与客户端进行通信。

      • 失败时,返回 -1,并设置 errno 来表示错误原因。

5. 连接
  • connect():是客户端用于建立与服务器的连接的函数。在客户端调用 connect() 后,它会尝试与指定的服务器(通过 IP 地址和端口号)建立连接。此函数通常是客户端程序的一部分,在发起通信之前,客户端需要先与服务器建立一个连接。

    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)

    int sock = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr))
    
    • sockfd:客户端通过 socket() 创建的套接字描述符。

    • addr:一个指向 struct sockaddr 结构体的指针,包含目标服务器的 IP 地址和端口号。通常会使用 struct sockaddr_in 来指定目标服务器的地址信息。

    • addrlen:地址结构的长度(通常是 sizeof(struct sockaddr_in))。

    • 返回值:成功时返回 0。出错时返回 -1,并设置 errno 以指示错误。

函数TCPUDP
socket()使用使用
bind()服务器使用客户端和服务器都可以使用
listen()服务器使用不使用
accept()服务器使用不使用
connect()客户端使用客户端和服务器都可以使用
6. 设置套接字
  • setsockopt():用于设置套接字选项的函数,可以用于配置套接字的行为和特性。通过这个函数,你可以设置多种套接字选项,比如设置缓冲区大小、超时、协议相关的设置等。

    int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen)

    // 创建 TCP 套接字
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int optval = 1;
    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval))
    
    • sockfd:套接字描述符,是通过 socket() 函数创建的。

    • level:协议层级,通常是 SOL_SOCKET,表示对套接字层进行操作,也可以是其他协议特定的层级(例如 IPPROTO_TCPIPPROTO_UDP)。

    • optname:要设置的选项名称。

      • SO_REUSEADDR:允许重用本地地址和端口。在关闭一个已绑定的套接字后,能允许快速重新绑定到相同的地址和端口。
      • SO_RCVBUF:设置接收缓冲区的大小。
      • SO_RCVBUF:设置发送缓冲区的大小。
      • SO_KEEPALIVE:启用或禁用连接保持活动。
      • SO_BROADCAST:允许发送广播。
      • SO_LINGER:设置 linger 选项,用来指定连接在关闭时的延迟行为。
    • optval:指向存储选项值的缓冲区。具体内容和类型取决于选项。

    • optlenoptval 缓冲区的大小。

    • 返回值:

      • 成功返回 0

      • 失败返回 -1,并且设置 errno 来指示错误原因。

4)传输数据

1. 接受数据
  • recvfrom() 是一个用于接收数据的函数,通常在基于 UDP 的无连接套接字通信中使用。它可以接收来自特定发送者或任意发送者的数据,并且可以选择性地获取发送方的地址信息。

    ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen );

    • sockfd

      • 接收数据的套接字文件描述符。
      • 必须是一个已经绑定到本地地址(使用 bind)的套接字。
    • buf

      • 接收数据的缓冲区指针。
      • 函数会将接收到的数据存入该缓冲区。
    • len

      • buf 缓冲区的大小(以字节为单位)。
      • 接收数据时,如果数据大于缓冲区,数据会被截断。
    • flags

      • 控制行为的标志位,可以为以下值的组合:
        • 0:默认行为。
        • MSG_PEEK:仅查看数据而不从队列中移除。
        • MSG_WAITALL:等待接收到足够数据(填满缓冲区)。
        • MSG_DONTWAIT:非阻塞模式(无数据时立即返回)。
    • src_addr

      • 指向 struct sockaddr 类型的结构体,用于存储发送方的地址信息(可为 NULL)。
      • 如果不关心发送方地址,可设置为 NULL
    • addrlen

      • 指向 socklen_t 的指针,用于存储 src_addr 的大小。
      • 在调用前,需初始化为 src_addr 缓冲区的大小;返回后,表示实际存储的地址大小。
      • 如果 src_addrNULL,此参数也应为 NULL
    • 返回值

      • 成功:返回接收到的字节数。
      • 失败:返回 -1,并设置 errno 以指示错误原因。
2. 发送数据
  • sendto():用于 UDP 套接字的发送数据的函数,它允许向指定的目标地址发送数据。对于 UDP,它是一个无连接的协议,因此你需要明确地指定目标地址和端口。

    ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    const char* message = "Hello, UDP!";
    // 2. 设置目标地址结构
    struct sockaddr_in dest_addr;
    dest_addr.sin_family = AF_INET;
    dest_addr.sin_port = htons(12345);  // 目标端口
    dest_addr.sin_addr.s_addr = inet_addr("127.0.0.1");  // 目标IP地址
    ssize_t sent_len = sendto(sockfd, message, strlen(message), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
    
    • sockfd:发送数据的套接字描述符,通常是通过 socket() 创建的,适用于无连接的 UDP 套接字。

    • buf:指向存储要发送数据的缓冲区的指针。

    • len:缓冲区的长度,指定要发送的数据的字节数。

    • flags:控制发送操作的标志,可以是以下值之一(可以组合使用):

      • MSG_CONFIRM:请求对方确认收到数据(适用于可靠传输协议)。
      • MSG_DONTROUTE:不走路由表,直接发送。
      • MSG_NOSIGNAL:如果发送数据时产生了信号,则不触发信号。
    • dest_addr:指向 sockaddr 结构体的指针,包含目标主机的地址信息,通常是 sockaddr_in(对于 IPv4)或 sockaddr_in6(对于 IPv6)。

    • addrlen:目标地址的长度,通常是 sizeof(struct sockaddr_in)sizeof(struct sockaddr_in6)

    • 返回值:

      • 成功时,返回发送的字节数。

      • 失败时,返回 -1,并设置 errno 以指示错误。

二、守护进程化

1)进程相关标识符

1. PID(Process ID,进程ID)
  • 每个正在运行的进程都会分配一个唯一的进程标识符(PID),用于区分不同的进程。
  • PID 是操作系统内部用来管理和调度进程的关键标识符。当我们执行命令或启动程序时,操作系统会分配一个 PID 来标识该进程。
2. PPID(Parent Process ID,父进程ID)
  • 每个进程都有一个父进程,也就是创建它的进程。PPID 就是父进程的 PID。
  • PPID 表示当前进程的父进程,用于构建进程树。通过 PPID 可以追踪到某个进程是由哪个父进程创建的。
3. PGID(Process Group ID,进程组ID)
  • 进程组是一个或多个进程的集合,它们共享一个进程组 ID。PGID 是进程组的标识符。
  • 进程组在 Unix 系统中用于信号的发送。例如,可以向整个进程组发送一个信号,而不仅仅是单个进程。
4. SID(Session ID,会话ID)
  • 会话是一组进程的集合,它通常由一个登录会话或某个程序的启动进程所创建。SID 是会话的标识符。
  • 会话用于管理进程的生命周期,特别是在控制终端的上下文中。例如,登录系统后,所有的进程都属于同一个会话。
5. TGID(Thread Group ID,线程组ID)
  • 线程组是指多个线程共享相同的进程 ID 和资源。TGID 是线程组的标识符。
  • 在 Linux 系统中,线程和进程都被视为任务。线程组 ID 用于标识一组共享资源的线程。
6. UID(User ID,用户ID)
  • UID 是操作系统用于标识用户的唯一标识符。
  • 操作系统使用 UID 来管理文件的访问权限、资源分配等。每个用户都有一个唯一的 UID,通常在多用户系统中,UID 用于控制每个用户的操作权限。

进程组与任务的关系

  • 任务包含进程:在 Linux 中,任务通常是指单个的进程或线程。每个进程都有一个唯一的 PID,并且是操作系统调度的基本单位。
  • 进程组包含任务(进程):多个进程可以组成一个进程组。进程组由一个或多个任务(进程)组成,这些任务共享相同的 PGID。一个进程属于一个进程组,而进程组内的所有进程会共享一些属性和资源(如信号的发送)。
  • 进程组 ID:每个进程组都有一个唯一的 PGID,它通常是该进程组内某个进程的 PID。这个 PID 一般是该组中的第一个进程的 PID。虽然进程组中的每个进程都有自己的 PID,但它们共享一个进程组 ID。

2)Session

在 Linux 中,前台进程后台进程的区分主要是通过进程的输入/输出交互方式来确定的,具体来说是基于终端控制和进程的执行方式。下面我会详细讲解如何区分前台进程和后台进程,以及相关的操作方法。

1. 前台进程(Foreground Process)

前台进程是指直接与用户交互的进程,通常在用户的终端窗口中执行,并且会占用该终端的输入输出。

  • 特征
    • 前台进程在启动后,会占用当前的终端,并与用户进行交互。
    • 用户可以直接向前台进程发送输入并获取其输出。
    • 一旦前台进程结束,终端控制权会归还给用户。
  • 如何识别
    • 在命令行中,运行一个进程时,默认情况下,它是前台进程。例如,运行 vim 编辑器,或者执行一个 python 脚本,这些都会是前台进程。
  • 管理前台进程
    • 如果你在执行一个命令时,你的终端会被该进程“占用”,你只能看到该进程的输出,而无法执行其他命令。
    • 可以使用 Ctrl+C 来终止当前前台进程。
2. 后台进程(Background Process)

后台进程是指在系统后台运行的进程,它不会占用当前终端的输入输出流。用户可以在终端继续执行其他命令,而后台进程在不干扰用户操作的情况下继续执行。

  • 特征

    • 后台进程不会占用终端的输入输出,用户可以在终端继续执行其他命令。
    • 通常使用一个符号 & 将命令放到后台运行,或者使用一些专门的命令(如 nohupdisown)将进程移到后台。
    • 后台进程的输出通常会显示在终端中,除非使用输出重定向将其发送到文件。
  • 如何识别

    • 在命令行中,使用符号 & 将一个命令放到后台执行,例如:

      long_running_command &
      
    • 该命令将在后台运行,并且会立即返回到命令行,用户可以继续输入其他命令。

  • 管理后台进程

    • 使用 jobs

      命令查看当前的后台进程:

      jobs
      

      输出类似:

      [1]+  1234 running    long_running_command &
      
    • 使用

      fg 将后台进程带到前台:

      fg %1
      

      这将把进程 id1234,任务号1的任务带到前台运行。

    • 使用 bg将暂停的进程继续在后台运行:

      bg %1
      
    • 使用 kill命令终止后台进程:

      kill %1
      
3. 如何从前台切换到后台

如果你已经在前台运行了一个进程,但希望将其切换到后台,可以使用以下方法:

  • 暂停前台进程并将其移至后台

    • 在执行的前台进程中,按下 Ctrl+Z 将进程暂停。

    • 然后使用 bg命令将其发送到后台继续运行:

      bg
      
  • 将后台进程移动到后台执行

    • 使用 disown命令来彻底把后台进程从当前 shell 会话中“脱离”出来,防止它在 shell 会话关闭时被杀死:

      disown %1
      
4. 使用 nohup 命令启动后台进程

启动一个即使在关闭终端或退出会话后仍然继续运行的后台进程,可以使用 nohup 命令。

  • nohup:该命令使进程在你退出终端后仍然运行,并且它会将标准输出和标准错误重定向到文件 nohup.out

    nohup long_running_command &
    

    这样,无论你是否退出终端,long_running_command 都会继续运行。

3)守护进程

守护进程(Daemon) 是在后台运行的进程,它通常不与任何终端交互,并且在系统启动时或系统运行过程中自动启动。守护进程通常用于处理系统级任务,如日志记录、网络服务、定时任务等。常见的守护进程包括 web 服务器(如 Apache)、数据库服务(如 MySQL)等。

守护进程化(Daemonization)指的是将一个普通的进程转变为守护进程的过程。这个过程通常包括一系列的步骤,以确保进程能够在后台独立运行,并在必要时能够安全退出。守护进程话步骤如下:

  • 忽略控制终端的信号

    signal(SIGCLD, SIG_IGN);
    signal(SIGPIPE, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);
    
    • 守护进程通常不希望接收到来自控制终端(例如键盘输入或终端关闭)的信号。可以使用 signal()sigaction() 来忽略这些信号。

    • 特别是,守护进程可能需要忽略 SIGHUP 信号,这个信号通常在终端关闭时发送。

  • 创建一个新的会话

    if(fork() > 0){exit(0);
    }
    setsid();
    
    • 使用 fork() 创建一个子进程,确保父进程终止。这样,子进程就不再是终端的控制进程(父进程)。

    • 调用 setsid() 将子进程创建为一个新的会话,并成为该会话的首进程。这样可以避免进程被控制终端和控制进程干扰。

    • 通过调用 setsid(),子进程会成为新的会话领导进程,脱离控制终端,彻底实现后台运行。

  • 改变工作目录

    if(!cwd.empty()){chdir(cwd.c_str());
    }
    
    • 守护进程应该将当前工作目录更改为根目录(/),以避免阻止文件系统的卸载。执行此操作可以确保守护进程不会锁定文件系统。

    • 使用 chdir("/") 进行目录切换。

  • 重定向文件描述符

    • 守护进程应该关闭标准输入(stdin)、标准输出(stdout)和标准错误输出(stderr)文件描述符。这是因为守护进程没有终端,通常不会与用户进行交互。

    • 可以使用 close() 关闭文件描述符

      fclose(stdin);
      fclose(stdout);
      fclose(stderr);
      
    • 或者将其重定向到 /dev/null

      freopen("/dev/null", "r", stdin);
      freopen("/dev/null", "w", stdout);
      freopen("/dev/null", "w", stderr);
      
  • 创建子进程

    • 使用 fork() 创建第二个子进程,确保守护进程不会偶然地重新获得控制终端。第一步的 fork() 创建的子进程还可能会被某些父进程或终端所关联,所以第二次 fork() 是确保完全与控制终端断开联系的措施。
  • 清理环境

    • 守护进程通常不需要与用户的会话状态或终端会话有关的环境变量。可以在守护进程化时清除这些变量,例如使用 unsetenv() 删除环境变量。

    • 通过调用 umask(0) 来设置文件创建权限掩码,这样守护进程创建的文件权限将不会受到父进程设置的限制。

  • deamon():函数通常位于 unistd.h 头文件中,使用它可以轻松地将一个进程转化为守护进程。

    int daemon(int nochdir, int noclose)

    // 将当前进程转化为守护进程
    daemon(0, 0)
    
    • nochdir:如果为 0,守护进程会将工作目录更改为根目录(/),以避免占用系统的任何文件系统。常见的做法是设置为 1 来保持当前工作目录不变,防止守护进程锁定特定目录。

    • noclose:如果为 0,守护进程会关闭标准输入、输出和错误输出,并将它们重定向到 /dev/null,通常会避免任何与终端的交互。设置为 1 会让守护进程保持标准文件描述符不关闭。

    • 返回值

      • 成功时,daemon() 返回 0。
      • 失败时,返回 -1,并设置 errno 以指示错误原因。

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

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

相关文章

程序员学商务英语之Visiting the Factory

Dialogue-1 Arranging a Visit安排参观 I was wondering if you would / could lend me a million bucks, you know, I’m trying to start / run my own business. 我想知道你是否能够借给我一百万美金&#xff0c;你知道&#xff0c;我正在创业。 Take off your tie befor…

机器视觉运动控制一体机在天地盖同步跟随贴合解决方案

市场应用背景 纸盒天地盖是一种包装形式&#xff0c;广泛应用于消费电子、食品礼盒、奢侈品及化妆品等领域。其采用高强度纸板&#xff0c;经过预组装处理&#xff0c;结构坚固稳定&#xff0c;能有效保护产品并提升品牌形象。随着包装行业快速发展&#xff0c;市场对天地盖的…

【智能体Agent】ReAct智能体的实现思路和关键技术

基于ReAct&#xff08;Reasoning Acting&#xff09;框架的自主智能体 import re from typing import List, Tuplefrom langchain_community.chat_message_histories.in_memory import ChatMessageHistory from langchain_core.language_models.chat_models import BaseChatM…

Electron打包工具对比

在 Electron 生态中&#xff0c;打包工具的选择直接影响开发效率、配置复杂度和最终应用的性能。以下是主流的 Electron 打包工具及其优劣分析&#xff0c;结合你的 Vue 项目需求&#xff0c;我会在最后给出推荐方案&#xff1a; 一、主流 Electron 打包工具对比 1. Electron …

云原生系列之本地k8s环境搭建

前置条件 Windows 11 家庭中文版&#xff0c;版本号 23H2 云原生环境搭建 操作系统启用wsl(windows subsystem for linux) 开启wsl功能&#xff0c;如下图 安装并开启github加速器 FastGithub 2.1 下载地址&#xff1a;点击下载 2.2 解压安装文件fastgithub_win-x64.zip 2…

【计算机网络入门】TCP拥塞控制

目录 1. TCP拥塞控制和TCP流量控制的区别 2. 检测到拥塞该怎么办 2.1 如何判断网络拥塞&#xff1f; 3. 慢开始算法 拥塞避免算法 4.快重传事件->快恢复算法 5. 总结 1. TCP拥塞控制和TCP流量控制的区别 TCP流量控制是控制端对端的数据发送量。是局部的概念。 TCP拥…

Spring Boot 整合 JMS-ActiveMQ,并安装 ActiveMQ

1. 安装 ActiveMQ 1.1 下载 ActiveMQ 访问 ActiveMQ 官方下载页面&#xff0c;根据你的操作系统选择合适的版本进行下载。这里以 Linux 系统&#xff0c;Java环境1.8版本为例&#xff0c;下载 apache-activemq-5.16.7-bin.tar.gz。 1.2 解压文件 将下载的压缩包解压到指定目…

《几何原本》命题I.13

《几何原本》命题I.13 两条直线相交&#xff0c;邻角是两个直角或者相加等于 18 0 ∘ 180^{\circ} 180∘。 若两角相等&#xff0c;则根据定义&#xff0c;两角为直角。 两角若不相等&#xff0c;如图&#xff0c;则 ( ∠ 1 ∠ 2 ) ∠ 3 ∠ 1 ( ∠ 2 ∠ 3 ) 9 0 ∘ …

优先级队列:通过堆的形式实现

描述: 大顶堆: 小顶堆: 索引位置查找: 代码实现: package com.zy.queue_code.deque;/*** @Author: zy* @Date: 2025-03-05-15:51* @Description:*/ public interface Priority

《OpenCV》—— dlib库

文章目录 dlib库是什么&#xff1f;OpenCV库与dlib库对比dlib库安装dlib——人脸应用实例——人脸检测dlib——人脸应用实例——人脸关键点定位dlib——人脸应用实例——人脸轮廓绘制 dlib库是什么&#xff1f; OpenCV库与dlib库对比 dlib库安装 dlib——人脸应用实例——人脸检…

蓝桥与力扣刷题(蓝桥 旋转)

题目&#xff1a;图片旋转是对图片最简单的处理方式之一&#xff0c;在本题中&#xff0c;你需要对图片顺时针旋转 90 度。 我们用一个 nm的二维数组来表示一个图片&#xff0c;例如下面给出一个 34 的 图片的例子&#xff1a; 1 3 5 7 9 8 7 6 3 5 9 7 这个图片顺时针旋转…

随机播放音乐 伪随机

import java.util.*;/*** https://cloud.tencent.com.cn/developer/news/1045747* 伪随机播放音乐*/ public class MusicPlayer {private List<String> allSongs; // 所有歌曲列表private List<String> playedSongs; // 已经播放过的歌曲列表private Map<String…

MiniMind用极低的成本训练属于自己的大模型

本篇文章主要讲解&#xff0c;如何通过极低的成本训练自己的大模型的方法和教程&#xff0c;通过MiniMind快速实现普通家用电脑的模型训练。 日期&#xff1a;2025年3月5日 作者&#xff1a;任聪聪 一、MiniMind 介绍 基本信息 在2小时&#xff0c;训练出属于自己的28M大模型。…

区块链中的数字签名:安全性与可信度的核心

数字签名是区块链技术的信任基石&#xff0c;它像区块链世界的身份证和防伪标签&#xff0c;确保每一笔交易的真实性、完整性和不可抵赖性。本文会用通俗的语言&#xff0c;带你彻底搞懂区块链中的数字签名&#xff01; 文章目录 1. 数字签名是什么&#xff1f;从现实世界到区块…

LLM自动金融量化-CFGPT

LLM自动金融量化-CFGPT 简介 CFGPT是一个开源的语言模型,首先通过在收集和清理的中国金融文本数据(CFData-pt)上进行继续预训练,包括金融领域特定数据(公告、金融文章、金融考试、金融新闻、金融研究论文)和通用数据(维基百科),然后使用知识密集的指导调整数据(CFD…

解决Docker拉取镜像超时错误,docker: Error response from daemon:

当使用docker pull或docker run时遇到net/http: request canceled while waiting for connection的报错&#xff0c;说明Docker客户端在访问Docker Hub时出现网络连接问题。可以不用挂加速器也能解决&#xff0c;linux不好用clash。以下是经过验证的方法&#xff08;感谢轩辕镜…

03.05 QT事件

实现一个绘图工具&#xff0c;具备以下功能&#xff1a; 鼠标绘制线条。 实时调整线条颜色和粗细。 橡皮擦功能&#xff0c;覆盖绘制内容。 撤销功能&#xff0c;ctrl z 快捷键撤销最后一笔 程序代码&#xff1a; <1> Widget.h: #ifndef WIDGET_H #define WIDGET…

【文生图】windows 部署stable-diffusion-webui

windows 部署stable-diffusion-webui AUTOMATIC1111 stable-diffusion-webui Detailed feature showcase with images: 带图片的详细功能展示: Original txt2img and img2img modes 原始的 txt2img 和 img2img 模式 One click install and run script (but you still must i…

go语言因为前端跨域导致无法访问到后端解决方案

前端服务8080访问后端8081这端口显示跨域了 ERROR Network Error AxiosError: Network Error at XMLHttpRequest.handleError (webpack-internal:///./node_modules/axios/lib/adapters/xhr.js:116:14) at Axios.request (webpack-internal:///./node_modules/axios/lib/core/A…

hive之lag函数

从博客上发现两个面试题&#xff0c;其中有个用到了lag函数。整理学习 LAG 函数是 Hive 中常用的窗口函数&#xff0c;用于访问同一分区内 前一行&#xff08;或前 N 行&#xff09;的数据。它在分析时间序列数据、计算相邻记录差异等场景中非常有用。 一、语法 LAG(column,…