TCP/IP网络编程学习(10):利用信号处理技术消灭僵尸进程和基于多任务的并发处理器

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

子进程终止时产生SIGCHLD信号。就可以捕捉信号终止子进程。

#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);//等待10s终止
		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;
}

基于多任务的并发服务器

在这里插入图片描述
每当有客户端请求服务时,服务器都创建子进程以提供服务。

  1. 服务器端(父进程)通过调用 accept画数受理连接请求 。
  2. 此时获取的套接字文件描述符创建并传递给子进程 。(因为子进程会复制父进程拥有的所有资源 。实际上根本不用另外经过传递文件描述符的过程 。)
  3. 子进程利用传递来 的文件描述符提供服务 。
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);
	}

通过fork函数复制文件描述符

套接字属于操作系统,fork子进程只是将父进程的文件描述符拿了过去。进程拥有代表相应套接字的文件描述符。父进程将2个套接字(一个是服务器端套接字,另一个是与客户端连接的套接字)文件描述符复制给子进程 。
在这里插入图片描述

1个套接字中存在2个文件描述符时,只有2个文件描述符都终止 (销毁)后,才能销毁套接字 。如果维持阁中的连接状态,即使子进程销毁了与客户端连接的套接字文件描述符,也无法完全销毁套接字(服务器端套接字同样如此)。 因此,调用fork函数后,要将无关的套接字文件描述符关掉,

在这里插入图片描述

一次accept就会创建一个套接字。

分割TCP的I/O程序

传输数据后需要等待服务器端返回的数据,因为程序代码中重复调用了 read和write函数 。 只能能这么写的原因之一是,程序在 1 个进程中运行 。 但现在可以创建多个进程,因此可以分割数据收发过程 。 客户端的父进程负责接收数据,额外创建的子进程负责发送数据 。 分割后,不同进程分别负责输入和输出,这样无论客户端是否从服务器端接收完数据都可以进行传输 。
在这里插入图片描述
之前的回声客户端要接收到服务器的数据才发数据,现在可以连发数据。
在这里插入图片描述
分割I/O后的客户端发送数据时不必考虑接收数据的情况,因此可以连续发送数据,由此提高同一时间内传输的数据量 。 这种差异在网速较慢时尤为明显 。

#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);
			return;
		}
		write(sock, buf, strlen(buf));
	}
}
void error_handling(char *message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值