一、1.粘包问提产生的原因:
(1)应用进程发的消息大于套接口的发送缓冲区大小,会导致大于的部分第二次发,一条消息被分成两份
(2) TCP最大段MSS限制,导致对消息分割
(3)链路层最大传输单元MTU,超过MTU会在IP层分组
( 4 )流量控制、拥塞控制、TCP延迟发送等
2.粘包问题的解决方案
本质是在应用层维护消息与消息边界
1.定长包 2.包尾加\r\n 3.包头长度+包尾长度 (包头长度固定,接收时可以算出包体长) 4.设计更复杂的应用层协议
3.粘包问题的解决的实现
(1)定长包
readn() 接收确切数目的读操作
writen() 发送确切数目的写操作
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已读字节数
char* bufp=(char*)buf;
while(nleft)
{
if((nread=read(fd,bufp,nleft)<0)
{
if(errno==EINTR)
{
contibue;
return -1;
}
}
else if(nread==0) //对等方放关闭
return count-nleft;
buf=+=nread; nleft-=nread;
}
return count;
}
ssize_t writen(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwriten;
char* bufp=(char*)buf;
while(nleft)
{
if(nwritten=write(fd,bufp,nleft)<0)
{
if(errno==EINTR)
{
contibue;
return -1;
}
}
else if(nwritten==0) //返回0表示什么都没干,继续写
continue;
bufp+=nwritten;nleft-=nwritten;
}
return count;
}
发送定长包的实现:
//服务器端server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
int main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd<0)
error_handling("socket");
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
/***************************************************************/
//这三句的作用是:当客户端关闭后,可以立即重启,而不需要经过一段时间的TIME_WAIT的等待
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
error_handling("socket");
/***************************************************************/
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
error_handling("bind");
if(listen(listenfd,SOMAXCONN)<0)
error_handling("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen=sizeof(peeraddr);
int conn;
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
error_handling("accept");
printf("IP=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));//打印对等方的IP地址和端口号
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret=readn(conn,recvbuf,sizeof(recvbuf));
if(ret==0)
{
printf("client close\n");
break;
}
else if(ret==-1)
{
error_handling("read");
}
fputs(recvbuf,stdout);
written(conn,recvbuf,ret);
}
close(conn);close(listenfd);
return 0;
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
int main()
{
int sock;
sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock<0)
error_handling("sock");
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
error_handling("connect");
char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
written(sock,sendbuf,sizeof(sendbuf));
readn(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;
}
这种方法的缺点是每次都要发1024个字节,网络负担大
(2).包头+包体长度
思想是:发送时,发包头加包体长度,对方分两次接收,第一次接收包头(包头存放后面的实际需要的数据的长度,这个长度可以用一个四字节的整形数表示),对方从包头读出数据长度n,并再接收n个字节的包体长度
//服务器端server.c
#include
#include
#include
#include
#include
#include
#include
#include
#include
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
int main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
if(listenfd<0)
error_handling("socket");
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
/***************************************************************/
//这三句的作用是:当客户端关闭后,可以立即重启,而不需要经过一段时间的TIME_WAIT的等待
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
error_handling("socket");
/***************************************************************/
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
error_handling("bind");
if(listen(listenfd,SOMAXCONN)<0)
error_handling("listen");
struct sockaddr_in peeraddr;
socklen_t peerlen=sizeof(peeraddr);
int conn;
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
error_handling("accept");
printf("IP=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));//打印对等方的IP地址和端口号
char recvbuf[1024];
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
int ret=readn(conn,recvbuf,sizeof(recvbuf));
if(ret==0)
{
printf("client close\n");
break;
}
else if(ret==-1)
{
error_handling("read");
}
fputs(recvbuf,stdout);
written(conn,recvbuf,ret);
}
close(conn);close(listenfd);
return 0;
}
//客户端
#include
#include
#include
#include
#include
#include
#include
#include
#include
struct packet
{
int len; //包头,存放包体的实际数据长度
char buf[1024]; //存放实际需发的数据
};
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
ssize_t readn(int fd,void* buf,size_t count)
{
size_t nleft=count; //剩余字节数
ssize_t nread; //已收字节数
char* bufp=(char*)buf; //char指针指向buf
while(nleft>0)
{
if((nread=read(fd,bufp,nleft))<0)
{
if(errno==EINTR) //读信号中断
{
continue;
return -1;
}
}
else if(nread==0) //对等方关闭
{
return count-nleft;
}
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t written(int fd,const void* buf,size_t count)
{
size_t nleft=count;
ssize_t nwritten;
char *bufp=(char*)buf;
while(nleft>0)
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)
{
continue;
return -1;
}
}
else if(nwritten==0)
continue;
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
int main()
{
int sock;
sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP);
if(sock<0)
error_handling("sock");
struct sockaddr_in servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family=AF_INET;
servaddr.sin_port=htons(5188);
servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
error_handling("connect");
struct packet sendbuf;
struct packet recvbuf;
memset(&sendbuf,0,sizeof(sendbuf));
memset(&recvbuf,0,sizeof(recvbuf));
int n;
while(fgets(sendbuf.buf,sizeof(sendbuf.buf),stdin)!=NULL)
{
n=strlen(sendbuf.buf);
sendbuf.len=htonl(n);
written(sock,&sendbuf,4+n);
int ret=readn(sock,&recvbuf.len,4);
if(ret==-1)
error_handling("readn");
else if(ret<4)
{
printf("client close\n");
break;}
n=ntohl(recvbuf.len);
ret=readn(sock,&recvbuf.buf,n);
if(ret==-1)
error_handling("readn");
else if(ret