【Linux】TCP网络套接字编程+守护进程

文章目录

  • 日志类(完成TCP/UDP套接字常见连接过程中的日志打印)
  • 单进程版本的服务器客户端通信
  • 多进程版本和多线程版本
  • 守护进程化的多线程服务器


日志类(完成TCP/UDP套接字常见连接过程中的日志打印)

为了让我们的代码更规范化,所以搞出了日志等级分类,常见的日志输出等级有 Info Debug Warning Error Fatal 等,再配合上程序运行的时间,输出的内容等,公司中就是使用日志分类的方式来记录程序的输出,方便程序员找bug。 实际上在系统目录/var/log/messages文件中也记录了Linux系统自己的日志输出,可以看到我的Linux系统中之前在使用时产生了很多的error和warning,我们的代码也可以搞出来这样的输出日志信息到文件或者显示器的功能。

#pragma once
#include <iostream>
#include <string>
#include <stdio.h>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>#define SIZE 1024
#define Screen 1    // 向屏幕打印
#define oneFile 2   // 向一个文件中打印
#define classFile 3 // 分类打印
#define LogFileName "log.txt"
enum
{Info = 0, // 信息Debug,    // 调试Warning,Error,Fatal // 严重错误
};class Log
{
private:int _printMethod;public:Log(){_printMethod = Screen;}~Log(){}// 设置打印方式void Enable(int method){_printMethod = method;}// 将日志等级转化为stringstd::string LevelToSting(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}// 向一个文件中打印void PrintfOneFile(const std::string &filename, const std::string &logtxt) // log.txt{int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size());close(fd);}// 分类打印void PrintfClassFile(int level, const std::string &logtxt) // log.txt.Info/Debug/Error等等{std::string filename = LogFileName;filename += '.';filename += LevelToSting(level);PrintfOneFile(filename, logtxt);}void printlog(int level, std::string logtxt){switch (_printMethod){case Screen:{std::cout << logtxt << std::endl;break;}case oneFile:{PrintfOneFile(LogFileName, logtxt);break;}case classFile:{PrintfClassFile(level, logtxt);break;}default:break;}}// 将日志信息写入到screen \ filevoid LogMessage(int level, const char *format, ...){char LeftBuffer[SIZE];time_t t = time(NULL);struct tm *ctime = localtime(&t);snprintf(LeftBuffer, sizeof(LeftBuffer), "[%s]:[%d-%d-%d %d:%d:%d]", LevelToSting(level).c_str(), ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday, ctime->tm_hour, ctime->tm_min, ctime->tm_sec);char RightBuffer[SIZE];va_list list;va_start(list, format);                                    // 将list指向可变参数的第一个参数vsnprintf(RightBuffer, sizeof(RightBuffer), format, list); // 这个函数按照调用者传过来的format格式执行list的可变参数部分va_end(list); //将list置NUllchar logtxt[2 * SIZE];snprintf(logtxt, sizeof(logtxt), "%s %s", LeftBuffer, RightBuffer);// 现在将Log打印到stdout// printf("%s", logtxt);printlog(level, logtxt);}
};
  1. 上面的localtime()是Linux中将时间戳转化本地时间的API,函数会返回一个结构struct tm *这个结构里面的成员就是年月日-时分秒,这个API的参数是本机的时间戳使用time(NULL)在这里插入图片描述
  2. snprintf是按照格式将指定内容和长度写入到指定缓冲区
  3. va_list 是 C 语言中用于处理可变参数列表的数据类型。在使用可变参数函数(如 printf、vprintf、fprintf、vfprintf 等)时,需要使用 va_list 类型的变量来访问这些参数。
    通常,你会在函数中声明一个 va_list 类型的变量,然后使用一系列宏来访问可变参数列表中的参数。在使用完之后,需要调用相应的宏来清理 va_list 变量。
    在这里插入图片描述4. vsnprintf是一个 C 标准库函数,用于格式化字符串并将结果输出到字符数组中。它类似于 snprintf,但是接受一个 va_list 类型的参数,允许处理可变参数列表。通过 vsnprintf,你可以将格式化后的字符串输出到指定的字符数组中,而不需要提前知道可变参数的数量。在这里插入图片描述

单进程版本的服务器客户端通信

TCP套接字的创建和UDP一样,先使用socket创建套接字,在结构中设置IP和port,其次就是将IP 和 端口的bind

  1. 不同点是bind之后需要将套接字设置为监听状态,因为TCP协议是面向连接的 在这里插入图片描述
    监听函数success的返回0,错误则返回-1,错误码被设置
  2. 在UDPbind完成套接字之后,就是recvfrom接受客户端发过来的数据,其次就是sendto
    将消息处理后发回客户端。但是在TCP将套接字设置为监听状态之后,需要accept接收客户端连接请求,并且返回一个新的sockfd文件描述符这个新的套接字用于与客户端进行通信,而原始的监听套接字仍然可以继续接受其他客户端的连接请求。,那么我们使用socketAPI创建套接字的时候,这个API返回的sockfd和我们使用accept返回的sockfd有什么区别呢?
    在这里插入图片描述
    使用socketAPI创建的套接字属于监听套接字,也就是说listenAPI需要使用它,它不能进行网络通信,使用accept接收的套接字这才是我们进行网络通信的套接字,如果是多线程或者多进程版本的服务器,我们就会使用监听套接字来进行另一个客户端的accept
  3. 在TCP套接字编程中使用read 和 write 进行读写数据
//TcpSever.hpp
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include "Log.hpp"
#include <arpa/inet.h> //struct sockaddr_in 结构在这个头文件里面
#include <unistd.h>
#include <signal.h>
#include <pthread.h>const uint16_t default_port = 8080;
const std::string default_ip = "0.0.0.0";
Log lg;class TcpSever
{
private:int _listen_sockfd;uint16_t _port;std::string _ip;
public:TcpSever(uint16_t port = default_port, std::string ip = default_ip) : _port(port), _ip(ip){}~TcpSever(){}void Init(){// 创建tcp套接字_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockfd < 0){lg.LogMessage(Fatal, "socket Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "socket success: %d", _listen_sockfd);// 设置端口的IPstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);inet_aton(_ip.c_str(), &(local.sin_addr));// 绑定套接字if (bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0){lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));exit(-1);}lg.LogMessage(Info, "bind socket success, listensock_: %d", _listen_sockfd);// 将套接字设置为监听状态if (listen(_listen_sockfd, 10) < 0){lg.LogMessage(Fatal, "listen Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "listen success");}void Service(int sockfd, uint16_t clientport, const std::string &clientip){char buffer[4096];while (true){ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(), echo_string.size());}// 如果客户端提前退出,服务端会读取到0else if (n == 0){lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);break;}}}void Run(){struct sockaddr_in client;socklen_t len = sizeof(client);while (true){// 接收客户端连接  返回通信套接字!!!struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?continue;}// 接收数据// 拿到客户端的 IP地址 和 端口uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));// version 1 单进程版本  只能有一个用户进程进行读写Service(sockfd, clientport, clientip);close(sockfd);}}
};

我们在Main.cc中创建一个服务器对象,然后进行初始化 和 运行服务器端
使用命令行参数告诉服务器端的port

//Main.cc
#include "TcpSever.hpp"
#include<iostream>
#include<memory>void Useage(const std::string& argv)
{std::cout << argv << " -> Should Enter port 1024+" << std::endl;
}// ./tcpsever 8080
int main(int argc, char* argv[])
{if(argc != 2){Useage(argv[0]);return -1;}uint port = atoi(argv[1]);std::unique_ptr<TcpSever> tcp_sever(new TcpSever(port));tcp_sever->Init();tcp_sever->Run();return 0;
}

接下来就是编写客户端代码了:在TCP套接字编程中,connect 函数用于向服务器发起连接请求。当客户端创建一个套接字后,需要调用 connect 函数来连接到服务器的指定地址和端口。

这里是引用
同样客户端也是需要bind的,但是不需要用户显式bind:在TCP套接字编程中,客户端不需要显式调用 bind 函数来绑定地址的原因主要有两点:

  1. 动态选择本地端口: 在客户端调用 connect 函数时,系统会自动为客户端选择一个合适的本地端口,并将其绑定到客户端的套接字上。这样可以确保客户端套接字与服务器端建立连接时不会与其他套接字冲突。
  2. 客户端套接字的行为: 客户端通常不需要在网络上提供服务,而是主动连接到服务器端,因此不需要像服务器端那样在特定地址上监听连接请求。客户端的套接字行为是发起连接,而不是等待连接,因此不需要显式绑定地址。
//TcpClient.cc
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include "Log.hpp"
#include <arpa/inet.h> //struct sockaddr_in 结构在这个头文件里面
Log lg;
using namespace std;void Useage(const std::string &argv)
{std::cout << argv << " -> Should Enter port 1024+" << std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){Useage(argv[0]);return -1;}// 创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){lg.LogMessage(Fatal, "socket Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "socket success: %d", sockfd);// 建立连接struct sockaddr_in sever;socklen_t len = sizeof(sever);memset(&sever, 0, sizeof(sever));uint port = atoi(argv[2]);std::string ip = argv[1];sever.sin_family = AF_INET;sever.sin_port = htons(port);inet_aton(ip.c_str(), &(sever.sin_addr));if (connect(sockfd, (sockaddr *)&sever, len) < 0){lg.LogMessage(Fatal, "connect Error: %s", strerror(errno));exit(-1);}std::string message;while(true){cout << "client please Enter@ " << endl;getline(cin, message);write(sockfd, message.c_str(), message.size());char inbuffer[4096];int n = read(sockfd, inbuffer, sizeof(inbuffer));if(n > 0){inbuffer[n] = 0;cout <<  inbuffer << endl;}}close(sockfd);return 0;
}

客户端开始死循环运行时,第一件事就是向服务器发起连接请求,这个连接的工作也不难做,因为客户端知道目的ip和目的port,所以直接填充server结构体中的各个字段,然后直接发起连接请求即可。连接成功后就可以开始通信,同样的客户端也是使用read和write等接口来进行数据包的发送和接收。如果服务器读到0,则说明客户端已经不写了,那么如果客户端继续向服务器发消息,就相当于写端向已经关闭的读端继续写入,此时OS会终止掉客户端进程。
由于UDP和TCP分别是无连接和面向连接的,所以两者有些许不同,TCP的服务器如果挂掉,客户端继续写,则客户端进程会被操作系统终止掉,而UDP的服务器如果挂掉,客户端是可以继续写的,只不过客户端发送的数据包会被简单的丢弃掉罢了

问题提出: 现在出现了一个新的问题,用户1连接成功并开始通信时,用户2可以连接服务器,因为服务器一直处于监听状态,但用户2发送的消息却并不会被服务器回显,而只有当第一个用户进程被终止掉之后,用户2进程才会立马回显刚刚所发送的一堆消息,接下来用户2才可以正常和服务器通信,这是为什么呢?其实主要是因为我们的代码逻辑是串行执行的,一旦服务器启动时,因为是单进程,所以连接一个客户端之后,服务器就会陷入service的死循环,无法继续循环执行accept以接收来自客户端的连接请求。而当连接的客户端终止掉之后,service会读到0,此时才会break跳出死循环,重新执行accept建立新的连接。
所以如果想要让服务器同时建立多个连接,可以通过多进程或多线程以及线程池的方式来实现。


多进程版本和多线程版本

多进程的实现方案也很简单,让父进程去执行客户端连接的代码,也就是执行accept的功能,让fork出来的子进程执行客户端进行通信的服务代码,也就是执行service,创建子进程后,子进程应该将自己不使用的文件描述符关闭,防止子进程对父进程打开的文件进行误操作,尤其是像listenSockfd这样的文件描述符,如果子进程对listenSockfd进行写入什么的,很有可能会导致服务器崩溃,此外,关闭不用的文件描述符也可以给子进程腾出来一部分文件描述符表的下标位置,防止文件描述符泄露。

创建出来的子进程是需要等待的,pid_t result = waitpid(pid, &status, WNOHANG); // 使用 WNOHANG 选项进行非阻塞等待在代码中使用非阻塞式等待是一个非常不好用的做法,这会让服务器的工作主线偏离,因为如果要使用非阻塞式等待,则势必得通过轮询的方式来检测子进程的状态,那服务器就需要一直询问子进程是否退出,但我服务器的核心工作主线是接收客户端的请求并建立连接进行网络通信的啊,一旦非阻塞等待,服务器的性能就一定会下降,因为需要一直做不必要的工作:比如询问子进程状态,况且waitpid还是系统调用,每次循环还要陷入内核,所以非阻塞式等待是一个非常不好的方案,不要用他。

第一种解决方案就是让子进程fork出孙子进程,子进程立马退出终止,让孙子进程去提供service服务,孙子进程退出时会被1号进程init进程接管,回收孙子进程(孤儿进程)的资源。父进程此时就可以阻塞式等待子进程退出,这个阻塞其实可以忽略不计,因为一旦创建出子进程,子进程就会立马退出,父进程也会立马回收掉子进程的资源,从而父进程可以继续向后accept其他客户端的连接请求,而让孙子进程提供service服务,当孙子进程退出后,1号进程会回收他的资源。

第二种解决方案就比较简单轻松,可以直接捕捉SIGCHLD信号显示设置为SIG_IGN,这样会直接忽略掉,父进程就不需要等待子进程,当子进程退出时,linux系统会自动帮我们回收子进程资源,父进程就省心了,不用管子进程退不退出的事了,把这件事丢给linux系统来干,我父进程专心accept其他的客户端连接请求就OK。

SIGCHLD 信号是在子进程改变状态时(如终止或停止)由内核发送给父进程的信号。父进程通常会安装一个信号处理函数来处理 SIGCHLD 信号。
通常情况下,SIGCHLD 信号的处理方式有以下几种:

  1. 忽略信号: 父进程可以选择忽略 SIGCHLD 信号。这通常意味着父进程对子进程的状态变化不感兴趣,因此子进程终止后会被操作系统回收。
  2. 捕获并处理信号: 父进程可以安装一个信号处理函数来处理 SIGCHLD 信号。在信号处理函数中,父进程可以调用 wait 或 waitpid 函数来等待子进程的终止,并处理子进程的退出状态。
  3. 使用信号的默认处理方式: 如果父进程没有对 SIGCHLD 信号进行特殊处理,那么默认情况下,操作系统会将子进程的状态变化通知给父进程,父进程可以通过调用 wait 或 waitpid 函数来获取子进程的退出状态。
    SIGCHLD 信号的处理方式通常取决于父进程的需求以及对子进程状态变化的关注程度。在使用非阻塞式等待子进程时,SIGCHLD 信号通常用于提醒父进程子进程的状态变化,以便父进程可以及时处理。
//多进程
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include "Log.hpp"
#include <arpa/inet.h> //struct sockaddr_in 结构在这个头文件里面
#include <unistd.h>
#include <signal.h>
#include <pthread.h>const uint16_t default_port = 8080;
const std::string default_ip = "0.0.0.0";
Log lg;
class TcpSever; //声明一下
class ThreadData
{
public:uint16_t _port;std::string _ip;int _sockfd;TcpSever* _tsvr;ThreadData(int sockfd, uint16_t port, const std::string &ip, TcpSever* ts) : _port(port), _ip(ip), _sockfd(sockfd), _tsvr(ts){}
};
class TcpSever
{
private:int _listen_sockfd;uint16_t _port;std::string _ip;
public:TcpSever(uint16_t port = default_port, std::string ip = default_ip) : _port(port), _ip(ip){}~TcpSever(){}void Init(){// 创建tcp套接字_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockfd < 0){lg.LogMessage(Fatal, "socket Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "socket success: %d", _listen_sockfd);// 设置端口的IPstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);inet_aton(_ip.c_str(), &(local.sin_addr));// 绑定套接字if (bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0){lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));exit(-1);}lg.LogMessage(Info, "bind socket success, listensock_: %d", _listen_sockfd);// 将套接字设置为监听状态if (listen(_listen_sockfd, 10) < 0){lg.LogMessage(Fatal, "listen Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "listen success");}void Service(int sockfd, uint16_t clientport, const std::string &clientip){char buffer[4096];while (true){ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(), echo_string.size());}// 如果客户端提前退出,服务端会读取到0else if (n == 0){lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);break;}}}void Run(){signal(SIGCHLD, SIG_IGN); // 忽略子进程的退出状态,让OS自动回收子进程僵尸struct sockaddr_in client;socklen_t len = sizeof(client);while (true){// 接收客户端连接  返回通信套接字!!!struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?continue;}// 接收数据// 拿到客户端的 IP地址 和 端口uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip))// version 2 多进程版pid_t id = fork();if (id == 0){close(_listen_sockfd);  //子进程不需要这个fd,父进程会指向fd文件Service(sockfd, clientport, clientip);close(sockfd);exit(-1);}close(sockfd);  //父进程需要关闭,文件描述符是引用计数的,如果父进程不关闭的话,用户不断增多,系统中fd会不断增多}}
};

多进程并不是一个好的实现方案,因为创建一个进程的代价远比线程大得多,频繁的创建和回收进程会给系统带来很大的压力,所以多进程是一个比较重量化方案,而反观多线程是一种轻量化的方案,所以使用多线程能让服务器的性能消耗更小一些。
实现的方式也比较简单,我们知道threadRoutine要传给pthread_create的话,必须为静态方法,如果是静态方法就无法调用service,所以我们搞一个结构体td包含TcpServer类的指针this,以及accept返回的用于通信的套接字文件描述符sockfd,将td地址传递给threadRoutine函数,线程函数内部进行回调service,service如果调用结束不要忘记将sockfd关闭,避免文件描述符资源泄露。在线程这里只有阻塞式等待join和不等待两种情况,没有非阻塞式等待,所以主线程创建线程之后如果不想阻塞式join从线程的退出,则可以创建线程之后立马将从线程设置为detach状态即线程分离,线程函数执行完毕之后退出时,由操作系统负责回收从线程资源,主线程也就撒手不管了。

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include "Log.hpp"
#include <arpa/inet.h> //struct sockaddr_in 结构在这个头文件里面
#include <unistd.h>
#include <signal.h>
#include <pthread.h>const uint16_t default_port = 8080;
const std::string default_ip = "0.0.0.0";
Log lg;
class TcpSever; //声明一下
class ThreadData
{
public:uint16_t _port;std::string _ip;int _sockfd;TcpSever* _tsvr;ThreadData(int sockfd, uint16_t port, const std::string &ip, TcpSever* ts) : _port(port), _ip(ip), _sockfd(sockfd), _tsvr(ts){}
};
class TcpSever
{
private:int _listen_sockfd;uint16_t _port;std::string _ip;
public:TcpSever(uint16_t port = default_port, std::string ip = default_ip) : _port(port), _ip(ip){}~TcpSever(){}void Init(){// 创建tcp套接字_listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);if (_listen_sockfd < 0){lg.LogMessage(Fatal, "socket Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "socket success: %d", _listen_sockfd);// 设置端口的IPstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);inet_aton(_ip.c_str(), &(local.sin_addr));// 绑定套接字if (bind(_listen_sockfd, (struct sockaddr *)&local, sizeof(local)) < 0){lg.LogMessage(Fatal, "bind error, errno: %d, errstring: %s", errno, strerror(errno));exit(-1);}lg.LogMessage(Info, "bind socket success, listensock_: %d", _listen_sockfd);// 将套接字设置为监听状态if (listen(_listen_sockfd, 10) < 0){lg.LogMessage(Fatal, "listen Error: %s", strerror(errno));exit(-1);}lg.LogMessage(Info, "listen success");}void Service(int sockfd, uint16_t clientport, const std::string &clientip){char buffer[4096];while (true){ssize_t n = read(sockfd, buffer, sizeof(buffer));if (n > 0){buffer[n] = 0;std::cout << "client say# " << buffer << std::endl;std::string echo_string = "server echo# ";echo_string += buffer;write(sockfd, echo_string.c_str(), echo_string.size());}// 如果客户端提前退出,服务端会读取到0else if (n == 0){lg.LogMessage(Info, "%s:%d quit, server close sockfd: %d", clientip.c_str(), clientport, sockfd);break;}else{lg.LogMessage(Warning, "read error, sockfd: %d, client ip: %s, client port: %d", sockfd, clientip.c_str(), clientport);break;}}}static void *Handler(void *args){ThreadData *td = static_cast<ThreadData*>(args);// 为例实现客户端-服务器端 并发运行 将线程设置为分离状态 线程终止的时候会自动释放资源pthread_detach(pthread_self());// 线程之间的大多数资源是共享的,所以不能不关闭文件描述符td->_tsvr->Service(td->_sockfd, td->_port, td->_ip);delete td;return nullptr;}void Run(){struct sockaddr_in client;socklen_t len = sizeof(client);while (true){// 接收客户端连接  返回通信套接字!!!struct sockaddr_in client;socklen_t len = sizeof(client);int sockfd = accept(_listen_sockfd, (struct sockaddr *)&client, &len);if (sockfd < 0){lg.LogMessage(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?continue;}// 接收数据// 拿到客户端的 IP地址 和 端口uint16_t clientport = ntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));// version 3 多线程版ThreadData *td = new ThreadData(sockfd, clientport, clientip, this);pthread_t tid;pthread_create(&tid, 0, Handler, td);}}
};

守护进程化的多线程服务器

上面的多线程服务器已经很完美了,但美中不足的是只要我的xshell或者vscode关闭了,该服务器就会被终止掉,我们还需要重新启动服务器,我们希望的是只要服务器启动之后,就不再受用户登录和注销的影响,这样的服务器进程我们把他叫做守护进程。
当xshell打开时,Linux会为我们创建一个会话,在一个会话当中有且只能有一个前台任务,可以有0个或多个后台任务,Linux创建的会话中,刚开始都是以bash作为前台任务,bash就是命令行解释器,用于客户输入指令和Linux kernel进行交互,当我们的程序运行起来时,bash进程会自动被切换为后台进程,所以你可以简单的试一下,当在命令行中启动进程后,执行pwd,ls,touch等bash指令一定是无效的,因为此时bash被切到后台运行了,等到进程终止退出后,Linux会重新将bash从后台切换为前台进程,此时用户就又可以通过bash指令重新和Linux kernel交互了。

自成一个会话的进程就被叫做守护进程,也叫做精灵进程。
要想让会话关闭以后进程还在运行,就需要让这个进程自成一个会话,也就是成为守护进程。
在这里插入图片描述
系统调用setsid的作用就是将调用该函数的进程变成守护进程,也就是创建一个新的会话,这个会话中只有当前进程。

注意:调用系统调用setsid的进程在调用之前不能是进程组的组长,否则无法创建新的会话,也就无法成为守护进程。

守护进程也具有文件描述符,但是它们通常不会使用标准输入、标准输出和标准错误这三个标准文件描述符(通常分别对应0、1和2),因为守护进程通常不与终端相关联,而是在后台独立运行。在守护进程启动时,通常会将这些标准文件描述符重定向到/dev/null(或者其他适当的文件描述符),以确保它们不会产生输出或输入。
这样做的目的是为了防止在后台运行时出现意外的输出,同时使得守护进程可以独立于终端运行,不会受到终端的影响。
/dev/null 是一个特殊的设备文件,系统中被用作无效设备。它实际上是一个位于文件系统中的文件,但是任何写入到 /dev/null 的数据都会被丢弃,任何从 /dev/null 读取的操作都会立即返回文件结束符。这意味着它可以用来丢弃不需要的输出或者提供一个空的输入源。
在一些情况下,程序可能会输出一些信息,但你并不想在这些信息中处理。这时你可以将输出重定向到 /dev/null,这样输出就会被丢弃而不会在终端上显示。同样地,如果程序需要一些输入但你并不想提供,你也可以将输入重定向自 /dev/null,这样程序会读取到空数据而不会被阻塞。总之,/dev/null 是一个用于丢弃数据或提供空数据的特殊设备文件,常用于重定向输入或输出到无效设备的场景中 。

在这里插入图片描述


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

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

相关文章

瑞_23种设计模式_观察者模式

文章目录 1 观察者模式&#xff08;Observer Pattern&#xff09;1.1 介绍1.2 概述1.3 观察者模式的结构1.4 观察者模式的优缺点1.5 观察者模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 JDK中提供的观察者模式实现 ★4.1 Observable类4.2 Obse…

Day63-LVS四层负载均衡及结合Nginx7层负载均衡实践

Day63-LVS四层负载均衡及结合Nginx7层负载均衡实践 1. LVS&#xff08;Linux Virtual Server&#xff09;介绍2. IPVS&#xff08;LVS&#xff09;发展史3. IPVS软件工作层次图4. LVS技术点小结5. LVS的4模式原理讲解5.1 NAT(Network AddressTranslation)&#xff0c;中文网络地…

《Retrieval-Augmented Generation for Large Language Models: A Survey》 AI 解读

论文链接&#xff1a;Retrieval-Augmented Generation for Large Language Models: A Survey 论文标题&#xff1a;《Retrieval-Augmented Generation for Large Language Models: A Survey》 一译中文版地址&#xff1a; https://yiyibooks.cn/arxiv/2312.10997v5/index.htm…

PI案例分享--2000A核心电源网络的设计、仿真与验证

目录 摘要 0 引言 1 为什么需要 2000A 的数字电子产品? 2 2000A 的供电电源设计 2.1 "MPM3698 2*MPM3699"的 MPS扩展电源架构 2.2 使用恒定导通时间(COT)模式输出核心电压的原因 2.3 模块化 VRM 的优势 2.4 用步进负载验证2000A的设计难点 2.4.1 电源网络 …

qtcreator的信号槽链接

在ui文件中简单创建一个信号槽连接并保存可以在ui_mainwindow.h下 class Ui_MainWindow 类 void setupUi(QMainWindow *MainWindow)函数 找到对应代码 QObject::connect(pushButton, SIGNAL(clicked()), MainWindow, SLOT(close())); 下拉&#xff0c;由于 class MainWind…

《权力》为什么只为某些人所拥有 - 三余书屋 3ysw.net

权力&#xff1a;为什么只为某些人所拥有 大家好&#xff0c;今天我们解读的书名是《权力》&#xff0c;副标题是“为什么只为某些人所拥有”。该书深入探讨了职场中的权力议题&#xff0c;强调获得权力是关键的职场技能之一。在激烈的职场竞争中&#xff0c;缺乏这一技能将使…

C#(winform) 调用MATLAB函数

测试环境 VisualStudio2022 / .NET Framework 4.7.2 Matlab2021b 参考&#xff1a;C# Matlab 相互调用 Matlab 1、编写Matlab函数 可以没有任何参数单纯定义matlab处理的函数&#xff0c;输出的数据都存在TXT中用以后期读取数据 function [result,m,n] TEST(list) % 计算…

Python 后端 Flask 使用 Flask-SocketIO、前端 Vue3 实现长连接 Websocket 通信详细教程(更新中)

Flask 安装 Flask-Socketio Flask-SocketIO 第三方库使 Flask 应用程序可以实现客户端和服务器之间的低延迟双向通信。客户端应用程序可以使用 Javascript、Python、C、Java 和 Swift 中的任何 SocketIO 客户端库或任何其他兼容客户端来建立与服务器的永久连接。 Flask-Socke…

逐步学习Go-Select多路复用

概述 这里又有多路复用&#xff0c;但是Go中的这个多路复用不同于网络中的多路复用。在Go里&#xff0c;select用于同时等待多个通信操作&#xff08;即多个channel的发送或接收操作&#xff09;。Go中的channel可以参考我的文章&#xff1a;逐步学习Go-并发通道chan(channel)…

使用 Yoda 和 ClickHouse 进行实时欺诈检测

背景 Instacart 是北美领先的在线杂货公司,拥有数百万活跃的客户和购物者。在其平台上打击欺诈和滥用行为不仅对于维护一个值得信赖和安全的环境至关重要,也对保持Instacart的财务健康至关重要。在这篇文章中,将介绍了一个欺诈平台——Yoda,解释了为什么我们选择ClickHous…

【踩坑】荣耀系统Android8.0 system目录Read-only file system

本来以为直接把Charles证书改成系统证书格式&#xff0c;然后通过mt管理器root之后移动到系统证书目录就行了&#xff0c;结果访问baidu仍然显示网络错误&#xff0c;折腾一晚上。后来直接安装为用户证书&#xff0c;与系统证书冲突。 手机型号&#xff1a;荣耀v10 EMUI&…

升级程序到Java21的记录一(在先升级jdk到21)

背景&#xff1a;为了使用Java21的最新特性虚拟线程以及提高程序的整体性能&#xff0c;决定将一个程序A升级到Java21. 备注&#xff1a;程序A有很多文件操作因此使用虚拟线程对提升性能有帮助&#xff0c;如果读者的程序是其他类型&#xff0c;请参考虚拟线程的一些资料决定是…

STM32学习笔记(10_3)- 软件I2C读写MPU6050

无人问津也好&#xff0c;技不如人也罢&#xff0c;都应静下心来&#xff0c;去做该做的事。 最近在学STM32&#xff0c;所以也开贴记录一下主要内容&#xff0c;省的过目即忘。视频教程为江科大&#xff08;改名江协科技&#xff09;&#xff0c;网站jiangxiekeji.com 本期开…

flutter官方案例context_menus

1&#xff1a;根据项目中的案例进行部署 2&#xff1a;运行查看有什么用&#xff0c;可不可以直接复制粘贴 案例地址 https://github.com/flutter/samples/tree/main/context_menus案例展示方法 直接把这个文件夹中的文件复制到lib文件夹中 3&#xff0c;19&#xff0c;4的fl…

Lazarus远控组件NukeSped分析

静态信息&#xff1a; 样本md5&#xff1a;9b656f5d7e679b94e7b91fc3c4f313e4 由此可见为假的Adobe Flash Player 的攻击样本 样本分析 通过五个函数&#xff0c;内部调用sub_40159D函数动态获取API函数 利用IDA python解密字符串。。 完整python代码 Python> idc.get_…

最优算法100例之18-列升序行升序的数组中查找元素

专栏主页:计算机专业基础知识总结(适用于期末复习考研刷题求职面试)系列文章https://blog.csdn.net/seeker1994/category_12585732.html 题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样一…

LeetCode刷题【链表,图论,回溯】

目录 链表138. 随机链表的复制148. 排序链表146. LRU 缓存 图论200. 岛屿数量994. 腐烂的橘子207. 课程表 回溯 链表 138. 随机链表的复制 给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节…

计算机网络——32差错检测和纠正

差错检测和纠正 错误检测 EDC 差错检测和纠错位&#xff08;冗余位&#xff09; D 数据由差错检测保护&#xff0c;可以包含头部字段 错误检测不是100%可靠的 协议会泄露一些错误&#xff0c;但是很少更长的EDC字段可以得到更好的检测和纠正效果 奇偶校验 单bit奇偶校验 …

App推广新篇章:Xinstall助力精准分析与优化

在当前的移动应用市场中&#xff0c;App推广已成为每个开发者不可或缺的一环。然而&#xff0c;推广并非简单的投放广告与等待用户下载&#xff0c;而是需要一套科学、系统的分析与优化流程。这正是Xinstall作为国内专业的App全渠道统计服务商&#xff0c;能够为您带来的核心价…

Decoupled Multimodal Distilling for Emotion Recognition 论文阅读

Decoupled Multimodal Distilling for Emotion Recognition 论文阅读 Abstract1. Introduction2. Related Works2.1. Multimodal emotion recognition2.2. Knowledge distillation3. The Proposed Method3.1. Multimodal feature decoupling3.2. GD with Decoupled Multimodal …