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