5.TCP/IP流协议(处理粘包):自定义包packet(readn/writen)***

1.TCP/UDP特点
	TCP  无边界  字节流  
	UDP  有边界  消息/数据报  

2.TCP读取流式套接字会出现未知的错误:
	对方,一次读操作,不能保证完全把消息读完
	对方接收数据包的个数是不确定的

3.产生粘包问题的原因:
	1.SQ_SNDBUF套接字本身有缓冲区(发送缓冲区/接收缓冲区)
	2.TCP传送的端MSS大小限制
	3.链路层也有MTU大小限制,如果数据包>MTU,则要在IP层进行分片
	4.TCP流量控制和拥塞控制,可能导致粘包
	5.TCP延迟发送机制

4.粘包的解决方案:本质上是在应用层维护消息与消息的边界
	定长包
	包尾部加\r\n  (FTP)
	自定义报文:包头+包体

案例1:包头+包体长度

自定义报文:包头+包体
	发送报文时:前4个字节长度(转成网络字节序)+包体
	接收报文时:先读取前4个字节,求出包体长度,根据长度读取包体

readn

//readn功能:从fd中读取count个字节的数据,存放在buf中
思想:TCPIP是流协议,不能保证1次读操作,把数据全部读走,所以要用[循环]读count长度的数据
返回值returnValue:实际读取到的字节数。因此---->
	[1]returnValue==count
		正常情况下,要求读取count个字节,就应该返回count个字节
	[2]returnValue<count
		如果读取的长度ssize_t<count,则说明读到了一个结束符,说明对方的socket已关闭	
ssize_t readn(int fd,void* buf,size_t count)                                                                                     
{                                                                                                                                
  ssize_t nread; //每次循环read函数的返回值                                                                                                                 
  char* bufp=(char*)buf; //作为辅助指针变量:每次读到nread个数据后,指针都要后移nread位置                                                                       
  size_t nleft=count;  //nleft:剩余nleft个数据[未读]
                                                                                                                                 
  while(nleft>0) //当有数据可读时,进入循环                                                                                                                 
  {                                                                                                                              
    if((nread=read(fd,bufp,nleft))<0) //read是可中断睡眠                                                                                            
    {                                                                                                                            
      if(errno==EINTR) //在读的过程中,被其他信号中断                                                                                                         
        continue; //执行continue,返回去继续读                                                                                              
      return -1;                                                                                                               
    }                                                                                                                            
    else if(nread==0) //本次read返回值nread=0,说明对方socket已经关闭
      return count-nleft; //返回readn读取到的值                                                                                                        
    
    //读取到nread个数据,更新bufp和nleft                                                                                                                            
    bufp+=nread;                                                                                                                 
    nleft-=nread;                                                                                                                
  }                                                                                                                              
  return count;                                                                                                                  
}   
--------------------------------
使用readn              
	//1.先读取recvbuf.len
	int ret=readn(conn,&recvbuf.len,4);                                                                                      
	if(ret==-1)                                                                                                              
	  ERR_EXIT("readn");                                                                                                     
	if(ret<4){                                                                                                               
	  printf("peer close\n");       
	  close(conn);                                                                                         
	  break;                                                                                                                 
	}                                                                                                                        
	int n=ntohl(recvbuf.len); //先取出长度recvbuf.len,[使用前,net--->host]
	//2.在读取recvbuf.buf
	ret=readn(conn,&recvbuf.buf,n); //read读取n个字节的数据                            
	if(ret==-1)                                                                                                              
	  ERR_EXIT("readn");                                                                                                     
	if(ret<n){                                                                                                               
	  printf("peer close\n"); 
	  close(conn);                                                                                               
	  break;                                                                                                                 
	}          
	//3.打印recvbuf.buf                                                                                                              
	printf("recv=%s",recvbuf.buf);   //打印read的结果
	
	memset(&recvbuf,0,sizeof(recvbuf)); 

writen

//writen功能:将buf指向的内存读取count个字节的数据,写入到fd中
ssize_t writen(int fd,void* buf,size_t count){                                                                                   
  ssize_t nwrite;                                                                                                                
  char* bufp=(char*)buf;                                                                                                         
  size_t nleft=count;                                                                                                            
                                                                                                                                 
  while(nleft>0)                                                                                                                 
  {                                                                                                                              
    if((nwrite=write(fd,bufp,nleft))<0)                                                                                          
    {                                                                                                                            
      if(errno==EINTR)                                                                                                           
        continue;                                                                                                                
      return -1;                                                                                                                 
    }                                                                                                                            
    else if(nwrite==0) //当且仅当nleft==0时,nwrite才等于0--->执行continue,此时while(nleft>0)不成立,退出while循环
      continue;                                                                                                                  
                                                                                                                                 
    bufp+=nwrite;                                                                                                                
    nleft-=nwrite;                                                                                                               
  }                                                                                                                              
  return count;                                                                                                                  
}    
-----------------------------------
使用writen                                                                                                 
	给sendbuf.buf赋值
	求出sendbuf.len的长度:int n=strlen(sendbuf.buf);   
	给sendbuf.len赋值:sendbuf.len=htonl(n); // send:host-->net                                                                                           
                                                                
	writen(conn,&sendbuf,4+n);                                                                                               
	                                                                                                                        
	memset(&sendbuf,0,sizeof(sendbuf)); 

回射客户端服务器模型代码

程序功能:
1.服务器
	可以监控多个客户端的连接
	readn客户端发来的消息,并将接收到的消息writen给客户端
2.客户端
	writen给客户端消息
	readn客户端的消息
客户端代码

#define ERR_EXIT(m) \                                                                                                            
  do  \                                                                                                                          
  { \                                                                                                                            
    perror(m);  \                                                                                                                
    exit(EXIT_FAILURE);  \                                                                                                       
  }while(0);                                                                                                                     
                                                                                                                                 
struct packet{                                                                                                                   
  int len;                                                                                                                       
  char buf[1024];                                                                                                                
};          
ssize_t readn(int fd,void* buf,size_t count);        
ssize_t writen(int fd,void* buf,size_t count);                     

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));                        
                                                                                                                                 
  struct packet sendbuf;                                                                                                         
  struct packet recvbuf;                                                                                                         
  printf("send=");                                                                                                               
  while(fgets(sendbuf.buf,sizeof(sendbuf),stdin)!=NULL){                                                                         
    //1.send message                                                                                                             
    int n=strlen(sendbuf.buf);                                                                                                   
    sendbuf.len=htonl(n); // send:host-->net                                                                                     
    writen(sockfd,&sendbuf,4+n);                                                                                                 
                                                                                                                                 
    int ret=readn(sockfd,&recvbuf.len,4);                                                                                        
    if(ret==-1){                                                                                                                 
      ERR_EXIT("readn");                                                                                                         
    }
    else if(ret<4){
      printf("peer close\n");
      close(conn);
      break;
    }
    n=ntohl(recvbuf.len); // read:net-->host                                                                                     
    ret=readn(sockfd,recvbuf.buf,n);                                                                                             
    if(ret==-1)                                                                                                                  
      ERR_EXIT("readn");                                                                                                         
    if(ret<n){                                                                                                                   
      printf("peer close\n");     
      close(conn);                                                                                               
      break;                                                                                                                     
    }                                                                                                                            
    printf("    recv=%s",recvbuf.buf);//else if(ret==n)                                                                          
                                                                                                                                 
                                                                                                                                 
    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);                                                                                                                     
                                                                                                                                 
struct packet{                                                                                                                   
  int len;                                                                                                                       
  char buf[1024];                                                                                                                
};          
ssize_t readn(int fd,void* buf,size_t count);        
ssize_t writen(int fd,void* buf,size_t count);  

void handler(int signo){                                                                                                         
  if(signo==SIGCHLD)                                                                                                             
    while(waitpid(-1,NULL,WNOHANG)!=-1);                                                                                         
}                                                                                                                                
int main(){                                                                                                                      
  signal(SIGCHLD,SIG_IGN); 
                                                                                                                   
  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){                                                                                                                  
        struct packet recvbuf;                                                                                                   
        struct packet sendbuf;                                                                                                   
                                                                                                                                 
        //1.recv message                                                                                                         
        int ret=readn(conn,&recvbuf.len,4);                                                                                      
        if(ret==-1)                                                                                                              
          ERR_EXIT("readn");                                                                                                     
        if(ret<4){                                                                                                               
          printf("peer close\n");     
          close(conn);                                                                                           
          break;                                                                                                                 
        }                                                                                                                        
        int n=ntohl(recvbuf.len); // read:net-->host                                                                             
        ret=readn(conn,&recvbuf.buf,n);
        if(ret==-1)
          ERR_EXIT("readn");
        if(ret<n){                                                                                                               
          printf("peer close\n");        
          close(conn);                                                                                        
          break;                                                                                                                 
        }                                                                                                                        
        printf("recv=%s",recvbuf.buf);                                                                                           
                                                                                                                                 
        //2.send message                                                                                                         
        strcpy(sendbuf.buf,recvbuf.buf);                                                                                         
        sendbuf.len=recvbuf.len;                                                                                                 
        n=ntohl(sendbuf.len); // send:net-->host                                                                                 
        writen(conn,&sendbuf,4+n);                                                                                               
                                                                                                                                 
        memset(&recvbuf,0,sizeof(recvbuf));                                                                                      
        memset(&sendbuf,0,sizeof(sendbuf));                                                                                      
      }                                                                                                                          
    }                                                                                                                            
  }                                                                                                                              
}             

猜你喜欢

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