加强网站建设 基本措施,wordpress 被挂广告,石家庄又开始管控了,电商网站设计页面设计网络字节序——TCP接口及其实现简单TCP服务器 文章目录 网络字节序——TCP接口及其实现简单TCP服务器简单TCP服务器的实现1. 单进程版#xff1a;客户端串行版2. 多进程版#xff1a;客户端并行版netstat查看网络信息3.多线程版#xff1a;并行执行log.hpp 守护进程fg、bg s…网络字节序——TCP接口及其实现简单TCP服务器 文章目录 网络字节序——TCP接口及其实现简单TCP服务器简单TCP服务器的实现1. 单进程版客户端串行版2. 多进程版客户端并行版netstat查看网络信息3.多线程版并行执行log.hpp 守护进程fg、bg setsid 简单TCP服务器的实现
TCP区别于UDP在于要设置套接字为监控状态即TCP是面向链接因此TCP套接字需要设置为监听状态
void initserver()
{
//1.创建套接字
_listensocksocket(AF_INET,SOCK_STREAM,0);
if(_listensock0)
{logMessage(FATAL,create listensocket error);exit(SOCK_ERR);
}logMessage(NORMAL, create socket success: %d, _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_familyAF_INET;
local.sin_porthtons(_port);
local.sin_addr.s_addrINADDR_ANY;
if(bind(_listensock,(struct sockaddr*)local,sizeof(local))0)//绑定失败
{logMessage(FATAL,bind error);exit(BIND_ERR);
}logMessage(NORMAL,bind success);
//3.将套接字设置为监听模式
if(listen(_listensock,0)0)
{logMessage(FATAL,listen error);exit(LISTEN_ERR);
}
logMessage(NORMAL,listen success);
}socket函数原型
#include sys/types.h
#include sys/socket.hint socket(int domain, int type, int protocol);domain 表示协议族常用的有 AF_INETIPv4和 AF_INET6IPv6。 type 表示Socket类型常用的有 SOCK_STREAMTCP和 SOCK_DGRAMUDP。 protocol 通常可以设置为 0让系统根据 domain 和 type 来选择合适的协议。 socket()打开一个网络通讯端口,如果成功的话,就像open()一样返回一个文件描述符 应用程序可以像读写文件一样通过socket函数用read/write在网络上收发数据
bind函数原型
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);sockfd 是socket描述符。 addr 是一个 struct sockaddr 结构体包含要绑定的IP地址和端口信息。 addrlen 是 addr 结构体的长度。因为addr结构体可以接受多种协议的sockaddr结构体因此要传其结构体的长度 bind()成功返回0,失败返回-1。 bind()的作用是将参数sockfd和myaddr绑定在一起, 使sockfd这个用于网络通讯的文件描述符监听addr所描述的地址和端口号;
listen函数原型
int listen(int sockfd, int backlog);sockfd 是socket描述符指用于进行网络监听的文件描述符backlog 表示等待连接队列的最大长度。listen成功返回0失败返回-1listen函数将使得sockfd处于监听状态并且允许backlog个客户端处于连接等待状态当收到多于backlog个客户端的的连接请求则选择忽略。实际上listen函数告诉操作系统指定的套接字sockfd处于监听状态该套接字开始等待其他计算机通过网络与其建立连接一旦有连接请求到达操作系统会将连接请求放入连接队列中连接队列的最大长度为backlog连接队列是一个存放连接请求的缓冲区如果队列已满新的连接请求将会被拒绝。即当一个套接字处于监听状态时它不直接处理数据传输而是等待其他计算机发起连接。 总的来说initserver函数作用是先创建套接字然后填充指定的端口号和ip并将套接字设置为监听状态 void start()
{while(true){struct sockaddr_in cli;socklen_t lensizeof(cli);bzero(cli,len);int sockaccept(_listensock,(struct sockaddr*)cli,len);if(sock0){logMessage(FATAL,accept client error);continue;}logMessage(NORMAL,accept client success);coutaccept sock: sockendl;}accept函数原型
#include sys/types.h
#include sys/socket.hint accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);sockfd是一个已经通过 socket 函数创建的套接字描述符并且是已经处于监听状态用于监听传入的连接请求。 addr是一个指向 struct sockaddr 结构的指针用于接收连接请求的客户端的地址信息。 addrlen是一个指向 socklen_t 类型的指针用于指定 addr 缓冲区的长度同时也用于返回实际客户端地址结构的大小。 accept函数作用是接受传入的连接请求他会阻塞程序的执行直到有一个连接请求到达。一旦有连接请求到达将会创建一个新的套接字并返回这个新套接字的文件描述符这个新套接字用于与客户端进行通信同时addr和 addrlen会填充上客户端的地址信息。 在服务器程序中accept函数会被用在一个循环中以接受多个客户端的连接请求 start函数作用是阻塞接受客户端发送来的连接请求使得服务器与客户端建立通信 tcpclient.cc #includeiostream
#includestring
#includememory
#includetcpclient.hpp
using namespace std;
using namespace client;
static void Usage(string proc)
{cout\nUsage :\n\tproc serverip serverport\nendl;
}
int main(int argc, char* argv[])
{if(argc!3){Usage(argv[0]);exit(1);}string serveripargv[1];
uint16_t serverportatoi(argv[2]);unique_ptrtcpclient tc(new tcpclient(serverip,serverport));tc-initclient();
tc-start();return 0;
}tcpclient.hpp #pragma once
#include iostream
#include string
#include cstring
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include unistd.h
using namespace std;
#define NUM 1024
namespace client
{class tcpclient
{public:
tcpclient(const string ip,const uint16_t port)
:_sock(-1)
,_port(port)
,_ip(ip)
{}void initclient()
{
//1.创建sockfd
_socksocket(AF_INET,SOCK_STREAM,0);
if(_sock0)
{cerrsocket create errorendl;exit(2);
}
//2.绑定 ip port不显示绑定OS自动绑定
}void start()
{
struct sockaddr_in ser;
bzero(ser,sizeof(ser));
socklen_t lensizeof(ser);
ser.sin_familyAF_INET;
ser.sin_porthtons(_port);
ser.sin_addr.s_addrinet_addr(_ip.c_str());
if(connect(_sock,(struct sockaddr *)ser,len)!0)
{cerrconnect errorendl;
}else
{string msg;while(true){coutEnter# ;getline(cin,msg);write(_sock,msg.c_str(),msg.size());char inbuffer[NUM];int nread(_sock,inbuffer,sizeof(inbuffer)-1);if(n0){coutserver return :inbufferendl;}else{break;}}
}
}~tcpclient()
{if(_sock0) close(_sock);
}private:
int _sock;
uint16_t _port;
string _ip;};
}tcpserver.cc #includetcpserver.hpp
#includelog.hpp
#includeiostream
#includestdlib.h
#includememory
using namespace Server;
using namespace std;static void Usage(string proc)
{cout\nUsage:\n\tproc local_port\n\nendl;
}int main(int argc,char* argv[])
{
if(argc!2)
{Usage(argv[0]);exit(USAGE_ERR);
}uint16_t portatoi(argv[1]);//将字符串转化为整数unique_ptrtcpserver ts(new tcpserver(port));
ts-initserver();
ts-start();return 0;
} tcpserver.hpp #pragma once
#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include sys/wait.h
#include signal.h
#include pthread.h
#includelog.hpp
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR1,SOCK_ERR,BIND_ERR,LISTEN_ERR};
class tcpserver;class ThreadData{
public:
ThreadData( tcpserver* self,int psock):_this(self),_psock(psock){}
tcpserver* _this;
int _psock;};class tcpserver
{public:tcpserver(const uint16_t port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensocksocket(AF_INET,SOCK_STREAM,0);
if(_listensock0)
{logMessage(FATAL,create listensocket error);exit(SOCK_ERR);
}logMessage(NORMAL, create socket success: %d, _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_familyAF_INET;
local.sin_porthtons(_port);
local.sin_addr.s_addrINADDR_ANY;
if(bind(_listensock,(struct sockaddr*)local,sizeof(local))0)//绑定失败
{logMessage(FATAL,bind error);exit(BIND_ERR);
}logMessage(NORMAL,bind success);
//3.将套接字设置为监听模式
if(listen(_listensock,0)0)
{logMessage(FATAL,listen error);exit(LISTEN_ERR);
}
logMessage(NORMAL,listen success);
}void start()
{// signal(SIGCHLD, SIG_IGN);threadPoolTask::getthpptr()-run();while(true){struct sockaddr_in cli;socklen_t lensizeof(cli);bzero(cli,len);int sockaccept(_listensock,(struct sockaddr*)cli,len);if(sock0){logMessage(FATAL,accept client error);continue;}logMessage(NORMAL,accept client success);coutaccept sock: sockendl;// serviceIO(sock);//客户端串行版// close(sock);//多进程版---//一个客户端占用一个文件描述符原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的而每次客户端进来都要占用新的文件描述符//因此若接收多个客户端不退出的话文件描述符会越来越少。
// pid_t idfork();//创建子进程
// if(id0)//子进程进入
// {
// close(_listensock);//子进程不需要用于监听因此关闭该文件描述符
// if(fork()0) exit(0);
// //子进程创建孙子进程子进程直接退出让孙子进程担任IO任务且孙子进程成为孤儿进程被OS领养
// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信// //孙子进程
// serviceIO(sock);
// close(sock);
// exit(0);
// }
// //父进程
// pid_t retwaitpid(id,nullptr,0);
// if(ret0)
// {
// cout waitsuccess: ret endl;
// }//多线程版
// pthread_t pid;
// ThreadData* thnew ThreadData(this,sock);
// pthread_create(pid,nullptr,start_routine,th);threadPoolTask::getthpptr()-push(Task(sock,serviceIO));}
}// static void* start_routine(void* args)
// {
// pthread_detach(pthread_self());
// ThreadData* retstatic_castThreadData*(args);
// ret-_this-serviceIO(ret-_psock);
// close(ret-_psock);
// delete ret;
// return nullptr;
// } // void serviceIO(int sock)
// {
// char inbuffer[NUM];
// while(true)
// {
// ssize_t nread(sock,inbuffer,sizeof(inbuffer)-1);
// if(n0)
// {
// inbuffer[n]0;
// coutrecv message: inbufferendl;
// string outbinbuffer;
// string outbufferoutb[server echo];// write(sock,outbuffer.c_str(),outbuffer.size());// }
// else
// {
// logMessage(NORMAL,client quit,i quit yep);
// break;
// }
// }// }~tcpserver(){}private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}1. 单进程版客户端串行版
#pragma once
#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include sys/wait.h
#include signal.h
#include pthread.h
#includelog.hpp
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR1,SOCK_ERR,BIND_ERR,LISTEN_ERR};class tcpserver
{public:tcpserver(const uint16_t port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensocksocket(AF_INET,SOCK_STREAM,0);
if(_listensock0)
{logMessage(FATAL,create listensocket error);exit(SOCK_ERR);
}logMessage(NORMAL, create socket success: %d, _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_familyAF_INET;
local.sin_porthtons(_port);
local.sin_addr.s_addrINADDR_ANY;
if(bind(_listensock,(struct sockaddr*)local,sizeof(local))0)//绑定失败
{logMessage(FATAL,bind error);exit(BIND_ERR);
}logMessage(NORMAL,bind success);
//3.将套接字设置为监听模式
if(listen(_listensock,0)0)
{logMessage(FATAL,listen error);exit(LISTEN_ERR);
}
logMessage(NORMAL,listen success);
}void start()
{while(true){struct sockaddr_in cli;socklen_t lensizeof(cli);bzero(cli,len);int sockaccept(_listensock,(struct sockaddr*)cli,len);if(sock0){logMessage(FATAL,accept client error);continue;}logMessage(NORMAL,accept client success);coutaccept sock: sockendl;serviceIO(sock);//客户端串行版close(sock);}
}void serviceIO(int sock)
{char inbuffer[NUM];while(true){ssize_t nread(sock,inbuffer,sizeof(inbuffer)-1);if(n0){inbuffer[n]0;coutrecv message: inbufferendl;string outbinbuffer;string outbufferoutb[server echo];write(sock,outbuffer.c_str(),outbuffer.size());}
else
{logMessage(NORMAL,client quit,i quit yep);break;
}}}~tcpserver(){}private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}注意客户端串行給服务器发数据是在哪里堵塞由于阻塞在accept函数处即accept等待客户端接入是阻塞式等待。accept函数接收了一个连接请求后后来的客户端连接请求需要在accept函数处等待当上一个客户端退出后服务器才能accept当前客户端发送来的连接请求成功才能接收当前客户端的数据。即服务器串行接收处理客户端发送来的数据
2. 多进程版客户端并行版 tcpserver.hpp #pragma once
#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include sys/wait.h
#include signal.h
#include pthread.h
#includelog.hpp
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR1,SOCK_ERR,BIND_ERR,LISTEN_ERR};
class tcpserver
{public:tcpserver(const uint16_t port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensocksocket(AF_INET,SOCK_STREAM,0);
if(_listensock0)
{logMessage(FATAL,create listensocket error);exit(SOCK_ERR);
}logMessage(NORMAL, create socket success: %d, _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_familyAF_INET;
local.sin_porthtons(_port);
local.sin_addr.s_addrINADDR_ANY;
if(bind(_listensock,(struct sockaddr*)local,sizeof(local))0)//绑定失败
{logMessage(FATAL,bind error);exit(BIND_ERR);
}logMessage(NORMAL,bind success);
//3.将套接字设置为监听模式
if(listen(_listensock,0)0)
{logMessage(FATAL,listen error);exit(LISTEN_ERR);
}
logMessage(NORMAL,listen success);
}void start()
{// signal(SIGCHLD, SIG_IGN);while(true){struct sockaddr_in cli;socklen_t lensizeof(cli);bzero(cli,len);int sockaccept(_listensock,(struct sockaddr*)cli,len);if(sock0){logMessage(FATAL,accept client error);continue;}logMessage(NORMAL,accept client success);coutaccept sock: sockendl;//多进程版---//一个客户端占用一个文件描述符原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的而每次客户端进来都要占用新的文件描述符//因此若接收多个客户端不退出的话文件描述符会越来越少。pid_t idfork();//创建子进程if(id0)//子进程进入{close(_listensock);//子进程不需要用于监听因此关闭该文件描述符if(fork()0) exit(0);
// //子进程创建孙子进程子进程直接退出让孙子进程担任IO任务且孙子进程成为孤儿进程被OS领养
// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信// //孙子进程
serviceIO(sock);
close(sock);
exit(0);}//父进程// close(sock);//父进程不使用文件描述符就关闭pid_t retwaitpid(id,nullptr,0);if(ret0){cout waitsuccess: ret endl;}}
}
void serviceIO(int sock)
{char inbuffer[NUM];while(true){ssize_t nread(sock,inbuffer,sizeof(inbuffer)-1);if(n0){inbuffer[n]0;coutrecv message: inbufferendl;string outbinbuffer;string outbufferoutb[server echo];write(sock,outbuffer.c_str(),outbuffer.size());}
else
{logMessage(NORMAL,client quit,i quit yep);break;
}}}
~tcpserver(){}
private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}父进程fork创建子进程创建完后waitpid等待回收子进程。子进程fork创建孙子进程创建完后直接退出。导致孙子进程成为孤儿进程进而被OS领养。因此除非客户端退出IO任务否则孤儿进程将一直运行下去不会干扰到其他进程即并行完成服务器和客户端的通信 注意的是服务器accept一次客户端的连接请求就需要申请一个文件描述符而文件描述符是有上限的如果大量的客户端请求连接成功并且不结束的话会造成文件描述符泄露。 因此在父进程那里需要关闭不使用的文件描述符 父进程这里回收子进程不能使用非阻塞等待原因在于非阻塞等待的本质是轮询而这里使用后会导致父进程会在accept函数处阻塞等待客户端发送连接请求那么父进程就无法回收子进程了。因此waitpid的返回值用ret接收等待回收成功就打印日志失败则跳过当子进程图退出或者被中止时子进程会发送17号信号SIGCHILD给父进程父进程可以通过忽略17号信号SIGCHILD的方式来不阻塞等待回收子进程这种方法对于linux环境可用其余不保证
signal(SIGCHLD, SIG_IGN);netstat查看网络信息
netstat 是一个用于查看网络连接和网络统计信息的命令行工具。它可以用来显示当前系统上的网络连接、路由表、接口统计信息等等。在 Linux 系统中netstat 命令的用法如下
netstat [options]一些常用的选项包括
-a显示所有的连接包括监听中和已建立的连接。-t显示 TCP 协议的连接。-u显示 UDP 协议的连接。-n以数字形式显示 IP 地址和端口号而不是尝试进行 DNS 解析。-p显示与连接关联的进程信息。-r显示路由表。-l仅显示监听中的连接。-atun显示所有的TCP和UDP连接 注意一下这里出现了两个连接,原因在于服务器和客户端在同一台主机上即服务器和客户端完成了本地环回因此能看到两个连接。
3.多线程版并行执行 tcpserver.hpp #pragma once
#include iostream
#include string
#include cstring
#include cstdlib
#include unistd.h
#include sys/types.h
#include sys/socket.h
#include netinet/in.h
#include arpa/inet.h
#include sys/wait.h
#include signal.h
#include pthread.h
#includelog.hpp
#define NUM 1024using namespace std;
namespace Server
{enum{USAGE_ERR1,SOCK_ERR,BIND_ERR,LISTEN_ERR};
class tcpserver;class ThreadData{
public:
ThreadData( tcpserver* self,int psock):_this(self),_psock(psock){}
tcpserver* _this;
int _psock;};class tcpserver
{public:tcpserver(const uint16_t port):_port(port),_listensock(-1){}void initserver()
{
//1.创建套接字
_listensocksocket(AF_INET,SOCK_STREAM,0);
if(_listensock0)
{logMessage(FATAL,create listensocket error);exit(SOCK_ERR);
}logMessage(NORMAL, create socket success: %d, _listensock);
//2.bind ip和port
struct sockaddr_in local;
local.sin_familyAF_INET;
local.sin_porthtons(_port);
local.sin_addr.s_addrINADDR_ANY;
if(bind(_listensock,(struct sockaddr*)local,sizeof(local))0)//绑定失败
{logMessage(FATAL,bind error);exit(BIND_ERR);
}logMessage(NORMAL,bind success);
//3.将套接字设置为监听模式
if(listen(_listensock,0)0)
{logMessage(FATAL,listen error);exit(LISTEN_ERR);
}
logMessage(NORMAL,listen success);
}void start()
{while(true){struct sockaddr_in cli;socklen_t lensizeof(cli);bzero(cli,len);int sockaccept(_listensock,(struct sockaddr*)cli,len);if(sock0){logMessage(FATAL,accept client error);continue;}logMessage(NORMAL,accept client success);coutaccept sock: sockendl;//多线程版
pthread_t pid;
ThreadData* thnew ThreadData(this,sock);
pthread_create(pid,nullptr,start_routine,th);}
}static void* start_routine(void* args)
{pthread_detach(pthread_self());//线程分离后让OS自动回收新线程ThreadData* retstatic_castThreadData*(args);ret-_this-serviceIO(ret-_psock);close(ret-_psock);delete ret;return nullptr;
} void serviceIO(int sock)
{char inbuffer[NUM];while(true){ssize_t nread(sock,inbuffer,sizeof(inbuffer)-1);if(n0){inbuffer[n]0;coutrecv message: inbufferendl;string outbinbuffer;string outbufferoutb[server echo];write(sock,outbuffer.c_str(),outbuffer.size());}
else
{logMessage(NORMAL,client quit,i quit yep);break;
}}}
~tcpserver(){}
private:
int _listensock;//用于监听服务器的sock文件描述符
uint16_t _port;//端口号
};}服务器接收一个客户端的连接请求就申请一个新线程多线程下可以让服务器接收多个线程
log.hpp
#pragma once#include iostream
#include string
#includectime
#include sys/types.h#include unistd.h#include stdio.h
#include stdarg.h
using namespace std;
#define DEBUG 0
#define NORMAL 1
#define WARNING 2
#define ERROR 3
#define FATAL 4#define NUM 1024
#define LOG_STR ./logstr.txt
#define LOG_ERR ./log.err
const char* to_str(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] [messge]// [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]// 暂定// std::cout message std::endl;char logprestr[NUM];
snprintf(logprestr,sizeof(logprestr),[%s][%ld][%d],to_str(level),(long int)time(nullptr),getpid());//把后面的内容打印进logprestr缓存区中char logeldstr[NUM];
va_list arg;
va_start(arg,format);
vsnprintf(logeldstr,sizeof(logeldstr),format,arg);//arg是logmessage函数列表中的...coutlogprestrlogeldstrendl;// FILE* strfopen(LOG_STR,a);
// FILE* errfopen(LOG_ERR,a);//以追加方式打开文件若文件不存在则创建// if(str!nullptr||err!nullptr)//两个文件指针都不为空则创建文件成功
// {
// FILE* ptrnullptr;
// if(levelDEBUG||levelNORMAL||levelWARNING)
// {
// ptrstr;
// }
// if(levelERROR||levelFATAL)
// {
// ptrerr;
// }
// if(ptr!nullptr)
// {
// fprintf(ptr,%s-%s\n,logprestr,logeldstr);
// }
// fclose(str);
// fclose(err);//}}可变参数列表 va_list是(char*)重命名的类型定义可以访问可变参数的变量。 va_start(ap, v) ap是定义的可变参数变量v是形参中可变参数前第一个参数名其作用是使ap指向可变参数部分。 va_arg(ap, t) ap是定义的可变参数变量t是可变参数的类型根据类型访问可变参数列表中的数据。 va_end(ap) ap是定义的可变参数变量使ap变量置空作为结束使用。 vsnprintf函数原型
#include stdio.h
int vsnprintf(char *str, size_t size, const char *format, va_list ap);str是一个指向字符数组缓冲区的指针用于存储格式化后的数据 size是缓冲区的大小限制了写入的最大字符数包括终止的 null 字符 format格式化字符串类似于 printf 函数中的格式化字符串 ap是一个 va_list 类型的变量用于存储可变参数列表的信息并且要注意OS对参数压栈的顺序是从右向左 vsnprintf 函数根据指定的 format 格式化字符串将数据写入 str 缓冲区但不会超出指定的缓冲区大小。它会在写入数据后自动在缓冲区末尾添加一个 null 终止字符确保结果是一个合法的 C 字符串。 守护进程
守护进程Daemon是在计算机系统中以后台方式运行的一类特殊进程。它通常在操作系统启动时被初始化并在整个系统运行期间保持活动状态不需要与用户交互。守护进程通常用于执行系统任务、服务管理以及提供后台服务如网络服务、定时任务等。
守护进程特点如下
后台运行守护进程在后台运行不与用户交互没有控制终端。独立性它通常独立于用户会话即使用户注销或关闭终端守护进程也会继续运行。没有标准输入输出守护进程通常没有标准输入和输出因为它们不与用户交互。它们通常将输出写入日志文件。分离自身守护进程会通过一系列操作来与终端、会话和控制组脱离连接以确保它不会意外地被控制终端关闭。
一个服务器中可以具有多个会话例如一个服务器上有一个root用户和多个普通用户当普通用户登录上服务器时即成为一个会话。
一个会话具有多个后台任务但只能具有一个前台任务bash。 jobs查看任务可以看到任务1是./tcpserver任务2是sleep 1000 | sleep 2000 | sleep 3000 任务3是sleep 4000 | sleep 5000 且三个任务后面都带在进程或任务后带作用是将该任务放到后台运行sleep 1000 、sleep 2000 、sleep 3000 、sleep 4000、sleep 5000的父进程都是16853即bash而 sleep 1000 、sleep 2000 、sleep 3000的PGID相同都是sleep 1000的pid即 sleep 1000 、sleep 2000 、sleep 3000属于同一组同一个组要协同起来完成同一个作业。第一个任务的pid是组长的pid即sleep 1000的pid而小组16858和小组17070的SID都是16853即这两个小组属于同一个会话bash要完成的是同一个任务
fg、bg fg 作业号将作业放到前台 bg 作业号将作业放到后台或者继续执行后台作业 ctrlZ将前台任务暂停并把作业放到后台 用户登录时服务器就需要为此创建一些后台作业和前台作业命令行来服务用户而用户注销或退出服务器也会影响其前台作业和后台作业。而服务器程序不能受到用户登录和注销的影响。我们可以使得服务器程序自成会话自成进程组那么该程序就与终端设备无关不能再收到用户登录和注销的影响了。该类进程被称为守护进程
setsid 在Unix和类Unix系统中setsid 是一个用于创建新会话的系统调用函数。会话Session是一组相关的进程组合通常由一个控制终端和一些子进程组成。setsid 函数的主要作用是将调用它的进程从当前会话中分离出来并创建一个新的会话。 #include unistd.h
pid_t setsid(void);创建新会话调用 setsid 的进程会成为一个新的会话的组长Session Leader。新会话不再与之前的控制终端相关联。但该进程在调用setsid函数之前不能是组长。分离终端调用 setsid 的进程不再与任何控制终端关联无法重新获得控制终端。成为新进程组的组长新会话中的第一个进程调用 setsid 的进程会成为新的进程组的组长。 daemon.hpp #pragma once#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);//选择忽略SIGPIPE信号// 2. 如何让自己不是组长setsid
if(fork()0)
exit(0);//父进程退出// 子进程 -- 守护进程精灵进程本质就是孤儿进程的一种
pid_t retsetsid();
assert(ret!-1);// 3. 守护进程是脱离终端的关闭或者重定向以前进程默认打开的文件
int fdopen(DEV,O_RDWR);
if(fd0)
{//dup2(oldfd,newfd):将oldfd的内容填充到newfd中这样输入到newfd的内容被重定向到oldfddup2(fd,0);dup2(fd,1);dup2(fd,2);
}else
{close(0);close(1);close(2);
}// 4. 可选进程执行路径发生更改
if(currPath) chdir(currPath);//更改currPath的路径
}/dev/null 是一个特殊的设备文件它被用作数据丢弃点向它写入的数据会被丢弃从它读取数据会立即返回EOFEnd of FileSIGPIPE的触发场景当一个进程向一个已经关闭写端的管道或者套接字写数据时、当进程向一个已经收到 RST 包连接重置的套接字发送数据时该进程就会向父进程发送SIGPIPE信号来进行进程终止。对SIGPIPE进行忽略行为避免了进程向/dev/null中写入数据并出现错误导致的进程终止父进程创建子进程父进程作为组长父进程退出后子进程能够自己成为组长即能够成为守护进程dup2(oldfd,newfd):将oldfd的内容填充到newfd中这样输入到newfd的内容被重定向到oldfd。在代码中是将输入文件描述符012的内容重定向到fd即/dev/null中 tcpserver.cc #includetcpserver.hpp
#includelog.hpp
#includedaemon.hpp
#includeiostream
#includestdlib.h
#includememory
using namespace Server;
using namespace std;static void Usage(string proc)
{cout\nUsage:\n\tproc local_port\n\nendl;
}int main(int argc,char* argv[])
{
if(argc!2)
{Usage(argv[0]);exit(USAGE_ERR);
}uint16_t portatoi(argv[1]);//将字符串转化为整数unique_ptrtcpserver ts(new tcpserver(port));
ts-initserver();
daemonSelf();
ts-start();return 0;
}