多进程服务器端

  拥有2个运算器的CPU称作双核CPU,拥有4个运算器的CPU称作4核CPU。核的个数与可同时运行的进程数相同。

通过fork函数创建进程

fork函数创建调用的进程副本,两个进程都将执行fork函数返回后的语句。 

#include <stdio.h>
#include <unistd.h>
int gval=10;

int main(int argc, char *argv[])
{
    pid_t pid;
    int lval=20;
    gval++, lval+=5;

    pid=fork();
    if(pid==0)	// if Child Process
        gval+=2, lval+=2;
    else			// if Parent Process
        gval-=2, lval-=2;

    if(pid==0)
        printf("Child Proc: [%d, %d] \n", gval, lval);
    else
        printf("Parent Proc: [%d, %d] \n", gval, lval);
    return 0;
}

 

进程和僵尸进程 

  一个已经终止但是其父进程尚未对其进行善后处理(获取终止子进程的有关信息,释放它仍占用的资源)的进程称为僵尸进程(zombie)。

产生僵尸进程的原因

  向exit函数传递的参数值和main函数的return语句返回的值都会传递给操作系统。而操作系统不会销毁子进程,直到把这些值传递给产生该子进程的父进程。处在这种状态下的进程就是僵尸进程。也是就说,将子进程变成僵尸进程的正是操作系统。

  操作系统不会主动把这些值传递给父进程,只有父进程主动发起请求(函数调用)时,操作系统才会传递该值。如果父进程未主动要求获得子进程的结束状态值,操作系统将一直保存,并让子进程长时间处于僵尸进程状态。

#include <stdio.h>
#include <unistd.h>

int main(int argc, char *argv[])
{
    pid_t pid=fork();

    if(pid==0)     // if Child Process
    {
        puts("Hi I'am a child process");
    }
    else
    {
        printf("Child Process ID: %d \n", pid);
        sleep(100);     // Sleep 30 sec.
    }

    if(pid==0)
        puts("End child process");
    else
        puts("End parent process");
    return 0;
}

父进程暂停30秒,如果父进程终止,处于僵尸状态的子进程将同时被销毁。 

 销毁僵尸进程1:利用wait函数

//wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int status;
    pid_t pid=fork();

    if(pid==0)
    {
        return 3; //终止子进程
    }
    else
    {
        printf("Child PID: %d \n", pid);
        pid=fork();
        if(pid==0)
        {
            exit(7); //终止子进程
        }
        else
        {
            printf("Child PID: %d \n", pid);
            wait(&status);  //将之间终止的子进程信息保存到status变量,同时相关子进程被完全销毁
            if(WIFEXITED(status))
                printf("Child send one: %d \n", WEXITSTATUS(status));

            wait(&status);
            if(WIFEXITED(status))
                printf("Child send two: %d \n", WEXITSTATUS(status));
            sleep(30);     // Sleep 30 sec.
        }
    }
    return 0;
}

  调用wait函数时,如果没有已终止的子进程,那么程序将阻塞直到有子进程终止,因此需谨慎调用该函数。

销毁僵尸进程2:使用waitpid函数 

 更多参数相关细节可参考《unix环境高级编程》

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[])
{
    int status;
    pid_t pid=fork();

    if(pid==0)
    {
        sleep(15);
        return 24;
    }
    else
    {
        while(!waitpid(-1, &status, WNOHANG))
        {
            sleep(3);
            puts("sleep 3sec.");
        }

        if(WIFEXITED(status))
            printf("Child send %d \n", WEXITSTATUS(status));
    }
    return 0;
}

信号处理

  此处的“信号”是在特定事件发生时由操作系统向进程发送的消息。为了响应该消息,执行与消息相关的自定义操作的过程称为“处理”或“信号处理”。

信号和signal函数

  调用该函数时,第一个参数为特殊情况信息,第二个参数为特殊情况下将要调用的函数的地址值(指针),第二个参数的int形参就是收到的信号值。发生第一个参数代表的情况时,调用第二个参数所指的函数。

signal函数中注册的部分特殊情况和对应的常数:

alarm函数:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void timeout(int sig) //信号处理函数(信号处理器)
{
	if(sig==SIGALRM)
		puts("Time out!");

	alarm(10);
}
void keycontrol(int sig)
{
	if(sig==SIGINT)
		puts("CTRL+C pressed");
}

int main(int argc, char *argv[])
{
	int i;
	signal(SIGALRM, timeout);
	signal(SIGINT, keycontrol);
	alarm(10);

	for(i=0; i<3; i++)
	{
		puts("wait...");
		sleep(100);
	}
	return 0;
}

 产生信号时,为了调用信号处理器,将唤醒由于调用sleep函数而进入阻塞状态的进程。而且,进程一旦被唤醒,就不会再进入(由上一次调用sleep导致的)睡眠状态。

利用sigaction函数进行信号处理

  sigaction函数完全可以替代signal函数,而且更稳定,因为signal函数在UNIX的不同操作系统中可能存在区别,但sigaction函数完全相同。

 sa_handler成员保存信号处理函数的指针值(地址值)。sa_mask和sa_flags用于指定信号相关的选项和特性。(我看网上对这个结构体的定义与书上的不同,先在这儿做个注释,书上后续的内容可能会有补充)

sigaction函数的示例:

#include <stdio.h>
#include <unistd.h>
#include <signal.h>

void timeout(int sig)
{
	if(sig==SIGALRM)
		puts("Time out!");
	alarm(2);	
}

int main(int argc, char *argv[])
{
	int i;
	struct sigaction act;
	act.sa_handler=timeout;
	//int sigemptyset(sigset_t *set) 将信号集初始化为空
	sigemptyset(&act.sa_mask);  //将sa_mask成员的所有位初始化为0
	act.sa_flags=0;
	sigaction(SIGALRM, &act, 0);

	alarm(2);

	for(i=0; i<3; i++)
	{
		puts("wait...");
		sleep(100);
	}
	return 0;
}

利用信号处理技术消灭僵尸进程

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>

void read_childproc(int sig)
{
	int status;
	pid_t id=waitpid(-1, &status, WNOHANG);
	if(WIFEXITED(status))
	{
		printf("Removed proc id: %d \n", id);
		printf("Child send: %d \n", WEXITSTATUS(status));
	}
}

int main(int argc, char *argv[])
{
	pid_t pid;
	struct sigaction act;
	act.sa_handler=read_childproc;
	sigemptyset(&act.sa_mask);
	act.sa_flags=0;
	sigaction(SIGCHLD, &act, 0);

	pid=fork();
	if(pid==0)
	{
		puts("Hi! I'm child process");
		sleep(10);
		return 12;
	}
	else
	{
		printf("Child proc id: %d \n", pid);
		pid=fork();
		if(pid==0)
		{
			puts("Hi! I'm child process");
			sleep(10);
			exit(24);
		}
		else
		{
			int i;
			printf("Child proc id: %d \n", pid);
			for(i=0; i<5; i++)
			{
				puts("wait...");
				sleep(5);
			}
		}
	}
	return 0;
}

基于多任务的并发服务器

   每当有客户端请求服务时(连接请求),服务器端都创建子进程以提供服务。

 调用fork函数后,父进程将文件描述符复制给子进程,而不是套接字。套接字并非进程所有,而是属于操作系统,只是进程拥有代表相应套接字的文件描述符。只有当套接字对应的文件描述符都销毁后,才能销毁套接字。因此,调用fork函数后,要将无关的套接字文件描述符关掉。

 

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);
void read_childproc(int sig);

int main(int argc, char *argv[])
{
    int serv_sock, clnt_sock;
    struct sockaddr_in serv_adr, clnt_adr;

    pid_t pid;
    struct sigaction act;
    socklen_t adr_sz;
    int str_len, state;
    char buf[BUF_SIZE];
    if(argc!=2) {
        printf("Usage : %s <port>\n", argv[0]);
        exit(1);
    }

    act.sa_handler=read_childproc;  //设置信号处理函数
    sigemptyset(&act.sa_mask);
    act.sa_flags=0;
    state=sigaction(SIGCHLD, &act, 0);
    serv_sock=socket(PF_INET, SOCK_STREAM, 0);
    memset(&serv_adr, 0, sizeof(serv_adr));
    serv_adr.sin_family=AF_INET;
    serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
    serv_adr.sin_port=htons(atoi(argv[1]));

    if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
        error_handling("bind() error");
    if(listen(serv_sock, 5)==-1)
        error_handling("listen() error");

    while(1)
    {
        adr_sz=sizeof(clnt_adr);
        clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
        if(clnt_sock==-1)
            continue;
        else
            puts("new client connected...");
        pid=fork();  //有新的连接请求时创建子进程
        if(pid==-1)
        {
            close(clnt_sock);
            continue;
        }
        if(pid==0)
        {
            close(serv_sock);  //关闭父进程传递的用于监听的文件描述符
            while((str_len=read(clnt_sock, buf, BUF_SIZE))!=0)
                write(clnt_sock, buf, str_len);

            close(clnt_sock);
            puts("client disconnected...");
            return 0;
        }
        else
            close(clnt_sock);  //关闭传递给子进程的文件描述符
    }
    close(serv_sock);
    return 0;
}

void read_childproc(int sig)
{
    pid_t pid;
    int status;
    pid=waitpid(-1, &status, WNOHANG);
    printf("removed proc id: %d \n", pid);
}
void error_handling(char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

分割TCP的I/O程序

 分割I/O程序优点:1.程序实现更加简单,读写可以分别在父进程与子进程中实现

2.提高频繁交换数据的程序性能

 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);
void read_routine(int sock, char *buf);
void write_routine(int sock, char *buf);

int main(int argc, char *argv[])
{
	int sock;
	pid_t pid;
	char buf[BUF_SIZE];
	struct sockaddr_in serv_adr;
	if(argc!=3) {
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}
	
	sock=socket(PF_INET, SOCK_STREAM, 0);  
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=inet_addr(argv[1]);
	serv_adr.sin_port=htons(atoi(argv[2]));
	
	if(connect(sock, (struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1)
		error_handling("connect() error!");

	pid=fork();
	if(pid==0)
		write_routine(sock, buf);
	else 
		read_routine(sock, buf);

	close(sock);
	return 0;
}

void read_routine(int sock, char *buf)
{
	while(1)
	{
		int str_len=read(sock, buf, BUF_SIZE);
		if(str_len==0)
			return;

		buf[str_len]=0;
		printf("Message from server: %s", buf);
	}
}
void write_routine(int sock, char *buf)
{
	while(1)
	{
		fgets(buf, BUF_SIZE, stdin);
		if(!strcmp(buf,"q\n") || !strcmp(buf,"Q\n"))
		{	
			shutdown(sock, SHUT_WR);  //通过shutdown向服务器发送
			return;
		}
		write(sock, buf, strlen(buf));
	}
}
void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

close和shutdown的具体细节可以参考这篇文章,写的很好:tcp - close和shutdown关闭TCP连接_个人文章 - SegmentFault 思否

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值