当前位置: 首页 > news >正文

网站建设域名注册wordpress定制

网站建设域名注册,wordpress定制,百度网页版登录,海外营销公司文章目录 #x1f4d6; 前言1. 服务端基本结构1.1 类成员变量#xff1a;1.2 头文件1.3 初始化#xff1a;1.3 - 1 全双工与半双工1.3 - 2 inet_aton1.3 - 3 listen 2. 服务端运行接口2.1 accept#xff1a;2.2 服务接口#xff1a; 3. 客户端3.1 connect#xff1a;3.2 … 文章目录 前言1. 服务端基本结构1.1 类成员变量1.2 头文件1.3 初始化1.3 - 1 全双工与半双工1.3 - 2 inet_aton1.3 - 3 listen 2. 服务端运行接口2.1 accept2.2 服务接口 3. 客户端3.1 connect3.2 客户端的实现 4. 提供服务4.1 单进程版本4.2 多进程1.0版本4.3 多进程1.1版本4.4 多线程2.0版本4.5 线程池3.0版本4.6 执行客户端指令 5. 守护进程5.1 进程组会话5.2 引入守护进程5.3 实现5.4 守护进程化的剩余两种方法 前言 上一节我们用了udp写了一个服务端和客户端之间通信的代码只要函数了解认识到位上手编写是很容易的。 本章我们开始编写tcp的服务端和客户端之前通信的代码要认识一批新的接口并将我们之前学习的系统知识加进来做到融会贯通… 代码详情 Gitee 1. 服务端基本结构 对于TCP服务器和UDP服务器的初始化接口确实有一些相似之处但是它们在选择字节流进行初始化方面存在一些区别。 首先无论是TCP服务器还是UDP服务器都需要进行套接字的创建、绑定和监听操作。这些初始化步骤是相同的。区别在于TCP服务器使用字节流byte stream 进行数据传输而UDP服务器使用数据报datagram 进行数据传输。对于UDP协议任何人都可以向服务器发送数据报而且不需要等待服务器响应。UDP协议是无连接的传输协议数据报发送出去后就结束。TCP协议是面向连接的传输协议需要先建立连接才能进行数据传输并且在连接建立、数据传输和断开连接的过程中需要互相响应。 1.1 类成员变量 class Task {// ....private:int sock_; // 给用户提供IO服务的sockuint16_t port_; // client portstd::string ip_; // client ipcallback_t func_; // 回调方法 };1.2 头文件 因为每个源文件都要包好多相同的头文件所以我们将要用到的头文件一并打包在一个头文件里 #pragma once#include iostream #include string #include cstring #include cstdlib #include cassert #include ctype.h #include unistd.h #include strings.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include log.hpp#define SOCKET_ERR 1 #define BIND_ERR 2 #define LISTEN_ERR 3 #define USAGE_ERR 4 #define CONN_ERR 5#define BUFFER_SIZE 1024一般涉及到struct sockaddr_in都要包含这两个头文件 1.3 初始化 TCP是面向字节流的 1.3 - 1 全双工与半双工 全双工(Full Duplex)和半双工(Half Duplex)是通信中两种不同的传输模式 全双工是指通信双方可以同时进行双向的数据传输。 在全双工模式下通信双方的发送和接收操作是独立进行的彼此之间不会互相干扰。 这种模式可以实现实时的双向通信类似于我们平时打电话或进行视频通话时的交流方式。 半双工是指通信双方在同一时间内只能进行单向的数据传输。 在半双工模式下通信双方轮流地进行发送和接收操作不能同时进行。 当一方发送数据时另一方只能等待接收反之亦然。 这种模式类似于对讲机的使用方式一方讲话时另一方只能听取无法即时回应。 套接字和管道 管道只能通过一个文件描述符读一个文件描述符写所以叫做单向管道。而在TCP中读写用的都是一个套接字fdUDP在读写时用的也是一个套接字。TCP/UDP都支持全双工。 1.3 - 2 inet_aton int inet_aton(const char *cp, struct in_addr *inp);它的作用是将一个点分十进制的IP地址字符串cp转换为网络字节序的二进制数并将结果存储在in_addr结构体inp中。因此inet_aton函数的第一个参数是要转换的IP地址字符串第二个参数是存储转换结果的结构体指针。函数的返回值是一个整数表示转换是否成功。如果转换成功返回值为非零如果转换失败返回值为零。 1.3 - 3 listen listen函数用于将一个已经建立连接的套接字通常是一个服务端的套接字标记为被动模式开始监听来自客户端的连接请求。 它接受两个参数sockfd是要设置为被动模式的套接字文件描述符backlog是指定等待连接队列的最大长度。 accept第一个参数监听到了之后然后返回一个值之后再继续去监听。 listen的第二个参数我们以后再讲… 监听socket为何要监听呢 因为udp是无连接的通信可以但是不用建立连接直接发消息就可以了而tcp是面向连接的面向就是在做任何事之前要先干什么这就是面向的意思面向连接就是在做其他工作之前先把连接建立好。不管有没有客户端连接得让服务器将来任何时候被别人连接所以要将套接字设置成监听状态。 下面的初始化就和之前udp的初始化大差不差了… void init() {// 1. 创建socketlistenSock_ socket(PF_INET, SOCK_STREAM, 0);if (listenSock_ 0){logMessage(FATAL, socket: %s, strerror(errno));exit(SOCKET_ERR);}logMessage(DEBUG, socket: %s, %d, strerror(errno), listenSock_);// 2. bind绑定// 2.1 填充服务器信息struct sockaddr_in local; // 用户栈memset(local, 0, sizeof local);local.sin_family PF_INET;local.sin_port htons(port_);ip_.empty() ? (local.sin_addr.s_addr INADDR_ANY) : (inet_aton(ip_.c_str(), local.sin_addr));// 2.2 本地socket信息写入sock_对应的内核区域if (bind(listenSock_, (const struct sockaddr *)local, sizeof local) 0){logMessage(FATAL, bind: %s, strerror(errno));exit(BIND_ERR);}logMessage(DEBUG, bind: %s, %d, strerror(errno), listenSock_);// 3. 监听socket为何要监听呢tcp是面向连接的if (listen(listenSock_, 5 /*后面再说*/) 0){logMessage(FATAL, listen: %s, strerror(errno));exit(LISTEN_ERR);}logMessage(DEBUG, listen: %s, %d, strerror(errno), listenSock_);// 走到这就意味着允许别人来连接你了// 4. 加载线程池// tp_ ThreadPoolTask::getInstance(); }2. 服务端运行接口 2.1 accept accept函数用于接受客户端连接的请求。它被用于一个已经处于被动监听状态的套接字通常是服务端的套接字。 当有新的客户端连接请求到达时accept函数将会返回一个新的套接字文件描述符此后服务端就可以通过这个新的套接字与客户端进行通信。 sockfd表示要接受连接的套接字文件描述符。addr指向保存客户端地址信息的结构体指针可以传入NULL。addrlen表示addr结构体的长度。 后面两个参数和recvfrom后两个参数的含义一模一样是想拿到是哪个客户端连接的。 第一个参数sockfd是套接字描述符 用来获取新连接的套接字叫做监听socket。 这个监听套接字负责监听指定的网络地址和端口等待客户端的连接请求。 返回值是一个套接字描述符 主要是为用户提供网络服务的socket主要是进行IO。 当有客户端发起连接请求时accept()函数就会返回一个新的套接字。这个新的套接字与客户端的套接字建立连接用于后续的数据传输。 accept函数的阻塞 accept函数是在网络编程中用于接受客户端连接的函数。当调用accept函数时如果有客户端连接请求到达它会立即返回一个新的套接字来与该客户端进行通信。如果没有客户端连接请求到达accept函数将会阻塞即一直等待直到有新的连接请求到达为止。 在阻塞状态下程序会停止执行后续代码直到有新的连接请求到达或者发生错误。因此可以将accept函数放在一个循环中反复接受多个客户端连接。需要注意的是在某些情况下可以通过设置套接字为非阻塞模式来避免accept函数的阻塞这样程序可以继续执行其他操作。 void loop() {tp_-start();logMessage(DEBUG, thread pool start success, thread num: %d, tp_-threadNum());while (true){struct sockaddr_in peer;socklen_t len sizeof(peer);// 4. 获取连接, accept 的返回值是一个新的socket fd // 4.1 listenSock_: 监听 获取新的链接- sock// 4.2 serviceSock: 给用户提供新的socket服务int serviceSock accept(listenSock_, (struct sockaddr *)peer, len);if (serviceSock 0){// 获取链接失败logMessage(WARINING, accept: %s[%d], strerror(errno), serviceSock);continue;}// 4.1 获取客户端基本信息uint16_t peerPort ntohs(peer.sin_port);std::string peerIp inet_ntoa(peer.sin_addr);logMessage(DEBUG, accept: %s | %s[%d], socket fd: %d,strerror(errno), peerIp.c_str(), peerPort, serviceSock);// 提供服务....} }2.2 服务接口 提供的服务将小写转成大写 // 大小写转化服务 // TCP UDP: 支持全双工 void transService(int sock, const std::string clientIp, uint16_t clientPort) {assert(sock 0);assert(!clientIp.empty());assert(clientPort 1024);char inbuffer[BUFFER_SIZE];while (true){ssize_t s read(sock, inbuffer, sizeof(inbuffer) - 1); // 我们认为我们读到的都是字符串if (s 0){// read successinbuffer[s] \0;if (strcasecmp(inbuffer, quit) 0){logMessage(DEBUG, client quit -- %s[%d], clientIp.c_str(), clientPort);break;}logMessage(DEBUG, trans before: %s[%d] %s, clientIp.c_str(), clientPort, inbuffer);// 可以进行大小写转化了for (int i 0; i s; i){if (isalpha(inbuffer[i]) islower(inbuffer[i]))inbuffer[i] toupper(inbuffer[i]);}logMessage(DEBUG, trans after: %s[%d] %s, clientIp.c_str(), clientPort, inbuffer);write(sock, inbuffer, strlen(inbuffer));}else if (s 0){// pipe: 读端一直在读写端不写了并且关闭了写端读端会如何s 0代表对端关闭// s 0: 代表对方关闭,client 退出logMessage(DEBUG, client quit -- %s[%d], clientIp.c_str(), clientPort);break;}else{logMessage(DEBUG, %s[%d] - read: %s, clientIp.c_str(), clientPort, strerror(errno));break;}}// 只要走到这里一定是client退出了服务到此结束close(sock); logMessage(DEBUG, server close %d done, sock); }recvfrom和sendto是专门针对udp发送用户数据报的它是一 个固定大小的报文在那里它是专函数专用的专门为udp提供的。而tcp就通用的多因为tcp是流式服务我们这里直接可以当做是处理文件的方式来进行读写。 如果一个进程对应的文件fd打开了没有被归还这种现象叫做文件描述符泄漏 如果不关来一个客户端打开一个文件描述符会导致该服务端进程可用文件描述符越来越少。文件描述符表是有上限的时间一久会导致服务器无法获取新连接申请文件描述符时发现所有文件描述符都被占用了。此时服务器就无法对外提供服务了。 3. 客户端 3.1 connect connect是一个系统调用函数用于建立与远程主机的连接。它通常用于创建客户端套接字并将其连接到服务器套接字。 sockfd套接字文件描述符由socket函数创建获得。addr指向远程主机的地址结构体的指针可以是struct sockaddr_in或struct sockaddr_in6。addrlen远程主机地址结构体的长度。 connect 会自动帮我们进行bind connect函数通过sockfd和addr参数指定的地址信息将本地套接字与远程主机的套接字连接起来。如果连接成功返回0如果连接失败返回-1并设置全局变量errno表示错误类型。 注意在使用connect函数之前必须先创建一个套接字并确保套接字是可用的可以使用socket函数进行创建。 三个问题 客户端需要绑定吗需要但是不需要自己显示的bind需要监听吗不需要监听是让别人来连你作为客户端不用被连需要accept吗都没人来连你根本不需要获取连接 3.2 客户端的实现 有了上面的分析再加上之前udp编写的基础我们很容易就能将tcp的客户端编写完成 #include util.hppvolatile bool quit false;static void Usage(std::string proc) {std::cerr Usage:\n\t proc serverIp serverPort std::endl;std::cerr Example:\n\t proc 127.0.0.1 8081\n std::endl; }// ./clientTcp serverIp serverPort int main(int argc, char *argv[]) {if (argc ! 3){Usage(argv[0]);exit(USAGE_ERR);}std::string serverIp argv[1];uint16_t serverPort atoi(argv[2]);// 1. 创建socket SOCK_STREAMint sock socket(AF_INET, SOCK_STREAM, 0);if (sock 0){std::cerr socket: strerror(errno) std::endl;exit(SOCKET_ERR);}// 2. connect发起链接请求你向谁发起请求呢当然是向服务器发起请求喽// 2.1 先填充需要连接的远端主机的基本信息struct sockaddr_in server;memset(server, 0, sizeof server);server.sin_family AF_INET;server.sin_port htons(serverPort);inet_aton(serverIp.c_str(), server.sin_addr);// 2.2 发起请求(隐性的概念)connect 会自动帮我们进行bindif (connect(sock, (const struct sockaddr *)server, sizeof server) ! 0){std::cerr connect: strerror(errno) std::endl;exit(CONN_ERR);}std::cout info : connect success: sock std::endl;std::string message;while (!quit){message.clear();std::cout 请输入你的消息 ;std::getline(std::cin, message);if (strcasecmp(message.c_str(), quit) 0)quit true;// 向服务器发消息ssize_t s write(sock, message.c_str(), message.size());std::cout read before std::endl;if (s 0){message.resize(1024);ssize_t s read(sock, (char *)(message.c_str()), 1024);if (s 0) message[s] 0;std::cout Server Echo message std::endl;}else if (s 0){break;}}close(sock);return 0; }日志重定向 之前我们将日志全部都打印在显示器上这次我们将日志全部都打印到一个文件中方便以后查看 客户端连接服务器 4. 提供服务 4.1 单进程版本 // 提供服务, echo - 小写 - 大写 // 0.0 版本 -- 单进程 -- 一旦进入transService主执行流就无法进行向后执行只能提供完毕服务之后才能进行accepttransService(serviceSock, peerIp, peerPort);我们不重定向方便我们进行实验。 实验结果 如果ctrl c杀掉客户端进程的话 ctrl c异常终止的话文件是只有这个进程打开的文件的生命周期是随进程的。如果强制的将客户端ctrl c掉操作系统会自动的关闭掉进程所对应的文件描述符。进程退出PCB被文件释放文件描述符表被释放文件指针指向的struct file结构体引用计数减减。因为只有一个指向文件结构体就减到0操作系统自动关闭这个文件描述符。已关闭该文件服务端读文件就会读到0就类似于读到文件结束。 多个客户端连接服务器有问题的 我们发现一个客户端连接服务器的时候客户端可以正常的显示出服务器处理过的结果。 但是一旦我们有两个或者两个以上的客户端连接服务器就会出问题新连接的客户端会卡在那里。 原因解释看我笔记吧 4.2 多进程1.0版本 // 1.0 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗会的 pid_t id fork(); assert(id ! -1); if(id 0) {close(listenSock_); // 建议关掉transService(serviceSock, peerIp, peerPort);exit(0); // 任务处理完就退出进入僵尸 } // 父进程 -- 父进程不用对外提供服务 close(serviceSock); // 这一步是一定要做的 // waitpid(); 默认是阻塞等待WNOHANG服务函数放在类内类外都行 子进程也会把曾经父进程打开的listen套接字继承下去。通过创建子进程让其去做父进程代码的一部分。close(listenSock_);建议关掉。 万一子进程将listenSock_文件描述符给写了可能影响将来accept。 close(serviceSock);这一步是一定要做的 如果父进程不关掉那么随着连接来的客户端的增多父进程可用的文件描述符就会越来越少。 父进程获取servicSock文件描述符是为了让子进程继承下去自己是不用的就不应该继续占着如果不关闭最后可能导致文件描述符泄漏的问题。 我们知道子进程退出之后就会进入僵尸状态等待父进程回收 那我们敢让父进程阻塞式等待吗显然是不能因为我们的目的是让服务器并发起来现在还阻塞着。 如果用非阻塞等待WNOHANG这是可以的我们要所有子进程的PID保存起来非阻塞等待的时候每一次都要轮询所有的子进程但是比较麻烦。 进程等待复习 - 传送门 或者直接忽略SIGCHLD // 不用等子进程了 // signal(SIGCHLD, SIG_IGN); // only Linux忽略SIGCHLD 复习传送门。 4.3 多进程1.1版本 // 1.1 版本 -- 多进程版本 -- 这样写也是可以的 // 爷爷进程 pid_t id fork(); if(id 0) {// 爸爸进程close(listenSock_);// 建议关掉// 又进行了一次fork让 爸爸进程if(fork() 0) exit(0);// 孙子进程 -- 就没有爸爸 -- 就变成了孤儿进程 -- 被系统领养 -- 孙子进程就交给了系统来回收transService(serviceSock, peerIp, peerPort);exit(0); }// 父进程 close(serviceSock); // 这一步是一定要做的 // 爸爸进程直接终止立马得到退出码释放僵尸进程状态 pid_t ret waitpid(id, nullptr, 0); // 就用阻塞式 assert(ret 0); (void)ret;我们这里用到了 爷爷、爸爸、孙子 三个进程。爷爷进程创建爸爸进程爸爸进程再创建孙子进程。只不过爸爸进程在创建完孙子进程之后直接就退出由爷爷进程对其进行回收。将服务任务交由孙子进程去做。 孙子进程没有了父进程就变成了孤儿进程被系统领养孙子进程就交给了系统来回收就不用我们来回收了。 子进程是从fork函数开始执行的。 复习传送门 服务函数放在类内类外都行 4.4 多线程2.0版本 因为我们是线程函数是设置在类内的方法所以成员函数第一个参数是隐藏的this指针我们要设置成静态的。 静态成员函数里要想获取到类内成员变量的话还要搞一些获取类内成员的接口我们直接将现这些数据封装一下 // 先声明一下 class ServerTcp;class ThreadData { public:uint16_t clientPort_;std::string clinetIp_;int sock_;ServerTcp *this_;public:ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts): clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts){} };线程函数 // 类内方法形参默认带有this指针 static void *threadRoutine(void *args) {pthread_detach(pthread_self()); // 设置线程分离ThreadData *td static_castThreadData*(args);td-this_-transService(td-sock_, td-clinetIp_, td-clientPort_);delete td;return nullptr; }此时服务函数放在了类里面 // 2.0 版本 -- 多线程 ThreadData *td new ThreadData(peerPort, peerIp, serviceSock, this); pthread_t tid; pthread_create(tid, nullptr, threadRoutine, (void*)td); // 不可进行线程等待一等待主线程就阻塞了只能用线程分离这里不需要进行关闭文件描述符吗不需要啦多线程是会共享文件描述符表的 不可进行线程等待pthread_join一等待主线程就阻塞了只能用线程分离。 4.5 线程池3.0版本 Task任务需要我们重写 #pragma once#include iostream #include string #include functional #include pthread.h #include log.hppclass Task { public:// 下面两个等价// typedef std::functionvoid (int, std::string, uint16_t) callback_t;using callback_t std::functionvoid (int, std::string, uint16_t); public:Task():sock_(-1), port_(-1){}Task(int sock, std::string ip, uint16_t port, callback_t func): sock_(sock), ip_(ip), port_(port), func_(func){}void operator () (){logMessage(DEBUG, 线程ID[%p]处理%s:%d的请求 开始啦...,\pthread_self(), ip_.c_str(), port_);func_(sock_, ip_, port_);logMessage(DEBUG, 线程ID[%p]处理%s:%d的请求 结束啦...,\pthread_self(), ip_.c_str(), port_);}~Task(){} private:int sock_; // 给用户提供IO服务的sockuint16_t port_; // client portstd::string ip_; // client ipcallback_t func_; // 回调方法 };交给线程池处理 // 3.0 版本 -- 线程池 // transService服务在类外 Task t(serviceSock, peerIp, peerPort, transService); tp_-push(t);服务函数放在类外 我们在初始化服务器的方法的最后加了一个启动线程池。 线程池 - 复习 还需要再loop函数循环之前将线程池中的线程加载好。 我们将服务方法通过Task打包封装一下加载进线程池当中然后Task有个仿函数里面就是调用回调函数。 之前我们在学C11的时候学过bind我们这里可以用起来 Task t(serviceSock, peerIp, peerPort, std::bind(ServerTcp::transService, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)); tp_-push(t);bind不熟悉的看过来 复习传送门 服务函数放在类内 4.6 执行客户端指令 popen函数 第一件事情创建管道第二件事情fork会自动帮我们创建子进程让子进程去执行command代码子进程执行完了之后让父进程通过文件能够读到结果。 具体来说popen函数会创建一个管道其中写入端口write end被父进程保留而读出端口read end被子进程保留。然后popen函数调用fork创建一个新的子进程该子进程会继承父进程的文件描述符包括管道的读写端口。匿名管道用于在父进程和子进程之间进行双向通信。 void execCommand(int sock, const std::string clientIp, uint16_t clientPort) {assert(sock 0);assert(!clientIp.empty());assert(clientPort 1024);char command[BUFFER_SIZE];while (true){ssize_t s read(sock, command, sizeof(command) - 1); // 我们认为我们读到的都是字符串if (s 0){command[s] \0;logMessage(DEBUG, [%s:%d] exec [%s], clientIp.c_str(), clientPort, command);// 考虑安全std::string safe command;if ((std::string::npos ! safe.find(rm)) || (std::string::npos ! safe.find(unlink))){break;}// 我们是以r方式打开的文件没有写入// 所以我们无法通过dup的方式得到对应的结果FILE *fp popen(command, r);if (fp nullptr){logMessage(WARINING, exec %s failed, beacuse: %s, command, strerror(errno));break;}char line[1024];while (fgets(line, sizeof(line) - 1, fp) ! nullptr){write(sock, line, strlen(line));}pclose(fp);logMessage(DEBUG, [%s:%d] exec [%s] ... done, clientIp.c_str(), clientPort, command);}else if (s 0){// pipe: 读端一直在读写端不写了并且关闭了写端读端会如何s 0 代表对端关闭// s 0: 代表对方关闭,client 退出logMessage(DEBUG, client quit -- %s[%d], clientIp.c_str(), clientPort);break;}else{logMessage(DEBUG, %s[%d] - read: %s, clientIp.c_str(), clientPort, strerror(errno));break;}}// 只要走到这里一定是client退出了服务到此结束close(sock); // 如果一个进程对应的文件fd打开了没有被归还文件描述符泄漏logMessage(DEBUG, server close %d done, sock); }同样的也是通过线程池的方式提供服务 Task t(serviceSock, peerIp, peerPort, execCommand); tp_-push(t);服务函数放在类外 备注 如果我们设置了对应的任务是死循环那么线程池提供服务就显得有不太合适了我们应该给线程池抛入的任务是短任务。 5. 守护进程 5.1 进程组会话 进程组 进程组是一个或多个相关联的进程的集合。在Linux操作系统中进程组是进程管理的一种机制用于管理共享相同终端或终端会话的一组进程。 进程组组长 每个进程组都有一个组长进程Group Leader Process它的进程ID与进程组ID相同。组长进程可以通过调用setpgid()系统调用将其他进程加入到自己的进程组中。组长进程通常是第一个创建进程组的进程或者通过调用setpgid()将其他进程加入到自己所在的进程组中的进程。组长进程拥有一些特殊的权限和责任例如可以向整个进程组发送信号、管理进程组的终止状态等。* 同时组长进程也有责任确保该进程组中的所有进程得到正确的处理和管理。 需要注意的是 一个进程可以同时属于多个进程组但一个进程只能担任一个进程组的组长。当组长进程终止时其所在的进程组中的所有进程都将收到SIGHUPhangup信号除非它们已经忽略了该信号或者通过调用signal()函数将其重置。 会话 当用户登录到Linux系统后系统为其创建一个会话由多个进程组构成并为其分配一个唯一的会话标识符Session ID。这个会话可以持续到用户注销或与系统断开连接为止。 首进程 在一个会话(Session) 中首进程(Session Leader)是创建该会话的进程。当一个新的会话被创建时通常由一个特定的进程作为首进程。首进程负责创建并管理该会话中的其他进程。 首进程具有以下特点 首进程是会话的领导者它拥有该会话的控制权和权限。首进程的进程ID (PID) 与会话ID (SID) 相同。首进程可以创建或终止会话中的其他进程并对它们进行管理和控制。首进程通常是用户登录系统后启动的shell进程(如Bash、Zsh、 Csh 等) 它会创建一个新的会话并成为该会话的首进程。首进程还负责设置会话的相关属性如控制终端、信号处理等。如果首进程退出或终止整个会话将结束会话中的所有其他进程也会被终止。 如果我们自己在新启进程或者启动进程组一定是属于bash自己的会话。 需要注意的是 一个会话可以包含多个进程组而首进程只是会话中的一个特定进程 它并不一定是进程组的领导者。进程组的领导者可以通过调用setpgid()来改变自己所属的进程组但这并不会影响首进程的身份和权限。 我们平时使用电脑卡的原因可能是因为在本次登录过程中起了很多个任务这些任务都属于同一个会话所以在卡的时候进行注销操作本质是将会话内部所有进程组全部删掉。 任何时刻只能有一个前台进程组而且必须要有一个前台进程组有0个或者多个后台进程组。 我们将一个任务启动到前台bash命令行解释器自己就将自己投递到后台了就没有办法接收输入了。所以我们再次输入指令时就不会有任何响应。 会话进程组举个栗子 起了三个进程其中第一个进程一般都是进程组的组长。SID是当前进程的会话ID。三个进程会话属于同一个就是bash。 bash自己就是个进程自己就是组长就是会话当中的话首进程自成一组。 当bash启动时它会成为一个新的进程并且作为会话的首进程。Bash进程的进程IDPID和进程组IDPGID会话IDSID通常是相同的。 5.2 引入守护进程 我们提供的网络服务能不能属于bash这会话呢 比如在登录的状态新起了一个网络服务器创建好之后再派生子进程也属于当前会话。所以就不能让网络服务器属于当前会话内容要不然会受会话的用户登录和注销的影响不一定会退出。 所有会话内的进程fork创建子进程一般而言依旧属于当前会话 当我们登录时以bash为首给我们构建一个会话在bash命令中可以输入各种前台任务或者取地址变成后台任务变成后台进程。 后台进程任务依旧属于当前会话登录登出可能会影响当前进程组。 起一个后台任务退出然后重新登录这个后台任务可能还在是会受到登录登出的影响的所以要设置新的会话。 当我们有网络服务的时候就应该脱离会话让它独立在计算机里让其形成自己的新会话。 自成进程组自成会话的周而复始运行的这一类进程我们称之为守护进程 or 精灵进程。 一般以服务器的方式工作对外提供服务的服务器都是以守护进程精灵进程的方式在服务器中工作的一旦启动之后除非用户主动关闭否则一直会在运行。 5.3 实现 谁调用这个函数谁就成为一个新的会话并且成为新会话内进程组的组长。 返回值 哪个进程调用它哪个进程的pid会被返回失败就返回-1并且错误码被设置。 但是有个要求调用setsid的这个进程不能是进程组的组长进程组组长一调用setsid调用就失败。 注意 必须调用一个函数setsid()将调用进程设置成为独立的会话。进程组的组长不能调用setsid();我如何不成为组长呢 可以成为进程组内的第二个进程 常规做法fork()子进程子进程就不再是组长就可以调用setsid()了。 管道如果写端一直在写读端关闭写端会被终止被信号终止SIGPIPE 如果server给client写入但是client已经关了就相当于向一个不存在的文件描述符写入那么此时会出现什么问题呢? server也会收到SIGPIPE信号 当进程收到SIGPIPE信号时如果未对该信号进行处理进程将以异常退出的形式终止。 所以我们忽略掉SIGPIPE信号当然这只是选做。 #pragma once#include cstdio #include iostream #include signal.h #include unistd.h #include sys/types.h #include sys/stat.h #include fcntl.hvoid daemonize() {int fd 0;// 1. 忽略SIGPIPEsignal(SIGPIPE, SIG_IGN);// 2. 更改进程的工作目录// chdir();// 3. 让自己不要成为进程组组长if (fork() 0)exit(0); // 父进程退出了管都不用管因为父进程有自己的父进程(bash)// 4. 设置自己是一个独立的会话 -- 不受登录退出的影响setsid();// 5. 重定向0, 1, 2if ((fd open(/dev/null, O_RDWR)) ! -1) // fd 3{dup2(fd, STDIN_FILENO);dup2(fd, STDOUT_FILENO);dup2(fd, STDERR_FILENO);// 6. 关闭掉不需要的fd -- 别浪费了就关掉了if(fd STDERR_FILENO)close(fd);}// 3, 4, 5这三步是网络服务器写守护进程时必写的三步// close(0,1,2);这种做法严重不推荐 }重定向的原因 daemonize();函数是在网络服务器启动之前调用的。 那么serverTcp服务器里一定有大量的cout cerr打印日志的行为。我们调用setsid创建一个会话已经和终端没有关系了。一个会话绑定上终端(命令行界面)是一个字符式的模拟终端。Linux中是bash进程和终端强关联终端也是文件是被打开的当前的进程就可以从终端里读向终端里写入。一旦将进程转变为守护进程它就会与终端断开关联。也就是说它不再与终端会话相关联这意味着守护进程不再受用户登录或注销的影响并且不会与用户直接交互。在setsid里就自动将进程和终端去关联了去关联之后就不能使用cin cout cerr。因为012描述符已经和终端去关联了后续程序一写就退出了。一旦重定向之后拦截输入输出只是放在dev/nul/里丢弃掉了。 dev/nul/是Linux中的数据垃圾桶或者叫信息黑洞。 在一些早期的Unix系统中关闭0、1、2代表关闭了所有与终端相关的文件描述符这样就成为了守护进程的一种惯例做法。但是在现代系统中关闭0、1、 2是一种错误的实践因为它们可能被其他进程使用而且这些文件描述符对于守护进程的运行是非常重要的。这样可以避免程序在后续的执行中因为无法访问标准流而出现问题并确保它能够正常地向用户提供服务。 效果 守护进程在命名时通常以d结尾。 5.4 守护进程化的剩余两种方法 第二种方法 如果不想手动写守护进程系统自带了函数接口 第三种方法nohup 自己自成一个进程组当前的会话依旧是属于3154这个进程依旧是在本会话内部。形成的一个并非是守护进程但是已经和守护进程是一样的了。虽然依就是属于3154这个会话但是设置了nohup就是不受用户登录和注销的影响了。 3154就是个bash 退出后再登录 此时已经是一个独立的会话了成了一个孤儿进程。
http://www.w-s-a.com/news/960589/

相关文章:

  • 丝网外贸做哪些网站最优的赣州网站建设
  • 如何做网站不被查网站开发工程师岗位说明书
  • 做网站需要vps吗网站建设后怎样发信息
  • 网站建立风格二手交易网站开发可参考文献
  • 成都微信网站开发优化大师优化项目有哪些
  • 哪个网站做自考题目免费郑州网站建设公司qq
  • 地方性的网站有前途顺的网络做网站好不好
  • 学校申请建设网站的原因不要网站域名
  • 推荐响应式网站建设子域名查询工具
  • 如何建设学校的微网站广告推广是什么
  • 设计类专业哪个就业前景好网站建设seoppt
  • 济南建站公司网站网站友链查询源码
  • 校园失物招领网站建设涪陵网站建设公司
  • 怎么做盗号网站手机网站建设需要租用什么科目
  • 成品网站是什么意思沈阳seo推广
  • 购物网站后台流程图昆明官网seo技术
  • 创建自己网站全网零售管理系统
  • 江苏省建设厅网站建筑电工证wordpress收费插件大全
  • 北京中国建设银行招聘信息网站宁德蕉城住房和城乡建设部网站
  • 泉州做网站优化哪家好wordpress站点预览
  • 创建门户网站一页网站首页图如何做
  • 服装手机商城网站建设sns社交网站有哪些
  • 无锡工程建设招标网站怎么自己建设公司网站
  • 哪个网站可以学做咸菜安卓软件开发需要学什么软件
  • 自有网站建设的团队遂宁市建设局网站
  • 网站建设哪个好一些网站内容导出
  • 什么网站的页面做的比较好看网上做平面设计的网站
  • 网站建设单选网站建设学校培训学校
  • 可以做app的网站logo设计在线生成免费标小智
  • 网站变更备案做酒类网站