内涵:Unix网络编程之细节处理决定成败


如果在设计服务器的时候,不注重细节处理,那么这样的服务器仅仅只能算是小孩儿的游戏,是不具有实用性的,为此单开一篇博客来记录服务器设计中的细枝末节。

#close之后会发生什么?
##问题描述
      close一个TCP套接字的默认行为是把该套接字标记成已关闭。该套接字不能再由调用进程调用,即这个套接字以后就不能再作为read或write的参数。但要注意TCP将尝试---------发送已排队等待发送到对端的任何数据----------(套接字发送缓冲区里面的数据),发送完毕后发生的是正常的TCP连接终止序列。

##问题解答
     至于如何设置:主要有1/SO_LINGER套接字选项来决定;2/若由close转换为shutdown则通过配合SHUT_RD和SHUT_WR来使用
#Ctrl+D就正常终止进程了吗?
##问题描述
        当通过客户端输入Ctrl+D时来终止与服务器端的连接,如果处理不慎,往往会有所残留(通常对于新手会造成,客户进程已关闭,但相对应的服务器子进程没有被真正的关闭而是进入到“僵死”状态)。这样的原因是:当服务器子进程终止时,给父进程发送一个SIGCHLD信号,而对于新手的程序,很有可能会忽略这些细节。如果我们没有在代码中捕获该信号,对其进行信号处理,而该信号的默认行为就是忽略我吧,哈哈。父进程未加处理这个信号,子进程就将进入“僵死”状态。
 ##做一个实验
我们以大家都会遇到的第一个服务-客户程序为例。这个程序无论是会是任何网络编程书上的第一个例子:代码如下:

#include "config.h"
void str_echo(int sockfd)
{
  ssize_t n;
  char buf[MAXLINE];
  again:
     while((n=read(sockfd,buf,MAXLINE))>0)
    	 write(sockfd,buf,n);
     if(n<0&& errno ==EINTR)
    	   goto again;
     else if(n<0)
    	 {perror("str_echo : read error");
    	   exit(1);
    	 }
}
int main(int argc,char **argv)
{
   int listenfd,connfd;
   pid_t childpid;
   socklen_t clilen;
   struct sockaddr_in cliaddr,servaddr;
    /*step 1: set up a listen socket*/
   listenfd=socket(AF_INET,SOCK_STREAM,0);
   /*step 2: bind server address*/
   bzero(&servaddr,sizeof(servaddr));
   servaddr.sin_family=AF_INET;
   servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
   servaddr.sin_port=htons(SERV_PORT);

   bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
   /*step3: listen*/
   listen(listenfd,LISTENQ);
   /*step4: */
   for( ; ; )
   {
	   connfd=accept(listenfd,(struct sockaddr *)&cliaddr,&clilen);
	   if((childpid=fork())==0)
	   {
		   close(listenfd);
		   str_echo(connfd);
		   close(connfd);
		   exit(0);
	  
 }
close(connfd);

 }

}                                              

客户端程序

#include "config.h"

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);

}
void str_cli(FILE *fp,int sockfd)
{
  char sendline[MAXLINE],recvline[MAXLINE];
  while(fgets(sendline,MAXLINE,fp)!=NULL)
  {
	  write(sockfd,sendline,strlen(sendline));
	  if(readline(sockfd,recvline,MAXLINE)==0)
		  {perror("str_cli:server terminated prematurely");
		    exit(1);
		  }
	  fputs(recvline,stdout);
  }
}
int main(int argc,char **argv)
{
   int sockfd;
   struct sockaddr_in servaddr;

   if(argc!=2)
	   {perror("usage:tcpcli <IPaddress>");
	      exit(1);
	   }


   /*step1   */
   sockfd=socket(AF_INET,SOCK_STREAM,0);
   /*step2   */
   bzero(&servaddr,sizeof(servaddr));
   servaddr.sin_family=AF_INET;
   servaddr.sin_port=htons(SERV_PORT);
   inet_pton(AF_INET,argv[1],&servaddr.sin_addr);
   connect(sockfd,(struct sockaddr *) &servaddr,sizeof(servaddr));
   str_cli(stdin,sockfd);
   exit(0);

}        

头文件

#ifndef CONFIG_H_
#define CONFIG_H_

#include <sys/socket.h>// socket functions
#include <unistd.h>// fork,read,write functions
#include <netinet/in.h>//address struct
#include <sys/errno.h>
#include <stdio.h>
#include <string.h> //bzero function
#include <stdlib.h>




#define SERV_PORT 9877
#define LISTENQ   100
#define MAXLINE   100






#endif /* CONFIG_H_ */



                   

例如上面这个程序,当我们按下ctrl+d终止之后,输入ps -a -o pid,ppid,tty,stat,args,wchan,我们可以看到当前进程的状态,状态Z代表僵死(zombie)
PID PPID TT STAT COMMAND WCHAN
72432 72361 pts/0 S+ ./server_cha5 inet_csk_accept
72469 72432 pts/0 Z+ [server_cha5] exit
72577 72433 pts/1 R+ ps -a -o pid,ppid,tty,stat, -
##解决办法

signal(SIGCHLD,sig_chld);

调用signal函数处理子进程在被关闭的时候传递给父进程的SIGCHLD的信号(其实本身已经有signal函数处理,但默认处理为忽略该信号,我们这里需要自己定义如何处理该信号)。下面为具体接收到SIGCHLD的具体处理函数。

void sig_chld(int signo)
{
	pid_t pid;
	int stat;
	pid=wait(&stat);
	printf("child %d terminated\n",pid);
	return;

}

注意点:signal函数的调用应该在listen()函数之后,fork()函数之前
我们再来看下添加完信号处理之后的运行情况:
PID PPID TT STAT COMMAND WCHAN
6395 6361 pts/1 S+ ./server_cha5 inet_csk_accept
6468 6396 pts/2 R+ ps -a -o pid,ppid,tty,stat, -
在终端CTRL+D,之后,

ps -a -o pid,ppid,tty,stat,args,wchan      

查看各个进程的状态,发现客户进程和,与之相连接的服务器子进程已经正常终止,无僵尸进程,只剩下服务器父进程阻塞与accept等待客户的再一次连接。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学弟

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值