【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);}
};
- 上面的
localtime()
是Linux中将时间戳转化本地时间的API,函数会返回一个结构struct tm *
这个结构里面的成员就是年月日-时分秒,这个API的参数是本机的时间戳使用time(NULL)
snprintf
是按照格式将指定内容和长度写入到指定缓冲区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
- 不同点是bind之后需要将套接字设置为监听状态,因为TCP协议是面向连接的
监听函数success的返回0,错误则返回-1,错误码被设置- 在UDPbind完成套接字之后,就是
recvfrom
接受客户端发过来的数据,其次就是sendto
将消息处理后发回客户端。但是在TCP将套接字设置为监听状态之后,需要accept
接收客户端连接请求,并且返回一个新的sockfd文件描述符,这个新的套接字用于与客户端进行通信,而原始的监听套接字仍然可以继续接受其他客户端的连接请求。,那么我们使用socketAPI创建套接字的时候,这个API返回的sockfd和我们使用accept返回的sockfd有什么区别呢?
使用socket
API创建的套接字属于监听套接字,也就是说listen
API需要使用它,它不能进行网络通信,使用accept
接收的套接字这才是我们进行网络通信的套接字,如果是多线程或者多进程版本的服务器,我们就会使用监听套接字来进行另一个客户端的accept- 在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 函数来绑定地址的原因主要有两点:
- 动态选择本地端口: 在客户端调用 connect 函数时,系统会自动为客户端选择一个合适的本地端口,并将其绑定到客户端的套接字上。这样可以确保客户端套接字与服务器端建立连接时不会与其他套接字冲突。
- 客户端套接字的行为: 客户端通常不需要在网络上提供服务,而是主动连接到服务器端,因此不需要像服务器端那样在特定地址上监听连接请求。客户端的套接字行为是发起连接,而不是等待连接,因此不需要显式绑定地址。
//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 信号的处理方式有以下几种:
- 忽略信号: 父进程可以选择忽略 SIGCHLD 信号。这通常意味着父进程对子进程的状态变化不感兴趣,因此子进程终止后会被操作系统回收。
- 捕获并处理信号: 父进程可以安装一个信号处理函数来处理 SIGCHLD 信号。在信号处理函数中,父进程可以调用 wait 或 waitpid 函数来等待子进程的终止,并处理子进程的退出状态。
- 使用信号的默认处理方式: 如果父进程没有对 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 是一个用于丢弃数据或提供空数据的特殊设备文件,常用于重定向输入或输出到无效设备的场景中 。
相关文章:
【Linux】TCP网络套接字编程+守护进程
文章目录 日志类(完成TCP/UDP套接字常见连接过程中的日志打印)单进程版本的服务器客户端通信多进程版本和多线程版本守护进程化的多线程服务器 日志类(完成TCP/UDP套接字常见连接过程中的日志打印) 为了让我们的代码更规范化&…...
【Qt 学习笔记】Day2 | 认识QtSDK中的重要工具
博客主页:Duck Bro 博客主页系列专栏:Qt 专栏关注博主,后期持续更新系列文章如果有错误感谢请大家批评指出,及时修改感谢大家点赞👍收藏⭐评论✍ Day2 | 认识QtSDK中的重要工具 文章编号:Qt 学习笔记 / 03…...
adc123456
DMA主要用于协助CPU完成数据转运的工作 DMA,英文全称Direct Memory Access,DMA这个外设是可以直接访问STM32内部存储器的,包括运行内存SRAM,程序存储器flash和寄存器等等,DMA都有权限访问,所以DMA能完成数据…...
YOLOV5训练自己的数据集教程(万字整理,实现0-1)
文章目录 一、YOLOV5下载地址 二、版本及配置说明 三、初步测试 四、制作自己的数据集及转txt格式 1、数据集要求 2、下载labelme 3、安装依赖库 4、labelme操作 五、.json转txt、.xml转txt 六、修改配置文件 1、coco128.yaml->ddjc_parameter.yaml 2、yolov5x.…...
精通Go语言文件上传:深入探讨r.FormFile函数的应用与优化
1. 介绍 1.1 概述 在 Web 开发中,文件上传是一项常见的功能需求,用于允许用户向服务器提交文件,如图像、文档、视频等。Go 语言作为一门强大的服务器端编程语言,提供了方便且高效的方式来处理文件上传操作。其中,r.F…...
【C语言】字符串
C语言用字符数组存放字符串,字符数组中的各元素依次存放字符串的各字符 一维字符数组:存放一个字符串(每个数组元素存放一个字符)二维字符数组:存放多个一维数组(字符串);二维数组的…...
云计算探索-DAS、NAS与SAN存储技术演进及其应用比较
1,介绍 随着信息技术的飞速发展,数据存储的需求日益增长,各种存储技术也应运而生。在众多的存储解决方案中,直接附加存储(Direct Attached Storage,简称DAS)、网络附加存储(Network …...
手机有线投屏到直播姬pc端教程
1 打开哔哩哔哩直播姬客户端并登录(按下图进行操作) 2 手机用usb数据线连接电脑(若跳出安装驱动的弹窗点击确定或允许),usb的连接方式为仅充电(手机差异要求为仅充电),不同品牌手机要求可能不一样,根据实际的来 3 在投屏过程中不要更改usb的连接方式(不然电脑会死机需要重启) …...
SOA、分布式、微服务之间的关系?
分布式它本身就是一种系统部署的架构理念,意思就是将一个系统拆分为各个部分,然后分别部署到不同的机器上去,SOA和微服务项目的部署方式都可以是分布式架构。 而SOA和微服务它们都是面向服务的架构,但是微服务相比于SOA在服务粒度…...
Java多线程学习(概念笔记)
面试题:并行和并发有什么区别? 现在都是多核CPU,在多核CPU下 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU 并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程 面试题:创建线…...
【C++】set和map
set和map就是我们上篇博客说的key模型和keyvalue模型。它们属于是关联式容器,我们之前说过普通容器和容器适配器,这里的关联式容器就是元素之间是有关联的,通过上篇博客的讲解我们也对它们直接的关系有了一定的了解,那么下面我们先…...
yolov5 v7.0打包exe文件,使用C++调用
cd到yolo5文件夹下 pyinstaller -p 当前路径 -i logo图标 detect.py问题汇总 运行detect.exe找不到default.yaml 这个是yolov8里的文件 1 复制权重文件到exe所在目录。 2 根据报错提示的配置文件路径,把default.yaml复制放到相应的路径下。(缺少相应…...
保研线性代数机器学习基础复习2
1.什么是群(Group)? 对于一个集合 G 以及集合上的操作 ,如果G G-> G,那么称(G,)为一个群,并且满足如下性质: 封闭性:结合性:中性…...
vultr ubuntu 服务器远程桌面安装及连接
一. 概述 vultr 上开启一个linux服务器,都是以终端形式给出的,默认不带 ui 桌面的,那其实对于想使用服务器上浏览器时的情形不是很好。那有没有方法在远程服务器安装桌面,然后原程使用呢?至少ubuntu的服务器是有的&am…...
前端学习<二>CSS基础——12-CSS3属性详解:动画详解
前言 本文主要内容: 过渡:transition 2D 转换 transform 3D 转换 transform 动画:animation 过渡:transition transition的中文含义是过渡。过渡是CSS3中具有颠覆性的一个特征,可以实现元素不同状态间的平滑过渡…...
Sqoop 的安装与配置
目录 1 下载并解压2 修改配置文件3 添加环境变量4 拷贝 JDBC 驱动5 测试Sqoop是否能够成功连接数据库 下载地址 1 下载并解压 (1)上传安装包 sqoop-1.4.6.bin__hadoop-2.0.4-alpha.tar.gz 到 hadoop101 的 /opt/software 路径中 (2…...
Mysql设置访问权限(docker配置)
1.运行命令:docker exec -it 数据库名 bash,我这里是bot_test, docker exec -it bot_test bash 2.运行命令mysql -uroot -p --default-character-setutf8,输入密码连接数据库 3.运行命令show databases,查看当前的表 4.进入my…...
【Linux】详解软硬链接
一、软硬链接的建立方法 1.1软链接的建立 假设在当前目录下有一个test.txt文件,要对其建立软链接,做法如下: ln就是link的意思,-s表示软链接,test.txt要建立软链接的文件名,后面跟上要建立的软链接文件名…...
维修贝加莱4PP420.1043-B5触摸屏Power Panel 400工业电脑液晶
深圳捷达工控维修为贝加莱、HMI 显示电源面板 400 4PP420.1043-B5 提供专业电子维修。在 深圳捷达工控维修,我们拥有及时且经济高效地维修 B&R 、HMI Display Power Panel 400 4PP420.1043-B5 的经验。我们为发送给我们工厂维修的贝加莱 HMI 显示面板 400 4PP42…...
Java_21 完成一半题目
完成一半题目 有 N 位扣友参加了微软与力扣举办了「以扣会友」线下活动。主办方提供了 2*N 道题目,整型数组 questions 中每个数字对应了每道题目所涉及的知识点类型。 若每位扣友选择不同的一题,请返回被选的 N 道题目至少包含多少种知识点类型。 示例…...
【WPF应用21】WPF 中的 TextBox 控件详解与示例
在 Windows Presentation Foundation (WPF) 中,TextBox 控件是一个强大的输入控件,允许用户输入、编辑和选择文本。TextBox 控件在各种应用程序中都非常常见,例如表单、对话框和编辑器。本文将详细介绍 TextBox 控件的功能、使用方法、属性、…...
小程序页面传参?
小程序页面之间传递参数通常可以通过以下几种方式实现: 通过 URL 参数传递:可以在跳转目标页面时,在 URL 中添加参数,目标页面可以通过 options 参数获取传递过来的数据。 // 页面 A wx.navigateTo({url: targetPage?param1value…...
C++list的模拟实现
为了实现list,我们需要实现三个类 一、List的节点类 template<class T> struct ListNode {ListNode(const T& val T()):_pPre(nullptr),_pNext(nullptr),_val(val){}ListNode<T>* _pPre;ListNode<T>* _pNext;T _val; }; 二、List的迭代器…...
Leetcode 187. 重复的DNA序列
DNA序列 由一系列核苷酸组成,缩写为 ‘A’, ‘C’, ‘G’ 和 ‘T’.。 例如,“ACGAATTCCG” 是一个 DNA序列 。 在研究 DNA 时,识别 DNA 中的重复序列非常有用。 给定一个表示 DNA序列 的字符串 s ,返回所有在 DNA 分子中出现不…...
都江堰泛计算操作系统(多机)应用方向
1、异构多核芯片 DJYOS是全球唯一支持异构多核的操作系统。当前的系统级芯片,为了适应多样化的用户需求,基本上都采用了异构多核架构。传统操作系统就需要在一个芯片内,运行多种操作系统,开发工作更加复杂,运行协同性低…...
【第十二届“泰迪杯”数据挖掘挑战赛】【2024泰迪杯】B题基于多模态特征融合的图像文本检索—解题全流程(论文更新)
【第十二届“泰迪杯”数据挖掘挑战赛】【2024泰迪杯】B题基于多模态特征融合的图像文本检索更新(论文更新) 本节主要更新了论文、训练日志的log数据提取(Loss、ACC、RK)等数据可视化作图的代码 B题交流QQ群: 4583…...
蓝桥杯22年第十三届省赛-统计子矩阵|一维前缀和加双指针
题目链接: 1.统计子矩阵 - 蓝桥云课 (lanqiao.cn) 蓝桥杯2022年第十三届省赛真题-统计子矩阵 - C语言网 (dotcpp.com) 说明: 涉及到子矩阵的时候,一般就跟前缀和相关,可以降维。 先回顾一下最大子矩阵,回忆一下一…...
SaaS 电商设计 (十) 记一次 5000kw 商品数据ES迁移 (详细的集群搭建以及线上灰度过程设计)
目录 一.背景二.技术目标三.技术方案3.1 整体流程3.2 ES 切换前:完成整体新集群的搭建.i:拓扑结构设计ii: 如何选择整体的 **ES** 集群配置. 3.3 **ES** 版本切换中3.3.1 多client版本兼容3.3.2 Router的设计 3.4 ES 切换后3.5 开箱即用 四.总结 专栏系列 -SaaS 电商设计 (一) …...
linux安装Tomcat
linux安装Tomcat 下载地址:https://archive.apache.org/dist/tomcat/tomcat-8/v8.5.87/bin/apache-tomcat-8.5.87.tar.gz 将下载的安装包传到该文件夹 解压安装包 tar -zxvf apache-tomcat-8.5.87.tar.gz 配置环境变量 vim /etc/profile 添加指定文件最下方 expo…...
【机器学习300问】57、机器是如何读得懂文本数据的呢?
你知道的,人工智能的大佬们想方设法的让机器具备人一样的能力,比如读懂文本的能力。既然机器是在模仿人类,那么问题“机器是如何读得懂文本数据的呢?”就可以变成“人是如何读得懂文本数据的呢?” 一、人是如何读得懂…...
今天31个省新增最新消息视频/seo服务哪家好
计算机辅助普通话水平测试考生须知 (4页)本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.9 积分计算机辅助普通话水平测试考生须知一、计算机辅助普通话水平测试的意义1.信息化:计算机智能…...
百度统计搜索词为什么有与网站不相关的词/工作手机
摘要:在当今信息化时代,大多数企业都需要网络支撑企业的ICT运行,提升企业运行效率,针对企业网络中的网元设备(包括交换机,路由器,防火墙等),很多企业希望根据自身的业务特…...
增城网站建设怎么选择/北京外包seo公司
Email:Louisa_wanguniland.net.cn MSN:louisa.wanglive.cn 待遇:面谈 12个月BASE,4-6个月奖金,2-3年后有股票分红 工作地点:杭州 职位基本要求: 学士学位及以上学历,重点院校优先;通信工程/应用…...
深圳做商城网站建设/公司网站搭建
tar -c 打tar包 -x 解tar包 -g 用gzip来压缩/解压缩文件 -j 使用‘bzip2’程序进行文件的压缩 -v 查看压缩过程 -f 指定文件转载于:https://blog.51cto.com/ceyes/706279...
济南软件开发培训机构/杭州网站seo价格
一次在使用json.dumps()过程中,出现错误提示:ERROR:"UnicodeDecodeError: utf8 codec cant decode byte 0xe1 in position 5: unexpected end of data"通过错误提示我们知道,肯定是编码问题,找到问题所在&am…...
六安最新疫情名单/自己怎么优化我网站关键词
转载于:https://www.cnblogs.com/classmethond/p/10387954.html...