服务器网络编程基础

几个重要的信号

  • SIGPIPE管道中止,当写入无人读取的管道时产生该信号,默认终止进程
  • SIGCHLD子进程结束或停止时发送
  • SIGALRM定时器信号,以秒为单位,默认终止进程
  • SIGUSR1/SIGUSR2自定义,默认终止进程
  • SIGINT键盘输入的退出信号
  • SIGQUIT键盘输入的退出信号
  • SIGHUP控制终端的挂起信号

SIGPIPE
网络程序必须要处理SIGPIPE信号,否则当客户端
退出后,服务器仍然向该SOCKET发数据时,则会
引起Crash
SIGCHLD

  1. 僵尸进程是一个早已死亡的进程,但在进程表中仍占有位置
  2. Linux中当子进程结束的时候,他并没有被完全销毁,因为父进程还
    要用它的信息
  3. 父进程没有处理SIGCHLD信号或调用wait / waitpid()等待子进
    程结束,就会出现僵尸进程

发送信号方式

硬件方式
如ctrl+ C、ctrl+\等
软件方式
kill api

安装信号方式

  1. 简单方式
    signal(int sig, void (*func)(int);
  2. 高级方式
    int sigaction(int sig, const struct sigaction act, struct sigaction oact);
    sigaction结构体:
struct sigaction {
	void(*sa_handler)(int); //处理函数
	void(*sa_sigaction)(int, siginfo_t*, void*); //处理函数
	sigset_tsa_mask; //掩码
	int sa_flags; //根据SA SIGINFO标记选择
	sa_handler/sa_sigaction
	void(*sa_restorer)(void); //设置为NULL, -般不用
}

两种方式例子:

#include<iostream>
#include<signal.h>
#include <unistd.h>
void sighandle(int sig) {
        std::cout << "receved signal" <<sig<<std::endl;
}
int main(int argc, char* argv[]) {
        signal(SIGINT, sighandle);
        signal(SIGQUIT, sighandle);
        signal(SIGHUP, sighandle);
        pause();
        return 0;
}

#include<iostream>
#include<signal.h>
#include <unistd.h>
void sighandler(int sig){
        std::cout << "receved signal" <<sig<<std::endl;
}

int main(int argc, char* argv[]) {
    struct sigaction act, oact;
    act.sa_handler = sighandler;
    //将所有信号位置一,不进行处理
    sigfillset(&act.sa_mask);
    //仅使用两个处理函数的第一个
    act.sa_flags = 0;
    sigaction(SIGINT,&act, &oact);
    pause();
    return 0;
}                    

后台进程

将进程编程后台的方式:
fork方式
调用系统的daemon API

Fork方式

fork一个子进程,父进程退出,子进程成为孤儿进程,被init进程接管
调用setsid建立新的进程会话
将当前工作目录切换到根目录
将标准输入,输出,出错重定向到/dev/null

#include<iostream>
#include<unistd.h>
#include <stdlib.h>
#include<fcntl.h>

void daemonize(){
    int fd;
    pid_t pid;
    if((pid=fork())<0){
    	std::cout<<"can't create supercess!"<<std::endl;
    }else{
	if(pid!=0){//如果是父进程,则推出
            exit(0);
        }
    }
    setsid();
    if(chdir("/")<0){
	std::cout<<"can't change dir!"<<std::endl;
	exit(-1);
    }
    fd = open("/dev/null", O_RDWR);
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    return;
}

int main(int argc, char* argv[]) {
    daemonize();
    while(1){
        sleep(1);
    }
}

daemon API方式

#include<iostream>
#include<unistd.h>
#include <stdlib.h>

int main(int argc, char* argv[]) {
	//两个参数都为0,表示改变目录,第二个表示关闭标准输入输出文件
    if(daemon(0,0)==-1){
    	std::cout<<"error"<<std::endl;
		exit(-1);
    }
    while(1){
 		sleep(1);
    }
}

在这里插入图片描述

网络编程基础

在这里插入图片描述

TCP服务端实现

重要结构体

struct sockaddr_in
{
	sa_family_t sin_family; //协议
	uint16_t sin_port;
	struct in_addr sin_addr; 
	char sin_zero[8] //填充位
}
struct in _addr
in_addr_t s_addr;
}
struct sockaddr
sa_family_t sin_family;
char sin_zero[14];

=======

#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#define PORT 8111
#define MESSAGE_LEN 1024
int main(int argc, char* argv[]){
    int socket_fd;
    int on = 1;
    int ret = -1;
    //并发10个
    int backlog = 10;
    int accept_fd;
    char in_buff[MESSAGE_LEN] = {0,};
    struct sockaddr_in localaddr, remoteaddr;
    //用man socket可查函数具体
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_fd==-1){
	std::cout<<"Failed to create socket!"<<std::endl;
	exit(-1);
    }
    ret = setsockopt(
	socket_fd, 
     	SOL_SOCKET, 
	SO_REUSEADDR, 
	&on, 
	sizeof(on)
    );
    if(ret==-1){
	std::cout<<"Failed to set socket options!"<<std::endl;
    }
    localaddr.sin_family = AF_INET;
    localaddr.sin_port = PORT;
    //所有地址都能侦听这个端口:
    localaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(localaddr.sin_zero),8);
    ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
    if(ret==-1){
	std::cout<<"Failed to bind addr!"<<std::endl;
	exit(-1);
    }
    //开启监听
    ret = listen(socket_fd, backlog);
    if(ret==-1){
	std::cout<<"Failed to listen socket!"<<std::endl;
	exit(-1);
    }
    while(1){
	socklen_t addr_len = sizeof(struct sockaddr);
	accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
	while(true){
	    //不断接受数据
	    ret = recv(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
	    if(ret==0){break;}
            std::cout<<"recv:"<<in_buff<<std::endl;
	    //数据原样回传
	    send(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
	}
	close(accept_fd);
	
    }
    close(socket_fd);
    return 0;
}

实现TCP客户端

TCP Client网络编程基本步骤
创建socket,指定使用TCP协议
使用connect连接服务器
使用recv/send接收/发送数据

#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<sys/types.h>
#include <stdio.h>
#include<unistd.h>
#include<string.h>
#include<arpa/inet.h>
#include <stdlib.h>
int main(int argc, char* argv[]){
    struct sockaddr_in serveraddr;
    int socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_fd<0){
	std::cout<<"Failed to create socket!"<<std::endl;
	exit(-1);
    }
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = 8111;
    //inet_addr()将字符串转为整型
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    int ret = connect(socket_fd, 
	(struct sockaddr *)&serveraddr, 
	sizeof(struct sockaddr)
    );
    if(ret<0){
	std::cout<<"Failed to connect server!"<<std::endl;
	exit(-1);
    }
    char sendbuf[1024] = {0,};
    char recvbuf[1024] = {0,};
    while(1){
	memset(sendbuf, 0, 1024);
        fgets(sendbuf,1024,stdin);
	ret = send(socket_fd, sendbuf, strlen(sendbuf), 0);
	if(ret<=0){//没有发送数据,说明连接断开了
	    std::cout<<"Failed to send data!"<<std::endl;
            break;
	}
	if(strcmp(sendbuf, "quit")==0){
	    break;	
	}
	ret = recv(socket_fd, recvbuf, 1024, 0);
	recvbuf[ret] = '\0';
	std::cout<<"recv:"<<recvbuf<<std::endl;
    }
    close(socket_fd);
}

实现UDP通信

UDP Server网络编程基本步骤
创建socket ,指定使用UDP协议
将socket与地址和端口绑定
使用recv/send接收/发送数据
使用close关闭连接
在这里插入图片描述

高性能网络服务器处理并发

通过fork实现高性能网络服务器
通过select实现高性能网络服务器
通过epoll实现高性能网络服务器
利用I/O事件处理库来实现高性能网络服务器

通过fork实现高性能网络服务器

每收到一个连接就创一个子进程
父进程负责接受连接
通过fork创建子进程

只需修改上面的server端代码:

while(1){
	socklen_t addr_len = sizeof(struct sockaddr);
	accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
	//每当获取到一个新连接就fork一个子进程
	pid = fork();
	if(pid==0){//子进程
	    while(true){
	        //不断接受数据
	        ret = recv(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
	        if(ret==0){break;}
	        std::cout<<"recv:"<<in_buff<<std::endl;
**加粗样式**	        //数据原样回传
	        send(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
	    }
	    close(accept_fd);
    }
}
if(pid!=0){
	//套接字也是文件,当server端监听到有连接时,
	//应用程序会请求内核创建Socket,
	//Socket创建好后会返回一个文件描述符给应用程序
	close(socket_fd);//文件描述符fork后共享,所以子进程不能关闭
}

可惜进程非常占用资源,创建子进程也耗费时间,fork并不可取

通过select实现高性能网络服务器

什么是异步IO

  1. 所谓异步IO ,是指以事件触发的机制来对IO操作进行处理。
  2. 与多进程和多线程技术相比,异步I/O技术的最大优势是系统开销小,系统不必创建进程/线程,也不必维护这些进程/线程,从而大大减小了系统的开销。

理解select模型

  1. 理解select模型的关键在于理解fd set类型
  2. fd_ set就是多个整型字的集合,每个bit代表-个文
    件描述符
  3. FD ZERO表示将所有位置0
  4. FDSET是将fdset中的某-位置1
  5. select函数执行后,系统会修改fd_set中的内容
  6. select函数执行后,应用层要得新设置fd_set中的内容

要求
遍历文件描述符集中的所有描述符,找出有变化的描述符
对于侦听的socket和数据处理的socket要区别对待
socket必须设置为非阻塞方式工作

修改后的服务端:

#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define FD_SIZE 1024
int main(int argc, char* argv[]){
    int socket_fd;
    int on = 1;
    int ret = -1;
    //并发10个
    int backlog = 10;
    int accept_fd;
    char in_buff[MESSAGE_LEN] = {0,};
    int max_fd = -1;
    int events = 0;
    fd_set fd_sets;
    int accept_fds[FD_SIZE] = {-1,};
    int curpos = -1;
    int flags;
    struct sockaddr_in localaddr, remoteaddr;
    //用man socket可查函数具体
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_fd==-1){
	std::cout<<"Failed to create socket!"<<std::endl;
	exit(-1);
    }
    //将socket变成异步
    flags = fcntl(socket_fd, F_GETFL, 0);
    fcntl(socket_fd, F_SETFL, flags|O_NONBLOCK);
    max_fd = socket_fd;

    ret = setsockopt(
	socket_fd, 
     	SOL_SOCKET, 
	SO_REUSEADDR, 
	&on, 
	sizeof(on)
    );
    if(ret==-1){
	std::cout<<"Failed to set socket options!"<<std::endl;
    }
    localaddr.sin_family = AF_INET;
    localaddr.sin_port = PORT;
    //所有地址都能侦听这个端口:
    localaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(localaddr.sin_zero),8);
    ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
    if(ret==-1){
	std::cout<<"Failed to bind addr!"<<std::endl;
	exit(-1);
    }
    //开启监听
    ret = listen(socket_fd, backlog);
    if(ret==-1){
	std::cout<<"Failed to listen socket!"<<std::endl;
	exit(-1);
    }
    int pid;
    while(1){
   	FD_ZERO(&fd_sets);
	FD_SET(socket_fd, &fd_sets);
	for(int i=0; i<FD_SIZE; i++){
	    if(accept_fds[i]!=-1){
	        if(accept_fds[i]>max_fd){
		    max_fd = accept_fds[i];		
		}
		FD_SET(accept_fds[i], &fd_sets);	
	    }	
	}
	events = select(max_fd+1, &fd_sets, NULL, NULL, NULL);
	if(events<0){
	    std::cout<<"Failed to use select!"<<std::endl;
	    break;
	}else if(events==0){
	    std::cout<<"timeout!"<<std::endl;
	    continue;
	}else {
	    if(FD_ISSET(socket_fd, &fd_sets)){
	   	for(int i=0; i<FD_SIZE; i++){
		    if(accept_fds[i]==-1){
			curpos = i;
			break;
		    }
		}
		socklen_t addr_len = sizeof(struct sockaddr);
		accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, 						&addr_len);
		flags = fcntl(accept_fd, F_GETFL, 0);
    		fcntl(accept_fd, F_SETFL, flags|O_NONBLOCK);
		accept_fds[curpos] = accept_fd;
	    }
	    for(int i=0; i<FD_SIZE; i++){
		if(accept_fds[i]!=-1&&FD_ISSET(accept_fds[i], &fd_sets)){
		    memset(in_buff, 0, MESSAGE_LEN);
		    ret = recv(accept_fd, (void*)in_buff, MESSAGE_LEN, 0);
		    if(ret==0){
			close(accept_fds[i]);
		    	break;
		    }
		    std::cout<<"recv:"<<in_buff<<std::endl;
		    send(accept_fds[i], (void*)in_buff, MESSAGE_LEN, 0);
		}
	    }
	}
	
    }
    close(socket_fd);
    return 0;
}

epoll实现高性能网络服务器

优点:

  1. 没有文件描述符的限制
  2. 工作效率不会随着文件描述符的增加而下降
  3. Epoll经过系统优化更高效

Epoll事件的触发模式

  1. Level Trigger没有处理反复发送
  2. Edge Trigger只发送一次

Epoll重要的API

  1. int epoll _create()参数无意义,可忽略
  2. int epoll_ ctl(epfd, op, fd, struct epoll_event *event)向文件描述符fd添加事件
  3. int epoll _wait(epfd, events, maxevents, timeout)等待事件

Epoll的事件

  1. EPOLLET,水平触发
  2. EPOLLIN,有数据来
  3. EPOLLOUT,写数据
  4. EPOLLPRI,出现中断问题
  5. EPOLLERR,读写出问题
  6. EPOLLHUP,挂起

epoll _ctl 相关操作
对文件描述符操作:

  1. EPOLL CTL ADD
  2. EPOLL CTL MOD
  3. EPOLL CTL DEL

Epoll重要的结构体

typedef union epoll data {
	void *ptr;
	int fd;
	uint32_t u32;
	uint64_t u64;
} epoll_data_t;
struct epoll_ event {
	uint32_t events; /* Epoll events */
	epoll_data_t data; /* User data variable */
};

修改后的server代码:

#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/epoll.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define MAX_EVENTS 20
#define TIMEOUT 500
int main(int argc, char* argv[]){
    int socket_fd;
    int on = 1;
    int ret = -1;
    int flag = 1;
    //并发10个
    int backlog = 10;
    int accept_fd;
    char in_buff[MESSAGE_LEN] = {0,};
    struct sockaddr_in localaddr, remoteaddr;
    //用man socket可查函数具体
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_fd==-1){
	std::cout<<"Failed to create socket!"<<std::endl;
	exit(-1);
    }
    //设为非阻塞
    flag = fcntl(accept_fd, F_GETFL, 0);
    fcntl(accept_fd, F_SETFL, flag|O_NONBLOCK);
    ret = setsockopt(
	socket_fd, 
     	SOL_SOCKET, 
	SO_REUSEADDR, 
	&on, 
	sizeof(on)
    );
    
    if(ret==-1){
	std::cout<<"Failed to set socket options!"<<std::endl;
    }
    localaddr.sin_family = AF_INET;
    localaddr.sin_port = PORT;
    //所有地址都能侦听这个端口:
    localaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(localaddr.sin_zero),8);
    ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
    if(ret==-1){
	std::cout<<"Failed to bind addr!"<<std::endl;
	exit(-1);
    }
    //开启监听
    ret = listen(socket_fd, backlog);
    if(ret==-1){
	std::cout<<"Failed to listen socket!"<<std::endl;
	exit(-1);
    }
   
    //创建描述符
    int epoll_fd;
    epoll_fd = epoll_create(256);
    struct epoll_event ev;
    //侦听数据到来事件
    ev.events = EPOLLIN;
    //这样通过ev.data.fd就可以知道是那个文件发的,如果拿到事件描述符为socket_fd,说明需要创建新socket
    ev.data.fd = socket_fd;
    //侦听描述符
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev);
   
    struct epoll_event events[MAX_EVENTS];
    int event_number;
    while(1){
	//开启侦听描述符, events为epoll数组,MAX_EVENTS为最大事件数,返回同一时刻并发数
        event_number = epoll_wait(epoll_fd, events, MAX_EVENTS,TIMEOUT);
	for(int i=0; i<event_number; i++){
	    //如果是侦听者socket,创建新连接
	    if(events[i].data.fd==socket_fd){
		socklen_t addr_len = sizeof(struct sockaddr);
		accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
		//设为非阻塞
		flag = fcntl(accept_fd, F_GETFL, 0);
		fcntl(socket_fd, F_SETFL, flag|O_NONBLOCK);
		//添加到epoll进行侦听
		ev.events = EPOLLIN|EPOLLET;
		ev.data.fd = accept_fd;
		epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);
	    }else if(events[i].events & EPOLLIN){
		do{
		    memset(in_buff, 0, MESSAGE_LEN);
	            ret = recv(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
	            if(ret==0){
		        close(events[i].data.fd);
		    }
		    if(ret == MESSAGE_LEN){
		        std::cout<<"maybe have data"<<std::endl;
		    }
		}while(ret < 0 && errno == EINTR);//EINTR操作中断,重新读

		if(ret < 0){
		    switch(errno){
			case EAGAIN://读完数据
			    break;
			default:
			    break;
		    }
		}
		if(ret>0){
		    std::cout<<"recv:"<<in_buff<<std::endl;
		    //数据原样回传
	            send(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
		}
	    }
	}
    }
    close(socket_fd);
    return 0;
}

epoll+fork实现高性能网络服务器

修改后的server代码:

#include<iostream>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/epoll.h>
#include <sys/wait.h>
#include<sys/types.h>
#define PORT 8111
#define MESSAGE_LEN 1024
#define MAX_EVENTS 20
#define TIMEOUT 500
#define MAX_PROCESS 4
int main(int argc, char* argv[]){
    int socket_fd;
    int on = 1;
    int ret = -1;
    int flag = 1;
    pid_t pid = -1; 
    //并发10个
    int backlog = 10;
    int accept_fd;
    char in_buff[MESSAGE_LEN] = {0,};
    struct sockaddr_in localaddr, remoteaddr;
    //用man socket可查函数具体
    socket_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(socket_fd==-1){
	std::cout<<"Failed to create socket!"<<std::endl;
	exit(-1);
    }
    //设为非阻塞
    flag = fcntl(accept_fd, F_GETFL, 0);
    fcntl(accept_fd, F_SETFL, flag|O_NONBLOCK);
    ret = setsockopt(
	socket_fd, 
     	SOL_SOCKET, 
	SO_REUSEADDR, 
	&on, 
	sizeof(on)
    );
    
    if(ret==-1){
	std::cout<<"Failed to set socket options!"<<std::endl;
    }
    localaddr.sin_family = AF_INET;
    localaddr.sin_port = PORT;
    //所有地址都能侦听这个端口:
    localaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(localaddr.sin_zero),8);
    ret = bind(socket_fd, (struct sockaddr *)&localaddr, sizeof(struct sockaddr));
    if(ret==-1){
	std::cout<<"Failed to bind addr!"<<std::endl;
	exit(-1);
    }
    //开启监听
    ret = listen(socket_fd, backlog);
    if(ret==-1){
	std::cout<<"Failed to listen socket!"<<std::endl;
	exit(-1);
    }
   
    //创建描述符
    int epoll_fd;
    struct epoll_event ev;
    struct epoll_event events[MAX_EVENTS];
    int event_number;
    //创建预设进程
    for(int i = 0; i<MAX_PROCESS; i++){
	if(pid != 0){
	    pid = fork();
	}
    }
    if(pid  == 0){
    	epoll_fd = epoll_create(256);
	ev.events = EPOLLIN;
	ev.data.fd = socket_fd;//由于socket_fd是在fork前创建,因此epoll非阻塞时,有连接进来将出现惊群现象,然后只有一个进程将抢到,其他进程再次进入睡眠
	epoll_ctl(epoll_fd, EPOLL_CTL_ADD, socket_fd, &ev);
    	while(1){
	    //开启侦听描述符, events为epoll数组,MAX_EVENTS为最大事件数,返回同一时刻并发数
	    event_number = epoll_wait(epoll_fd, events, MAX_EVENTS,TIMEOUT);
	    for(int i=0; i<event_number; i++){
	        //如果是侦听者socket,创建新连接
	        if(events[i].data.fd==socket_fd){
	    	    socklen_t addr_len = sizeof(struct sockaddr);
		    accept_fd = accept(socket_fd, (struct sockaddr *)&remoteaddr, &addr_len);
		    //设为非阻塞
		    flag = fcntl(accept_fd, F_GETFL, 0);
		    fcntl(socket_fd, F_SETFL, flag|O_NONBLOCK);
		    //添加到epoll进行侦听
		    ev.events = EPOLLIN|EPOLLET;
		    ev.data.fd = accept_fd;
		    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, accept_fd, &ev);
	        }else if(events[i].events & EPOLLIN){
		    do{
		        memset(in_buff, 0, MESSAGE_LEN);
	                ret = recv(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
	                if(ret==0){
		            close(events[i].data.fd);
		        }
		        if(ret == MESSAGE_LEN){
		            std::cout<<"maybe have data"<<std::endl;
		        }
		    }while(ret < 0 && errno == EINTR);//EINTR操作中断,重新读

		    if(ret < 0){
		        switch(errno){
			    case EAGAIN://读完数据
			        break;
			    default:
			        break;
		        }
		    }
		    if(ret>0){
		        std::cout<<"recv:"<<in_buff<<std::endl;
		        //数据原样回传
	                send(events[i].data.fd, (void*)in_buff, MESSAGE_LEN, 0);
		    }
	        }
	    }
        }
        close(socket_fd);
    }else{//pid!=0父进程 
	do{
	     pid = waitpid(-1, NULL, 0);//等待子进程结束, 否则先结束父进程,子进程就会变成由init进程管理的进程,waitpid(-1, NULL, 0)在所有子进程停止后会返回-1
	}while(pid!=-1);
        return 0;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值