void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
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_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_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
#include
int socket(int domain, int type, int protocol);
domain
表示协议族,常用的有 AF_INET
(IPv4)和 AF_INET6
(IPv6)。
type
表示Socket类型,常用的有 SOCK_STREAM
(TCP)和 SOCK_DGRAM
(UDP)。
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
表示等待连接队列的最大长度。总的来说initserver函数作用是先创建套接字,然后填充指定的端口号和ip,并将套接字设置为监听状态
void start()
{
while(true)
{
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
bzero(&cli,len);
int sock=accept(_listensock,(struct sockaddr*)&cli,&len);
if(sock<0)
{
logMessage(FATAL,"accept client error");
continue;
}
logMessage(NORMAL,"accept client success");
cout<<"accept sock: "<<sock<<endl;
}
accept函数原型
#include
#include
int 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
#include
#include
#include
#include"tcpclient.hpp"
using namespace std;
using namespace client;
static void Usage(string proc)
{
cout<<"\nUsage :\n\t"<<proc<<" serverip serverport\n"<<endl;
}
int main(int argc, char* argv[])
{
if(argc!=3)
{
Usage(argv[0]);
exit(1);
}
string serverip=argv[1];
uint16_t serverport=atoi(argv[2]);
unique_ptr<tcpclient> tc(new tcpclient(serverip,serverport));
tc->initclient();
tc->start();
return 0;
}
tcpclient.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
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
_sock=socket(AF_INET,SOCK_STREAM,0);
if(_sock<0)
{
cerr<<"socket create error"<<endl;
exit(2);
}
//2.绑定 ip port,不显示绑定,OS自动绑定
}
void start()
{
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
socklen_t len=sizeof(ser);
ser.sin_family=AF_INET;
ser.sin_port=htons(_port);
ser.sin_addr.s_addr=inet_addr(_ip.c_str());
if(connect(_sock,(struct sockaddr *)&ser,len)!=0)
{
cerr<<"connect error"<<endl;
}else
{
string msg;
while(true)
{
cout<<"Enter# ";
getline(cin,msg);
write(_sock,msg.c_str(),msg.size());
char inbuffer[NUM];
int n=read(_sock,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
cout<<"server return :"<<inbuffer<<endl;
}else
{
break;
}
}
}
}
~tcpclient()
{
if(_sock>=0) close(_sock);
}
private:
int _sock;
uint16_t _port;
string _ip;
};
}
tcpserver.cc
#include"tcpserver.hpp"
#include"log.hpp"
#include
#include
#include
using namespace Server;
using namespace std;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);//将字符串转化为整数
unique_ptr<tcpserver> ts(new tcpserver(port));
ts->initserver();
ts->start();
return 0;
}
tcpserver.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"log.hpp"
#define NUM 1024
using namespace std;
namespace Server
{
enum
{
USAGE_ERR=1,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.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
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_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_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);
threadPool<Task>::getthpptr()->run();
while(true)
{
struct sockaddr_in cli;
socklen_t len=sizeof(cli);
bzero(&cli,len);
int sock=accept(_listensock,(struct sockaddr*)&cli,&len);
if(sock<0)
{
logMessage(FATAL,"accept client error");
continue;
}
logMessage(NORMAL,"accept client success");
cout<<"accept sock: "<<sock<<endl;
// serviceIO(sock);//客户端串行版
// close(sock);
//多进程版---
//一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符
//因此若接收多个客户端不退出的话文件描述符会越来越少。
// pid_t id=fork();//创建子进程
// if(id==0)//子进程进入
// {
// close(_listensock);//子进程不需要用于监听因此关闭该文件描述符
// if(fork()>0) exit(0);
// //子进程创建孙子进程,子进程直接退出,让孙子进程担任IO任务,且孙子进程成为孤儿进程被OS领养,
// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信
// //孙子进程
// serviceIO(sock);
// close(sock);
// exit(0);
// }
// //父进程
// pid_t ret=waitpid(id,nullptr,0);
// if(ret<0)
// {
// cout << "waitsuccess: " << ret << endl;
// }
//多线程版
// pthread_t pid;
// ThreadData* th=new ThreadData(this,sock);
// pthread_create(&pid,nullptr,start_routine,th);
threadPool<Task>::getthpptr()->push(Task(sock,serviceIO));
}
}
// static void* start_routine(void* args)
// {
// pthread_detach(pthread_self());
// ThreadData* ret=static_cast(args);
// ret->_this->serviceIO(ret->_psock);
// close(ret->_psock);
// delete ret;
// return nullptr;
// }
// void serviceIO(int sock)
// {
// char inbuffer[NUM];
// while(true)
// {
// ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);
// if(n>0)
// {
// inbuffer[n]=0;
// cout<<"recv message: "<
// string outb=inbuffer;
// string outbuffer=outb+"[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;//端口号
};
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"log.hpp"
#define NUM 1024
using namespace std;
namespace Server
{
enum
{
USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR
};
class tcpserver
{
public:
tcpserver(const uint16_t& port):_port(port),_listensock(-1){}
void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
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_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_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 len=sizeof(cli);
bzero(&cli,len);
int sock=accept(_listensock,(struct sockaddr*)&cli,&len);
if(sock<0)
{
logMessage(FATAL,"accept client error");
continue;
}
logMessage(NORMAL,"accept client success");
cout<<"accept sock: "<<sock<<endl;
serviceIO(sock);//客户端串行版
close(sock);
}
}
void serviceIO(int sock)
{
char inbuffer[NUM];
while(true)
{
ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[n]=0;
cout<<"recv message: "<<inbuffer<<endl;
string outb=inbuffer;
string outbuffer=outb+"[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当前客户端发送来的连接请求成功,才能接收当前客户端的数据。即服务器串行接收处理客户端发送来的数据
tcpserver.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"log.hpp"
#define NUM 1024
using namespace std;
namespace Server
{
enum
{
USAGE_ERR=1,SOCK_ERR,BIND_ERR,LISTEN_ERR
};
class tcpserver
{
public:
tcpserver(const uint16_t& port):_port(port),_listensock(-1){}
void initserver()
{
//1.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
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_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_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 len=sizeof(cli);
bzero(&cli,len);
int sock=accept(_listensock,(struct sockaddr*)&cli,&len);
if(sock<0)
{
logMessage(FATAL,"accept client error");
continue;
}
logMessage(NORMAL,"accept client success");
cout<<"accept sock: "<<sock<<endl;
//多进程版---
//一个客户端占用一个文件描述符,原因在于孙子进程执行IO任务需要占用独立的文件描述符,而文件描述符是继承父进程的,而每次客户端进来都要占用新的文件描述符
//因此若接收多个客户端不退出的话文件描述符会越来越少。
pid_t id=fork();//创建子进程
if(id==0)//子进程进入
{
close(_listensock);//子进程不需要用于监听因此关闭该文件描述符
if(fork()>0) exit(0);
// //子进程创建孙子进程,子进程直接退出,让孙子进程担任IO任务,且孙子进程成为孤儿进程被OS领养,
// //除非客户端退出IO任务结束否则该孤儿进程一直运行下去不会相互干扰,即并行完成服务器和客户端的通信
// //孙子进程
serviceIO(sock);
close(sock);
exit(0);
}
//父进程
// close(sock);//父进程不使用文件描述符就关闭
pid_t ret=waitpid(id,nullptr,0);
if(ret<0)
{
cout << "waitsuccess: " << ret << endl;
}
}
}
void serviceIO(int sock)
{
char inbuffer[NUM];
while(true)
{
ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[n]=0;
cout<<"recv message: "<<inbuffer<<endl;
string outb=inbuffer;
string outbuffer=outb+"[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一次客户端的连接请求,就需要申请一个文件描述符,而文件描述符是有上限的,如果大量的客户端请求连接成功并且不结束的话,会造成文件描述符泄露。
因此在父进程那里需要关闭不使用的文件描述符
signal(SIGCHLD, SIG_IGN);
netstat
是一个用于查看网络连接和网络统计信息的命令行工具。它可以用来显示当前系统上的网络连接、路由表、接口统计信息等等。在 Linux 系统中,netstat
命令的用法如下:
netstat [options]
一些常用的选项包括:
-a
:显示所有的连接,包括监听中和已建立的连接。-t
:显示 TCP 协议的连接。-u
:显示 UDP 协议的连接。-n
:以数字形式显示 IP 地址和端口号,而不是尝试进行 DNS 解析。-p
:显示与连接关联的进程信息。-r
:显示路由表。-l
:仅显示监听中的连接。-atun
:显示所有的TCP和UDP连接注意一下:这里出现了两个连接,原因在于服务器和客户端在同一台主机上,即服务器和客户端完成了本地环回,因此能看到两个连接。
tcpserver.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include"log.hpp"
#define NUM 1024
using namespace std;
namespace Server
{
enum
{
USAGE_ERR=1,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.创建套接字
_listensock=socket(AF_INET,SOCK_STREAM,0);
if(_listensock<0)
{
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_family=AF_INET;
local.sin_port=htons(_port);
local.sin_addr.s_addr=INADDR_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 len=sizeof(cli);
bzero(&cli,len);
int sock=accept(_listensock,(struct sockaddr*)&cli,&len);
if(sock<0)
{
logMessage(FATAL,"accept client error");
continue;
}
logMessage(NORMAL,"accept client success");
cout<<"accept sock: "<<sock<<endl;
//多线程版
pthread_t pid;
ThreadData* th=new ThreadData(this,sock);
pthread_create(&pid,nullptr,start_routine,th);
}
}
static void* start_routine(void* args)
{
pthread_detach(pthread_self());//线程分离后让OS自动回收新线程
ThreadData* ret=static_cast<ThreadData*>(args);
ret->_this->serviceIO(ret->_psock);
close(ret->_psock);
delete ret;
return nullptr;
}
void serviceIO(int sock)
{
char inbuffer[NUM];
while(true)
{
ssize_t n=read(sock,inbuffer,sizeof(inbuffer)-1);
if(n>0)
{
inbuffer[n]=0;
cout<<"recv message: "<<inbuffer<<endl;
string outb=inbuffer;
string outbuffer=outb+"[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;//端口号
};
}
#pragma once
#include
#include
#include
#include
#include
#include
#include
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函数列表中的...
cout<<logprestr<<logeldstr<<endl;
// FILE* str=fopen(LOG_STR,"a");
// FILE* err=fopen(LOG_ERR,"a");//以追加方式打开文件,若文件不存在则创建
// if(str!=nullptr||err!=nullptr)//两个文件指针都不为空则创建文件成功
// {
// FILE* ptr=nullptr;
// if(level==DEBUG||level==NORMAL||level==WARNING)
// {
// ptr=str;
// }
// if(level==ERROR||level==FATAL)
// {
// ptr=err;
// }
// 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
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)。
fg 作业号:将作业放到前台
bg 作业号:将作业放到后台,或者继续执行后台作业
ctrl+Z将前台任务暂停并把作业放到后台
在Unix和类Unix系统中,
setsid
是一个用于创建新会话的系统调用函数。会话(Session)是一组相关的进程组合,通常由一个控制终端和一些子进程组成。setsid
函数的主要作用是将调用它的进程从当前会话中分离出来,并创建一个新的会话。
#include
pid_t setsid(void);
setsid
的进程会成为一个新的会话的组长(Session Leader)。新会话不再与之前的控制终端相关联。但该进程在调用setsid函数之前不能是组长。setsid
的进程不再与任何控制终端关联,无法重新获得控制终端。setsid
的进程)会成为新的进程组的组长。daemon.hpp
#pragma once
#include
#include
#include
#include
#include
#include
#include
#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 ret=setsid();
assert(ret!=-1);
// 3. 守护进程是脱离终端的,关闭或者重定向以前进程默认打开的文件
int fd=open(DEV,O_RDWR);
if(fd>=0)
{
//dup2(oldfd,newfd):将oldfd的内容填充到newfd中,这样输入到newfd的内容被重定向到oldfd
dup2(fd,0);
dup2(fd,1);
dup2(fd,2);
}else
{
close(0);
close(1);
close(2);
}
// 4. 可选:进程执行路径发生更改
if(currPath) chdir(currPath);//更改currPath的路径
}
/dev/null
是一个特殊的设备文件,它被用作数据丢弃点,向它写入的数据会被丢弃,从它读取数据会立即返回EOF(End of File)RST
包(连接重置)的套接字发送数据时,该进程就会向父进程发送SIGPIPE信号来进行进程终止。对SIGPIPE进行忽略行为避免了进程向/dev/null中写入数据并出现错误导致的进程终止tcpserver.cc
#include"tcpserver.hpp"
#include"log.hpp"
#include"daemon.hpp"
#include
#include
#include
using namespace Server;
using namespace std;
static void Usage(string proc)
{
cout<<"\nUsage:\n\t"<<proc<<" local_port\n\n"<<endl;
}
int main(int argc,char* argv[])
{
if(argc!=2)
{
Usage(argv[0]);
exit(USAGE_ERR);
}
uint16_t port=atoi(argv[1]);//将字符串转化为整数
unique_ptr<tcpserver> ts(new tcpserver(port));
ts->initserver();
daemonSelf();
ts->start();
return 0;
}