TCP/IP流协议(处理粘包):readline—包尾\n(recv/send)


一.为什么使用recv实现readline函数

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

二.recv函数介绍

ssize_t recv(int socket, void *buffer, size_t length, int flags);
返回值
	 >  0  成功接收数据大小
	 =  0  另外一端关闭了套接字
	 = -1  错误,需要获取错误码errno
参数:
flags
	MSG_OOB   带外数据  紧急指针
	MSG_PEEK  偷窥缓冲区中的数据(预先读缓冲区,不将数据从缓冲区中读走)
注意:recv只能用于socket流;read可以用于socket流和文件

三. 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);
      exit(EXIT_SUCCESS);     
    }
    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);   
          exit(EXIT_SUCCESS);          
        }   
        printf("recvline=%s\n",recvbuf);
 
        writen(conn,recvbuf,strlen(recvbuf));      
 
        memset(recvbuf,0,sizeof(recvbuf));         
      }     
    }       
  }         
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值