在讨论之前先给出一段代码,其中存在一个很大的隐患,就是僵尸进程!!!!可用top命令进行查看,如下图所示:
由上图可看出,存在1个zombie(僵尸进程)!!!
下面就给出具体的实现:
config.h:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#include <netinet/in.h>
#include <netdb.h>
#include <arpa/inet.h>
const int MAX_LINE = 2048;
const int PORT = 9877;
const int BACKLOG = 10;
const int LISTENQ = 6666;
const int MAX_CONNECT = 20;
服务器端:
#include "config.h"
int main(int argc,char **argv)
{
//声明服务器地址和客户链接地址
struct sockaddr_in servaddr,cliaddr;
//声明服务器监听套接字和客户端链接套接字
int listenfd,connfd;
pid_t childpid;
//声明缓冲区
char buf[MAX_LINE];
socklen_t clilen;
//(1)初始化监听套接字litenfd
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket error");
exit(1);
}
//(2)设置服务器sockaddr_in结构
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
//(3)绑定套接字和端口
if(bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
perror("bind error");
exit(1);
}
//(4)监听客户请求
if(listen(listenfd,LISTENQ) < 0){
perror("listen error");
}
//(5)接受客户端请求
for(; ;){
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd,(struct sockaddr *)&cliaddr,&clilen)) < 0){
perror("accept error");
exit(1);
}
//新建子进程单独处理链接
if((childpid = fork()) == 0){
close(listenfd);
//str_echo
ssize_t n;
char buff[MAX_LINE];
while((n = read(connfd,buff,MAX_LINE)) > 0){
write(connfd,buff,n);
}
exit(0);
}
close(connfd);
}
//(6)关闭监听套接字
close(listenfd);
}
客户端:
#include "config.h"
//readline函数实现
ssize_t readline(int fd,char *vptr,size_t maxlen)
{
ssize_t n,rc;
char c,*ptr;
ptr = vptr;
for(n = 1;n < maxlen;n++){
if((rc = read(fd,&c,1)) == 1){
*ptr++ = c;
if(c == '\n'){
break;
}
}else if(rc == 0){
*ptr = 0;
return(n - 1);
}else {
return(-1);
}
}
*ptr = 0;
return(n);
}
int main(int argc,char **argv)
{
//声明套接字和链接服务器地址
int sockfd;
struct sockaddr_in servaddr;
//判断是否为合法的输入
if(argc != 2){
perror("usage :tcpcli<IPaddress>");
exit(1);
}
//(1)创建套接字
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1){
perror("socket error");
exit(1);
}
//(2)设置链接服务器地址结构
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(PORT);
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) < 0){
printf("inet_pton error for %s\n",argv[1]);
exit(1);
}
//(3)发送链接服务器请求
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
perror("connect error");
exit(1);
}
//(4)消息处理
char sendline[MAX_LINE],recvline[MAX_LINE];
while(fgets(sendline,MAX_LINE,stdin) != NULL){
write(sockfd,sendline,strlen(sendline));
if(readline(sockfd,recvline,MAX_LINE) == 0){
perror("server terminated prematurely");
exit(1);
}
if(fputs(recvline,stdout) == EOF){
perror("fputs error");
exit(1);
}
}
//(5)关闭套接字
close(sockfd);
}
执行结果:
其实现的功能就是客户端发送什么,服务器就会回射回来什么。。。看似程序没什么问题,但其存在一个很大隐患就是僵尸进程,产生僵尸进程的最主要的原因就是:在UNIX 系统中,一个进程结束了,但是他的父进程没有等待(调用wait / waitpid)他, 那么他将变成一个僵尸进程。 但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程, 因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程, 看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init 来接管他,成为他的父进程……
僵尸进程的危害
由于子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程 到底什么时候结束. 那么会不会因为父进程太忙来不及wait子进程,或者说不知道 子进程什么时候结束,而丢失子进程结束时的状态信息呢? 不会。因为UNⅨ提供了一种机制可以保证只要
父进程想知道子进程结束时的状态信息, 就可以得到。这种机制就是: 在每个进程退出的时候,
内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)。直到
父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生
僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。
僵尸进程的避免
⒈父进程通过wait和waitpid等函数等待子进程结束,这会导致父进程挂起。
⒉ 如果父进程很忙,那么可以用signal函数为SIGCHLD安装handler,因为子进程结束后, 父进程会收到该信号,可以在handler中调用wait回收。
⒋ 还有一些技巧,就是fork两次,父进程fork一个子进程,然后继续工作,子进程fork一 个孙进程后退出,那么孙进程被init接管,孙进程结束后,init会回收。不过子进程的回收 还要自己做。(上述的文字摘自百度百科)
下面给出一个不会产生僵尸进程的示例:
config.h:
config.c:
服务器端:
客户端:
执行结果:
上述示例的功能就是完成客户端给服务器发送两个数,服务器进行求和,同时也没有僵尸进程的出现!!!
下面给出一个不会产生僵尸进程的示例:
config.h:
#ifndef _CONFIG_H_
#define _CONFIG_H_
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <strings.h>
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#define MAX_LINE 4096
#define SERV_PORT 9879
#define LISTENQ 6666
#define SA struct sockaddr
#define BACKLOG 10
typedef void Sigfunc(int);
struct args{
long arg1;
long arg2;
};
struct result{
long sum;
};
#endif
config.c:
#include "config.h"
void sig_chld(int signo)
{
pid_t pid;
int stat;
while((pid = waitpid(-1,&stat,WNOHANG)) > 0){
printf("child %d terminated\n",pid);
}
return;
}
Sigfunc *Signal(int signo,Sigfunc *func)
{
struct sigaction act,oact;
act.sa_handler = func;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
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);
}
ssize_t readn(int fd,void *vptr,size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while(nleft > 0){
if((nread = read(fd,ptr,nleft)) < 0){
if(errno == EINTR){
nread = 0;
}else{
return(-1);
}
}else if(nread == 0){
break;
}
nleft -= nread;
ptr += nread;
}
return(n - nleft);
}
void str_echo(int sockfd)
{
ssize_t n;
struct args args;
struct result result;
for(;;){
if((n = readn(sockfd,&args,sizeof(args))) == 0){
return;
}
//result.sum = args.arg1 ;
if(write(sockfd,&result,sizeof(result)) < 0){
perror("write error");
exit(1);
}
}
}
void str_cli(FILE *fp,int sockfd)
{
char sendline[MAX_LINE];
struct args args;
struct result result;
while(fgets(sendline,MAX_LINE,fp) != NULL){
if(sscanf(sendline,"%ld%ld",&args.arg1,&args.arg2) != 2){
printf("invalid input:%s",sendline);
continue;
}
if(write(sockfd,&args,sizeof(args)) < 0){
perror("write error");
exit(1);
}
if(readn(sockfd,&result,sizeof(result)) == 0){
perror("str_cli:server terminated prematurely");
exit(1);
}
result.sum = args.arg1 + args.arg2;
printf("%ld\n",result.sum);
}
}
服务器端:
#include "config.h"
int main(int argc,char **argv)
{
//定义监听套接字和连接套接字
int listenfd,connfd;
//定义子进程pid
pid_t childpid;
//套接字地址结构的长度
socklen_t clilen;
//ipv4套接字地址结构
struct sockaddr_in cliaddr,servaddr;
//信号处理函数
void sig_chld(int);
//创建套接字
if((listenfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket error");
exit(1);
}
//指定服务器ip和端口
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
//绑定协议地址和套接字
if((bind(listenfd,(SA *)&servaddr,sizeof(servaddr))) < 0){
perror("bind error");
exit(1);
}
//监听套接字
if(listen(listenfd,LISTENQ) < 0){
perror("listen error");
exit(1);
}
Signal(SIGCHLD,sig_chld);
for(;;){
clilen = sizeof(cliaddr);
if((connfd = accept(listenfd,(SA *)&cliaddr,&clilen)) < 0){
if(errno = EINTR){
continue;
}else{
perror("accept error");
exit(1);
}
}
//创建子进程
if((childpid = fork()) == 0){
//关闭监听套接字
close(listenfd);
str_echo(connfd);
exit(0);
}
//父进程关闭连接
close(connfd);
}
}
客户端:
#include "config.h"
int main(int argc,char **argv)
{
int sockfd;
struct sockaddr_in servaddr;
if(argc != 2){
printf("usage:tcpcli<IPaddress>");
exit(1);
}
if((sockfd = socket(AF_INET,SOCK_STREAM,0)) < 0){
perror("socket error");
exit(1);
}
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) < 0){
printf("inet_pton error for %s\n",argv[1]);
exit(1);
}
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)) < 0){
perror("connect error");
exit(1);
}
str_cli(stdin,sockfd);
exit(0);
}
执行结果:
上述示例的功能就是完成客户端给服务器发送两个数,服务器进行求和,同时也没有僵尸进程的出现!!!