一个文本回射C/S程序并讨论:fork、信号处理机制、僵死进程处理


最近在学习unix网络编程卷一学习,跟着书,敲了下代码。在这里做些记录与复习。

前言

之前初认识了linux下的一些API做了简单的时间获取C/S程序模型。现在进一步了解了关于并发服务器、信号处理、僵死进程等等。做了一个文本回射C/S模型。并做下笔记。

概述

文本回射服务器:
1)客户从终端输入一行文本,发送给服务器
2)服务器从网络输入读入这行文本,并回射给客户
3)客户从网络输入读取这行文本,并显示在终端上
4)服务器每接受一个客户的连接就新建一个子进程来服务这个客户
5)当客户退出需要回收子进程的资源

fork

1)、fork为每个客户派生一个服务进程,子进程需要关闭监听套接字,父进程需要关闭这个已连接的套接字。因为子进程会复制父进程的资源(资源文件描述符+1,不是真的复制文件),如果没有关闭,在客户退出后,没能关闭这些资源
2)、fork返回两个pid_t类型的数值。父进程返回子进程的pid(>0)子进程返回(0)

进程终止

1)当我们键入EOF字符时(ctrl+d)或者客户端终止,客户端发送FIN给服务端,服务端响应ACK,省略其他tcp连接释放步骤。
2)当服务器接收到客户端的FIN时,服务器子进程阻塞于readline调用,于是马上返回,子进程调用exit终止。随之子进程关闭所打开的描述符。
3)子进程终止时会给父进程发送一个SIGCHLD信号,需要对它进行捕捉,否则默认忽略,子进程进入僵死状态。

信号捕捉

1)为了给子进程收尸,父进程必须对其捕捉。(SIGKILL和SIGTOP不能捕捉)
2)当信号来了,程序实际是进入软中断状态,需要设置一个信号处理函数。
3)Sigfunc signal(int signo, Sigfuncfunc)是参照原书封装了一个unix原生API的信号函数,signo是信号编号,func是一个函数指针,信号处理函数的入口。sigfunc是一个类型定义typedef void Sigfunc(int);函数类型

僵尸进程

定义

是指子进程退出时,父进程并未对其发出的SIGCHLD信号进行适当处理,导致子进程停留在僵死状态等待其父进程为其收尸,这个状态下的子进程就是僵死进程。

产生的原因

子进程退出时,父进程没有为子进程收尸。

危害

子进程退出时,虽然会关闭它的资源描述符(比如套接字),但是进程描述符仍然占用,需要父进程来回收,系统的进程描述符是有限的,大量的僵死进程将拖垮服务器性能,最终无法新建进程。

处理方法

父进程捕捉信号,调用wait或者waitpid来进行回收

wait和waitpid函数

#include <sys/wait.h>
pid_t wait(int *staloc);
pid_t waitpid(pid_t pid, int *staloc, int options);
//返回:若成功则返回进程ID,若出错则为0或者-1,以及由staloc返回的子进程终止状态。

区别:wait_pid就等待哪个进程以及是否阻塞给了更多的控制。pid可以指定想等待的进程,值-1表示等待第一个终止的子进程。options可指定附加选项,比如此文用的WNOHANG告诉内核在没有已终止子进程时不要阻塞。

为什么要设置非阻塞
因为linux的信号机制,并不排队,当你在处理其中一个信号的时候可能错过了其他信号的捕捉。因此这里的void sig_chld(int signo)信号处理函数使用while里面调用waitpid以便捕捉所有信号。

完整代码

自己常用的函数集
mfs.cpp:

#include "mfs.h"
#include <iostream>
using namespace std;

/************iofunction***********/ 
ssize_t readline(int fd, void *vptr, size_t n){
           size_t  nleft;
           ssize_t nread;
           char *  ptr;
           ptr =(char*) vptr;
           nleft = n;
           while(nleft > 0){
                   if( (nread = read(fd, ptr, nleft)) < 0){
                           cout<<nread<<endl;
                          if(errno == EINTR)//中断重新调用read
                                  nread = 0;
                          else return (-1);
                  }else if (!nread)//EOF
                          break;
                  
                  nleft -= nread;
                  ptr   += nread;
                if(ptr[nread-1]='\n')break;//readline function return when meet '\n'
          }
          return (n-nleft);
  }
 ssize_t writen(int fd, void const *vptr, size_t n){
          size_t nleft;
          ssize_t nwritten;;
          const char *ptr;
          ptr = (const char *)vptr;
          nleft = n;
          while(nleft > 0){
                  if( (nwritten = write(fd, ptr, nleft)) <=0){
                  if(nwritten < 0 && errno == EINTR)
                          nwritten = 0;
                  else return(-1);
                  }
                  nleft -= nwritten;
                  ptr +=nwritten;
          }
          return n;
  }

/**********Signal function***********/
Sigfunc *signal(int signo, Sigfunc*func){
	struct sigaction act, oact;
        /*
        If  SA_SIGINFO  is specified in sa_flags, then sa_sigaction (instead of
        sa_handler) specifies the signal-handling function  for  signum.   This
        function receives three arguments, as described below.
        如果你的应用程序只需要接收信号,而不需要接收额外信息,那你需要的设置的是sa_handler,而不是sa_sigaction
        */ 
        act.sa_handler =func;
        sigemptyset(&act.sa_mask);
        act.sa_flags = 0;
        /*SIGALRMS是计时器中断,否则应该设置自动重启*/
        if(signo == SIGALRM){
                #ifdef SA_INTERRUPT
                act.sa_flags |= SA_INTERRUPT;
                #endif
        }else{
                #ifdef SA_RESTART
                act.sa_flags |= SA_RESTART;//被中断的调用自动重启
                #endif
        }
        if (sigaction(signo, &act, &oact) < 0)
                return (SIG_ERR);
        return (oact.sa_handler);//返回发生信号时的前一个动作
}
void sig_chld(int signo){
        pid_t pid;
        int stat;
        while( (pid = waitpid(-1, &stat,WNOHANG)) >0 )
        printf ("chaild %d terminated\n", pid);
        return ;
}

server.cpp

#include "mfs.h"
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
using namespace std;
void  str_serv(int sockfd){
    ssize_t n;
    char buf[MAXLINE];
    while((n = readline(sockfd, buf ,MAXLINE))> 0){
        buf[n]='\0';
        std::cout<<buf<<std::endl;
       writen(sockfd, buf, strlen(buf));      
    }
}

int main(int argc ,char **argv){
    int listenfd;
    int connfd;
    pid_t childpid;
    socklen_t clilen;
    struct sockaddr_in cliaddr;
    struct sockaddr_in servaddr;
    char buff[MAXLINE];
    
    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(2020);
    
    bind(listenfd, (SA *)&servaddr, sizeof(servaddr));
    
    listen(listenfd, LISTENQ);
    signal(SIGCHLD, sig_chld);
    for(; ;){
        clilen = sizeof(cliaddr);
      	if(  (connfd = accept(listenfd, (SA*)&cliaddr, &clilen)) <0  ){
		if(errno == EINTR)
			continue; //restart
		else perror("accept error");	
	}
 
	if( (childpid = fork()) == 0){
        printf("connection from %s, port %d\n",
        inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff) ),ntohs(cliaddr.sin_port));
            close(listenfd);
            str_serv(connfd);
            exit(0);
        }
        close(connfd);
    }
}

client.cpp

#include "mfs.h"
#include <iostream>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>


void str_cli(int sockfd){
    std::string sendline;
    char recvline[MAXLINE];
    int nread;
    while(std::cin>>sendline){
        sendline+="\r\n";
        int fuck = writen(sockfd, sendline.c_str(), sendline.size());
        std::cout<<"等待信息返回"<<std::endl;
        if( (nread=readline(sockfd, recvline, MAXLINE)) >0){
            recvline[nread]=0;
          
        }    
            if(nread < 0) {
            std::cout<<"nread <0"<<std::endl;
            exit(1);
            }
        std::cout<<recvline<<std::endl;
    }
}

int main (int argc, char **argv){
    std::cout<<1<<std::endl;
    int sockfd;
    struct sockaddr_in servaddr;
    if(argc != 2){
        perror ("usage: tcpcli<IPaddress>");
        exit(1);
    }
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port =htons(2020);
    inet_pton(AF_INET,argv[1],&servaddr.sin_addr);  

    if(connect(sockfd, (SA *)&servaddr, sizeof(servaddr) ))
    std::cout<<"connect error"<<std::endl;
    str_cli(sockfd);

    exit(0);
}

参考:《unix网络编程 卷1:套接字联网API》

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值