一、tcp套接字通信
1.多进程版本服务器
tcpserver.hpp:对服务端套接字相关的封装
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<string>
static uint16_t defaultport=3333;
int backlog=4;//设置服务器来连接connect listen上限
class sockmas
{
public:
int sockfd_;
//uint32_t clientip_;
//uint16_t clientport_;
std::string clientip_;
std::string clientport_;
};
class Server
{
public:
Server(uint16_t port=defaultport):port_(port),listenfd_(0)
{
}
int Init()
{
// 1.创建tcp套接字
listenfd_ = socket(AF_INET, SOCK_STREAM, 0); // tcp,面向字节流
if (listenfd_ < 0)
{
std::cout << "socket fail\n";
return 1;
}
std::cout << "socket success\n";
// 2.绑定IP地址和端口号
sockaddr_in servermas;
memset(&servermas, 0, sizeof(servermas));
servermas.sin_family = AF_INET;
// servermas.sin_addr.s_addr=htonl(std::stoi(serverip.c_str()));
// servermas.sin_addr.s_addr=inet_addr(serverip.c_str());//将字符串转为网络字节序//该方法bind失败
servermas.sin_addr.s_addr = INADDR_ANY;//任意地址绑定
servermas.sin_port = htons(port_);
socklen_t len = sizeof(servermas);
if (bind(listenfd_, (sockaddr *)&servermas, len) < 0)
{
std::cout << "bind fail\n";
return 2;
}
std::cout << "bind success\n";
int n = listen(listenfd_, backlog);
if (n < 0)
{
std::cout << "listen fail\n";
return 2;
}
std::cout << "listen success\n";
}
void Run()
{
//如果需要满足服务器为多个客户端建立链接,方案如下
//1.多进程
while (1)
{
sockaddr_in clientmas;
socklen_t addrlen = sizeof(clientmas);
int sockfd = accept(listenfd_, (sockaddr *)&clientmas, &addrlen); // 返回新的文件描述符,阻塞式等待connect
if (sockfd < 0)
{
std::cout << "accept fail\n";
std::cout<<"errno is:"<<errno<<"errno massage is:"<<strerror(errno);
sleep(5);
continue;
}
std::cout << "accept success\n";
int ret=fork();//创建子进程 ,由子进程负责通信,如进程只需要
if(ret==0)//子进程
{
close(listenfd_);
int n=fork();//由子进程再次创建子进程,子进程本身直接退出,让再次创建的子进程变为孤儿进程
//直接由系统负责管理,无需父进程等待回收资源
if(n>0)
{
exit(0);
}
//通信
sockmas mas;
mas.sockfd_ = sockfd;
mas.clientip_ = std::to_string(ntohl(clientmas.sin_addr.s_addr));
mas.clientport_ = std::to_string(ntohs(clientmas.sin_port));
Service(mas);
}
//父进程
close(sockfd);//父进程不再需要通信套接字
}
}
void Service(sockmas mas)
{
printf("clientip is %s,clientport is:%s\n", mas.clientip_.c_str(), mas.clientport_.c_str());
while(1)
{
char readbuff[128];
ssize_t n = read(mas.sockfd_, readbuff, sizeof(readbuff));
if (n < 0)
{
std::cout << "server read fail\n";
break;
}
else if (n == 0) // 如果客户端退出,也即通信双方写段关闭,服务器这里的读端会一直读到0
{
std::cout << "client already exit\n";
break;
}
readbuff[n] = '\0';
std::cout << "client say:" << readbuff << std::endl;
std::string writebuff(readbuff);//服务器仅仅回显客户端发送的信息
//getline(std::cin, writebuff);//服务器自主输入
n = write(mas.sockfd_, writebuff.c_str(), writebuff.size());
if (n < 0)
{
std::cout << "write fail\n";
continue;
}
//std::cout << "server say:" << writebuff << std::endl;
}
}
public:
int listenfd_;
uint16_t port_;
};
server.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<string>
#include"tcpserver.hpp"
void Usage(const std::string &proc)
{
std::cout << "\n\rUsage: " << proc << " serverport\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t serverport=std::stoi(argv[1]);
Server ser(serverport);
ser.Init();
ser.Run();
return 0;
}
client.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
//本身为uint32_t 点分10进制,但习惯以字符输入
void Usage(const std::string &proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
void* rstart_route(void* args)//读线程
{
int *sockfd = static_cast<int *>(args);
pthread_detach(pthread_self());
while(1)
{
char readbuff[128];
int n = read(*sockfd, readbuff, sizeof(readbuff));
if (n < 0)
{
std::cout << "read fail\n";
break;
}
else if(n==0)
{
std::cout << "read fail\n";
break;
}
readbuff[n] = '\0';
std::cout << "server say:";
std::cout << readbuff << std::endl;
// std::cout<<"client read success\n";
}
close(*sockfd);
return nullptr;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip=argv[1];
uint16_t serverport=std::stoi(argv[2]);//
//1.创建tcp套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cout<<"client socket fail\n";
return 1;
}
std::cout<<"client socket success\n";
//2.客户端的绑定会在connect时自动绑定
//3.连接服务器
// sockaddr_in servermas;
// memset(&servermas,0,sizeof(servermas));
// servermas.sin_family=AF_INET;
// servermas.sin_addr.s_addr=htonl((std::stoi(serverip.c_str())));
// servermas.sin_port=htons(serverport);
struct sockaddr_in servermas;
memset(&servermas,0,sizeof(servermas));
servermas.sin_family=AF_INET;
//servermas.sin_addr.s_addr=inet_addr(serverip.c_str());//该方法会绑定成功
//uint32_t ip=std::stoi(serverip.c_str());
// servermas.sin_addr.s_addr=htonl(ip);//stoi就是string到int,该方法绑定失败
inet_pton(AF_INET, serverip.c_str(), &(servermas.sin_addr));//该方法会绑定成功
servermas.sin_port=htons(serverport);
socklen_t addrlen=sizeof(servermas);
int n=connect(sockfd,(struct sockaddr*)&servermas,addrlen);
if(n<0)
{
std::cout<<"client connect fail\n";
return 2;
}
std::cout<<"client connect success\n";
//4 通信:多线程
// 主线程负责写,新建线程负责读
pthread_t rid;
pthread_create(&rid,nullptr,rstart_route,&sockfd);
while (1)
{
std::string writebuff;
getline(std::cin, writebuff);
ssize_t n = write(sockfd, writebuff.c_str(), writebuff.size());
if (n < 0)
{
std::cout << "write fail\n";
continue;
}
std::cout << "client say:"<<writebuff<<std::endl;
}
return 0;
}
效果展示:一台云服务器的服务器端可以与多台服务器上的多个客户端提示进行网络通信。缺点是服务器端创造多进程的代价太高。
2.多线程版本服务器
tcpserver.hpp
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<string>
static uint16_t defaultport=3333;
int backlog=4;//设置服务器来连接connect listen上限
class sockmas
{
public:
int sockfd_;
//uint32_t clientip_;
//uint16_t clientport_;
std::string clientip_;
std::string clientport_;
};
class Server
{
public:
Server(uint16_t port=defaultport):port_(port),listenfd_(0)
{
}
int Init()
{
// 1.创建tcp套接字
listenfd_ = socket(AF_INET, SOCK_STREAM, 0); // tcp,面向字节流
if (listenfd_ < 0)
{
std::cout << "socket fail\n";
return 1;
}
std::cout << "socket success\n";
// 2.绑定IP地址和端口号
sockaddr_in servermas;
memset(&servermas, 0, sizeof(servermas));
servermas.sin_family = AF_INET;
// servermas.sin_addr.s_addr=htonl(std::stoi(serverip.c_str()));
// servermas.sin_addr.s_addr=inet_addr(serverip.c_str());//将字符串转为网络字节序//该方法bind失败
servermas.sin_addr.s_addr = INADDR_ANY;//任意地址绑定
servermas.sin_port = htons(port_);
socklen_t len = sizeof(servermas);
if (bind(listenfd_, (sockaddr *)&servermas, len) < 0)
{
std::cout << "bind fail\n";
return 2;
}
std::cout << "bind success\n";
int n = listen(listenfd_, backlog);
if (n < 0)
{
std::cout << "listen fail\n";
return 2;
}
std::cout << "listen success\n";
}
static void* start_route(void* args)//多线程版本需要
{
pthread_detach(pthread_self());
sockmas* mas=static_cast<sockmas*>(args);
printf("clientip is %s,clientport is:%s\n", mas->clientip_.c_str(), mas->clientport_.c_str());
while (1)
{
char readbuff[128];
ssize_t n = read(mas->sockfd_, readbuff, sizeof(readbuff));
if (n < 0)
{
std::cout << "server read fail\n";
break;
}
else if (n == 0) // 如果客户端退出,也即通信双方写段关闭,服务器这里的读端会一直读到0
{
std::cout << "client already exit\n";
break;
}
readbuff[n] = '\0';
std::cout << "client say:" << readbuff << std::endl;
std::string writebuff(readbuff); // 服务器仅仅回显客户端发送的信息
// getline(std::cin, writebuff);//服务器自主输入
n = write(mas->sockfd_, writebuff.c_str(), writebuff.size());
if (n < 0)
{
std::cout << "write fail\n";
continue;
}
// std::cout << "server say:" << writebuff << std::endl;
}
}
void Run()
{
//2.多线程版本
while (1)
{
sockaddr_in clientmas;
socklen_t addrlen = sizeof(clientmas);
int sockfd = accept(listenfd_, (sockaddr *)&clientmas, &addrlen); // 返回新的文件描述符,阻塞式等待connect
if (sockfd < 0)
{
std::cout << "accept fail\n";
std::cout << "errno is:" << errno << "errno massage is:" << strerror(errno);
sleep(5);
continue;
}
std::cout << "accept success\n";
sockmas* mas=new sockmas;
mas->sockfd_ = sockfd;
mas->clientip_ = std::to_string(ntohl(clientmas.sin_addr.s_addr));
mas->clientport_ = std::to_string(ntohs(clientmas.sin_port));
pthread_t tid;
pthread_create(&tid,nullptr,start_route,mas);
}
}
public:
int listenfd_;
uint16_t port_;
};
多线程版本的服务器和多进程版本的服务器的区别并不大,只不过创建线程的代价要远远小于创建进程。只有tcpserver.hpp有所不同
3.线程池版本服务器+服务器守护进程化
服务器端相关
ThreadPool.hpp
#pragma once
#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include <unistd.h>
struct ThreadInfo //设置结构体储存每个线程的信息,包括线程id
{
pthread_t tid;
std::string name;//仅仅作为不同线程的区分
};
static const int defalutnum = 10;//线程数量上限设置
template <class T>
class ThreadPool
{
public:
void Lock()//加锁,保证互斥性
{
pthread_mutex_lock(&mutex_);
}
void Unlock()//解锁,保证互斥性
{
pthread_mutex_unlock(&mutex_);
}
void Wakeup()//条件变量下等待,保证顺序性
{
pthread_cond_signal(&cond_);
}
void ThreadSleep()//唤醒等待在条件变量下等待的线程,保证顺序性
{
pthread_cond_wait(&cond_, &mutex_);
}
bool IsQueueEmpty()
{
return tasks_.empty();
}
std::string GetThreadName(pthread_t tid)//获取与线程tid对应的线程名name
{
for (const auto &ti : threads_)
{
if (ti.tid == tid)
return ti.name;
}
return "None";
}
public:
static void *HandlerTask(void *args)//线程执行函数入口
{
ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
std::string name = tp->GetThreadName(pthread_self());
while (true)
{
tp->Lock();//加锁
//队列为空时,消费者不能消费
while (tp->IsQueueEmpty())//防止伪唤醒,所以是循环判断,
{
tp->ThreadSleep();
}
T t = tp->Pop();
tp->Unlock();
t();//重载,函数实现的是服务器端线程提供一次服务
}
}
void Start()//创建线程池,上限设置为10,创建的线程的角色都是消费者,生产者由主函数充当
{
int num = threads_.size();
for (int i = 0; i < num; i++)
{
threads_[i].name = "thread-" + std::to_string(i + 1);
pthread_create(&(threads_[i].tid), nullptr, HandlerTask, this);
}
}
T Pop()
{
T t = tasks_.front();
tasks_.pop();
return t;
}
void Push(const T &t)
{
Lock();
tasks_.push(t);
Wakeup();
Unlock();
}
static ThreadPool<T> *GetInstance()//单例模式,通过此函数创建唯一的类对象
{
if (nullptr == tp_) // ???
{
pthread_mutex_lock(&lock_);//防止创建多个类对象,加锁保护
if (nullptr == tp_)
{
///std::cout << "log: singleton create done first!" << std::endl;
tp_ = new ThreadPool<T>();
}
pthread_mutex_unlock(&lock_);
}
return tp_;
}
private:
ThreadPool(int num = defalutnum) : threads_(num)//互斥锁和条件变量初始化
{
pthread_mutex_init(&mutex_, nullptr);
pthread_cond_init(&cond_, nullptr);
}
~ThreadPool()//互斥锁和条件变量的释放
{
pthread_mutex_destroy(&mutex_);
pthread_cond_destroy(&cond_);
}
private:
std::vector<ThreadInfo> threads_;//储存线程池信息的数组
std::queue<T> tasks_;//任务仓库,临界区,生产者添加任务,消费者取走任务
pthread_mutex_t mutex_;//互斥锁,保证互斥访问任务仓库这一临界区
pthread_cond_t cond_;//条件变量,创建的线程角色都是嗷嗷待哺的消费者,所以仅需在一个条件变量下等待
static ThreadPool<T> *tp_;//单例模式,指向唯一创建对象的指针
static pthread_mutex_t lock_;//单例模式,static成员变量,类内声明,类外定义
};
template <class T>
ThreadPool<T> *ThreadPool<T>::tp_ = nullptr;
template <class T>
pthread_mutex_t ThreadPool<T>::lock_ = PTHREAD_MUTEX_INITIALIZER;
Task.hpp
#pragma once
#include <iostream>
#include <string>
#include<unistd.h>
#include<string.h>
class Task
{
public:
Task(int sockfd, const std::string &clientip, const uint16_t &clientport)
: sockfd_(sockfd), clientip_(clientip), clientport_(clientport)
{
}
Task()
{
}
void run()//线程和客户端通信,短服务,服务器端仅仅提供一次服务,之后就关闭通信套接字文件描述符
{
// 测试代码
while(1)//去掉循环是短服务
{
char buffer[4096];
// Tcp是面向字节流的,你怎么保证,你读取上来的数据,是"一个" "完整" 的报文呢?//协议规定,序列化和反序列化
ssize_t n = read(sockfd_, buffer, sizeof(buffer)); // BUG?
if (n > 0)
{
buffer[n] = 0;
std::cout << "client key# " << buffer << std::endl;
std::string echo_string(buffer);//回显
n = write(sockfd_, echo_string.c_str(), echo_string.size()); // 100 fd 不存在
if (n < 0)
{
std::cout << "write error, errno is:" << errno << "errstring is:" << strerror(errno) << std::endl;
}
}
else if (n == 0)
{
std::cout << "client exit"
<< "clientip is :" << clientip_.c_str() << "clientport is:" << clientport_
<< "sockfd is:" << sockfd_ << std::endl;
break;
}
else
{
std::cout << "read error"
<< "clientip is :" << clientip_.c_str() << "clientport is:" << clientport_
<< "sockfd is:" << sockfd_ << std::endl;
break;
}
}
close(sockfd_);
}
void operator()()
{
run();
}
~Task()
{
}
private:
int sockfd_;//通信文件描述符
std::string clientip_;//客户端ip
uint16_t clientport_;//客户端端口号
};
Daemon.hpp
#pragma once
#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <signal.h>
#include <string>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
const std::string nullfile = "/dev/null";
void Daemon(const std::string &cwd = "")
{
// 1. 忽略其他异常信号
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGSTOP, SIG_IGN);
// 2. 将自己变成独立的会话
if (fork() > 0)
exit(0);
setsid();
// 3. 更改当前调用进程的工作目录
if (!cwd.empty())
chdir(cwd.c_str());
// 4. 标准输入,标准输出,标准错误重定向至/dev/null
int fd = open(nullfile.c_str(), O_RDWR);
if(fd > 0)
{
dup2(fd, 0);
dup2(fd, 1);
dup2(fd, 2);
close(fd);
}
}
tcpserver.hpp
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<string>
#include"ThreadPool.hpp"
#include"Task.hpp"
#include"Daemon.hpp"//守护进程实现
static uint16_t defaultport=3333;
int backlog=4;//设置服务器来连接connect listen上限
class sockmas
{
public:
int sockfd_;
//uint32_t clientip_;
//uint16_t clientport_;
std::string clientip_;
std::string clientport_;
};
class Server
{
public:
Server(uint16_t port=defaultport):port_(port),listenfd_(0)
{
}
int Init()
{
// 1.创建tcp套接字
listenfd_ = socket(AF_INET, SOCK_STREAM, 0); // tcp,面向字节流
if (listenfd_ < 0)
{
std::cout << "socket fail\n";
return 1;
}
//std::cout << "socket success\n";
// 2.绑定IP地址和端口号
sockaddr_in servermas;
memset(&servermas, 0, sizeof(servermas));
servermas.sin_family = AF_INET;
// servermas.sin_addr.s_addr=htonl(std::stoi(serverip.c_str()));
// servermas.sin_addr.s_addr=inet_addr(serverip.c_str());//将字符串转为网络字节序//该方法bind失败
servermas.sin_addr.s_addr = INADDR_ANY;//任意地址绑定
servermas.sin_port = htons(port_);
socklen_t len = sizeof(servermas);
if (bind(listenfd_, (sockaddr *)&servermas, len) < 0)
{
std::cout << "bind fail\n";
return 2;
}
//std::cout << "bind success\n";
int n = listen(listenfd_, backlog);
if (n < 0)
{
std::cout << "listen fail\n";
return 2;
}
//std::cout << "listen success\n";
}
void Start()
{
Daemon();//守护进程实现
//主线程已经结束运行,返回会有创建的子进程,也是会话执行后续代码
//3.线程池版本
ThreadPool<Task>::GetInstance()->Start();//创建线程池对象(单例模式),并创建线程池
while (1)
{
sockaddr_in clientmas;
socklen_t addrlen = sizeof(clientmas);
int sockfd = accept(listenfd_, (sockaddr *)&clientmas, &addrlen); // 返回新的文件描述符,阻塞式等待connect
if (sockfd < 0)
{
std::cout << "accept fail\n";
std::cout << "errno is:" << errno << "errno massage is:" << strerror(errno);
sleep(5);
continue;
}
std::cout << "accept success\n";
uint16_t clientport=ntohs(clientmas.sin_port);
std::string clientip=std::to_string(ntohl(clientmas.sin_addr.s_addr));
Task t(sockfd,clientip,clientport);//根据连接线程创建任务
ThreadPool<Task>::GetInstance()->Push(t);//相当于充当生产者
}
}
public:
int listenfd_;
uint16_t port_;
};
server.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
#include<string>
#include"tcpserver.hpp"
void Usage(const std::string &proc)
{
std::cout << "\n\rUsage: " << proc << " serverport\n"
<< std::endl;
}
int main(int argc, char *argv[])
{
if (argc != 2)
{
Usage(argv[0]);
exit(1);
}
uint16_t serverport=std::stoi(argv[1]);
Server ser(serverport);
ser.Init();
ser.Start();
return 0;
}
客户端相关
client.cc
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>//sockaddr_in所在
#include<arpa/inet.h>
#include<string.h>
#include<unistd.h>
//本身为uint32_t 点分10进制,但习惯以字符输入
void Usage(const std::string &proc)
{
std::cout << "\n\rUsage: " << proc << " serverip serverport\n"
<< std::endl;
}
void* rstart_route(void* args)//读线程
{
int *sockfd = static_cast<int *>(args);
pthread_detach(pthread_self());
while(1)
{
char readbuff[128];
int n = read(*sockfd, readbuff, sizeof(readbuff));
if (n < 0)
{
std::cout << "read fail\n";
break;
}
else if(n==0)
{
std::cout << "本次服务到此结束\n";
break;
}
readbuff[n] = '\0';
std::cout << "server say:";
std::cout << readbuff << std::endl;
// std::cout<<"client read success\n";
}
close(*sockfd);
return nullptr;
}
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
std::string serverip=argv[1];
uint16_t serverport=std::stoi(argv[2]);//
//1.创建tcp套接字
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
std::cout<<"client socket fail\n";
return 1;
}
std::cout<<"client socket success\n";
//2.客户端的绑定会在connect时自动绑定
//3.连接服务器
// sockaddr_in servermas;
// memset(&servermas,0,sizeof(servermas));
// servermas.sin_family=AF_INET;
// servermas.sin_addr.s_addr=htonl((std::stoi(serverip.c_str())));
// servermas.sin_port=htons(serverport);
struct sockaddr_in servermas;
memset(&servermas,0,sizeof(servermas));
servermas.sin_family=AF_INET;
//servermas.sin_addr.s_addr=inet_addr(serverip.c_str());//该方法会绑定成功
//uint32_t ip=std::stoi(serverip.c_str());
// servermas.sin_addr.s_addr=htonl(ip);//stoi就是string到int,该方法绑定失败
inet_pton(AF_INET, serverip.c_str(), &(servermas.sin_addr));//该方法会绑定成功
servermas.sin_port=htons(serverport);
socklen_t addrlen=sizeof(servermas);
int n=connect(sockfd,(struct sockaddr*)&servermas,addrlen);
if(n<0)
{
std::cout<<"client connect fail\n";
return 2;
}
std::cout<<"client connect success\n";
//4 通信:多线程
// 主线程负责写,新建线程负责读
pthread_t rid;
pthread_create(&rid,nullptr,rstart_route,&sockfd);
while (1)
{
std::string writebuff;
getline(std::cin, writebuff);
ssize_t n = write(sockfd, writebuff.c_str(), writebuff.size());
if (n < 0)
{
std::cout << "write fail\n";
break;
}
std::cout << "client say:"<<writebuff<<std::endl;
}
close(sockfd);
return 0;
}
4.关于守护进程
守护进程的实现效果是,服务器运行之后,能够一直提供服务,不会被ctrl+c或者因为终端与云服务器断开连接而终止运行。
不受终端和硬件信号影响的进程被称为守护进程。
不受硬件信号影响只要保证是后台任务即可。
守护进程的名字一般以d结尾。
如何实现守护进程?
自成进程组自成会话的进程会成为守护进程。
5.关于会话
每一个会话存在一个会话id,会话id和进程组id相同。进程组id是第一个进程的id。
每一个会话中只存在两种任务,分别是前台任务和后台任务,每个任务由一个或者多个进程组完成。
能收到键盘信息的被称为前台任务,反之则是后台任务,守护进程本身也是后台任务。
每个会话只能有一个前台任务,会话创建时,bash进程就是默认的前台任务对应的前台任务进程组,之后创建的进程属于后台任务对应的后台任务进程组。
在可执行程序运行时,可以通过&让其成为前台任务,此时,原本的前台任务bash就会变为后台任务。而前台任务可以通过键盘信号终止。如下
而如果以后台任务的方式运行,就不会受到键盘信号的影响。如下
同时,每有一个回台任务,都会为其分配一个后台任务号,任务号后的是该任务进程组的id。如下
fg 后台任务号 可以将对应的后台任务变为前台任务
bg 任务号 将一个在后台暂停的任务,变成继续执行
6.关于setsid函数
创建会话使用函数如下
创建会话的进程一定不能是进程组的第一个进程,所以需要fork创建子进程,由子进程创建会话,让该进程成为独立会话独立进程组的守护进程。
之所以重定向,是不想让服务器启动后的消息显示在命令行,而是重定向到了linux默认存在的垃圾桶/dev/null中