6.TCP/IP流协议(处理粘包):包尾\n(recv/send)—防止僵尸进程

recv

ssize_t recv(int socket, void *buffer, size_t length, int flags);
返回值
	 >  0  成功接收数据大小
	 =  0  另外一端关闭了套接字
	 = -1  错误,需要获取错误码errno
	 
1.recv与read相比,只能用于socket流
2.多了一个辅助选项
	MSG_OOB   带外数据  紧急指针
	MSG_PEEK  偷窥缓冲区中的数据(预先读缓冲区,不将数据从缓冲区中读走)

MSG_PEEK:可以读数据,不从缓冲区中读走,利用次特点可以方便的实现按行读取数据
1.使用read函数:一个字符一个字符读,方法不好(会多次调用系统调用read方法)
2.使用recv函数+MSG_PEEK选项
	提前偷窥下缓冲区,缓冲区里边有数据后,把缓冲区中的数据读到内存中
	然后在内存中一个字节一个字节判断是否为\n

readline函数实现:recv+MSG_PEEK

//recv_peek:看一下缓冲区中有没有数据,并不移除内核缓冲区中的数据
ssize_t recv_peek(int fd,void* buf,size_t len){                                                                                  
  while(1){                                                                                                                      
    int ret=recv(fd,buf,len,MSG_PEEK);                                                                                           
    if(ret==-1 && errno==EINTR)                                                                                                  
      continue;                                                                                                                  
    return ret;                                                                                                        
  }                                                                                                                              
}

ssize_t readline(int fd,void* buf,size_t maxLine){                                                                               
  int ret;                                                                                                                       
  int nread;                                                                                                                     
  char* bufp=(char*)buf;                                                                                                         
  int nleft=maxLine;                                                                                                             
  while(1){                                                                                                                      
    ret=recv_peek(fd,bufp,nleft);                                                                                                
    if(ret<0) //失败                                                                                                        
      return ret;                                                                                                                
    else if(ret==0) //对方已关闭
      return ret;   
    //else if(ret>0) //recv_peekt偷窥到了ret个字节的数据                                                               
    nread=ret;                                                                                      
    int i;                                                                                                                       
    for(i=0;i<nread;i++){ //逐个判断读到的bufp中是否有\n                                                                                                        
      if(bufp[i]=='\n'){ //如果缓冲区中有\n                                                                                            
        ret=readn(fd,bufp,i+1); //读走数据                                                                             
        if(ret!=i+1)                                                                                                             
          exit(EXIT_FAILURE);                                                                                                    
        return ret; //有\n就返回,并返回读走的字节数                                                                       
      }                                                                                                                          
    }                                                                                                                    
                                                                                                                                 
    if(nread>nleft) //if 读到的数 > 一行最大数  —> 异常处理                                                                                                              
      exit(EXIT_FAILURE);
    nleft-=nread; //若缓冲区没有\n,把剩余的数据读走                                            
    ret=readn(fd,bufp,nread);                                                                                                    
    if(ret!=nread)                                                                                                               
      exit(EXIT_FAILURE);                                                                                                        
                                                                                                                                 
    bufp+=nread; //bufp指针后移后,再接着偷看缓冲区数据recv_peek,直到遇到\n                                                      
  }                                                                                                                              
}  

readline的使用:
	while(1){                                                                                                                  
	    char recvbuf[1024];                                                                                                      
	    int ret=readline(conn,recvbuf,1024);  //readline                                                                                   
	    if(ret<0)                                                                                                                
	        ERR_EXIT("readline");                                                                                                  
	    else if(ret==0){                                                                                                              
	        printf("peer close\n");     
	        close(conn);                                                                                           
	        break;                                                                                                                 
	    }     
	    else if(ret>0)                                                                                                                  
	    printf("recvline=%s\n",recvbuf);                                                                                                                                                                          
	}//while  

客户端服务器回射模型

客户端代码

#define ERR_EXIT(m) \
  do  \
  { \
    perror(m);  \
    exit(EXIT_FAILURE);  \
  }while(0);

ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,void* buf,size_t count);

ssize_t recv_peek(int fd,void* buf,size_t len);
ssize_t readline(int fd,void* buf,size_t maxLine);

int main(){
  int sockfd=socket(AF_INET,SOCK_STREAM,0);
  if(sockfd==-1)
    ERR_EXIT("socket");

  struct sockaddr_in svraddr;
  svraddr.sin_family=AF_INET;
  svraddr.sin_port=htons(8001);
  svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");

  if(connect(sockfd,(struct sockaddr*)&svraddr,sizeof(struct sockaddr))<0)
    ERR_EXIT("connect");
 
  printf("connect svr success:svraddr=%s,port=%d\n",inet_ntoa(svraddr.sin_addr),ntohs(svraddr.sin_port));

  char sendbuf[1024];
  char recvbuf[1024];
  printf("send=");
  while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL){
    //1.send message
    writen(sockfd,&sendbuf,strlen(sendbuf));
    //2.recv message
    int ret=readline(sockfd,recvbuf,sizeof(recvbuf));
    if(ret<0)
      ERR_EXIT("recvline");
    if(ret==0){
      printf("peer close\n");
      close(sockfd);
      break;
    }
    else if(ret>0)
      printf("recvline=%s",recvbuf);
    
    memset(&recvbuf,0,sizeof(recvbuf));
    memset(&sendbuf,0,sizeof(sendbuf));

    printf("send=");
  }
  return 0;
}
服务器代码

#define ERR_EXIT(m) \
  do  \
  { \
    perror(m);  \
    exit(EXIT_FAILURE);  \
  }while(0);

ssize_t readn(int fd,void* buf,size_t count);
ssize_t writen(int fd,void* buf,size_t count);

ssize_t recv_peek(int fd,void* buf,size_t len);
ssize_t readline(int fd,void* buf,size_t maxLine);
/*  方法2:错误的方法
如果有5个客户端连接服务器
当5个客户端同时死掉(5个子进程将会同时死掉)--->5个子进程同时向老爹发送SIGCHLD信号
但是:SIGCHLD是不可靠信号,因此老爹可能收到1个,2个或者5个(结果不确定),因此使用
wait函数,只能清理一个子进程后就退出,--->正确地做法:使用waitpid

void handler(int signo){                                                                                                         
  printf("waitpid child process\n");                                                                                             
  wait(NULL);                                                                                                                              
}
*/
//方法3:正确的方法
void handler(int signo){                                                                                                         
  printf("waitpid child process\n");                                                                                             
  if(signo==SIGCHLD){                                                                                                            
    while(waitpid(-1,NULL,WNOHANG)!=-1);                                                                                         
  }                                                                                                                              
}
int main(){                                                                                                                      
  //signal(SIGCHLD,SIG_IGN);  //方法1
  signal(SIGCHLD,handler);  //方法2  方法3
  int listenfd;
  if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
    ERR_EXIT("socket");

  struct sockaddr_in svraddr;
  svraddr.sin_family=AF_INET;
  svraddr.sin_port=htons(8001);
  svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");
  
  int on=1;
  if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
    ERR_EXIT("setsockopt");
  
  if(bind(listenfd,(struct sockaddr*)&svraddr,sizeof(struct sockaddr))<0)
    ERR_EXIT("bind");

  if(listen(listenfd,SOMAXCONN)<0)
    ERR_EXIT("listen");
  printf("listen...\n");    
  
  struct sockaddr_in peeraddr;
  socklen_t peerlen;

  int conn;
  while(1){
    if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
      ERR_EXIT("accept");
    printf("cli connect success\n");

    pid_t pid=fork();
    if(pid==-1){
      ERR_EXIT("fork");
    }
    else if(pid>0){
      close(conn);
    }
    else if(pid==0){                                                                                                             
      while(1){                                                                                                                  
        char recvbuf[1024];                                                                                                      
        int ret=readline(conn,recvbuf,1024);                                                                                     
        if(ret<0)                                                                                                                
          ERR_EXIT("readline");                                                                                                  
        if(ret==0){                                                                                                              
          printf("peer close\n");  
          close(conn);                                                                                              
          break; //break后,执行exit(EXIT_SUCCESS),退出                                                                                                                
        }                                                                                                                        
        printf("recvline=%s\n",recvbuf);                                                                                         
                                                                                                                                 
        writen(conn,recvbuf,strlen(recvbuf));                                                                                    
                                                                                                                                 
        memset(recvbuf,0,sizeof(recvbuf));                                                                                       
      }                                                                                                                   
      exit(EXIT_SUCCESS);                                                                                                        
    }                                                                                                                            
  }                                                                                                                              
}                                                                                                                                

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_36750623/article/details/83212919