阅读背景:

socket编程之解决流协议的粘包问题(二)

来源:互联网 
常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行'\n'的比用'
常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行'\n'的比用'\0'的更常见,如HTTP协议。

可变长字段的协议用readn来读就很不方便了,为此我们实现一个类似于fgets的readline函数。

在readline函数中,我们先用recv_peek”偷窥“ 一下现在缓冲区有多少个字符。
然后查看是否存在换行符'\n',如果存在,则使用readn连通换行符一起读取。
如果不存在,则也先将前面的数据读取进bufp, 且移动bufp的位置,回到while循环开头。
再从当前bufp位置窥看,注意,当我们调用readn读取数据时,那部分缓冲区是会被清空的。
因为readn调用了read函数,还需注意一点是,如果第二次才读取到了'\n'。
则先用count保存了第一次读取的字符个数,然后返回的ret需加上原先的数据大小。

使用 readline函数也可以认为是解决粘包问题的一个办法,即以'\n'为结尾当作一条消息

代码:

#include<unistd.h>//
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
//ssize_t=int,size_t=unsigned int
//接收count个字节数
size_t nleft=count;//剩余字节数
ssize_t nread;//已经接收字节数
char *bufp=(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;//读到EOF,对方关闭
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd,void *buf,size_t count)
{
size_t nleft=count;//剩余发送字节数
ssize_t nwritten;//已经发送字节数
char *bufp=(char *)buf;
while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)//被中断
continue;
return -1;
}
else if(nwritten==0)//对等方关闭
continue;//读到EOF,对方关闭
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1){
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续执行。
continue;
return ret;//返回读取的字节数
}

}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
int ret;
int nread;
char *bufp=buf;
int nleft=maxline;//读取遇到\n返回,不会超过maxline
while(1){
ret=recv_peek(sockfd,bufp,nleft);
if(ret<=0)
return ret;
nread=ret;
int i;//接下来判断接收的缓冲区是否有\n
for(i=0;i<nread;i++){
if(bufp[i]=='\n'){
ret=readn(sockfd,bufp,i+1);
if(ret!=i+1)
exit(EXIT_FAILURE);//偷窥方法
return ret;
}
}
if(nread>nleft){
//偷窥到的数据不能大于maxline
exit(EXIT_FAILURE);
}
nleft-=nread;//剩余字节数
ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//继续偷窥
}
return 1;
}
void do_service(int conn)
{
char recvbuf[1024];
while(1){
memset(recvbuf,0,sizeof(recvbuf));//初始化recvbuf
int ret=readline(conn,recvbuf,1024);//一行一行接收数据。
if(ret==-1)
{
ERR_EXIT("readine");
}
if(ret==0)
{
printf("client close\n");
break;
}

fputs(recvbuf,stdout);//读取一行数据,就将其输出到标准输出
writen(conn,recvbuf,strlen(recvbuf));//buf中数据被复制到了TCP发送缓冲区
}
}
int main(void)
{
int listenfd;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
ERR_EXIT("socket");

//IPV4地址结构
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");*/
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
ERR_EXIT("setsockopt");

if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind");


if(listen(listenfd,SOMAXCONN)<0)
ERR_EXIT("listen");

struct sockaddr_in peeraddr;
socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t
int conn;//已连接套接字
pid_t pid;
while(1)
{
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept");

printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);//将子进程退出,要不它会fork()
}
else
close(conn);
}
return 0;
}


客户端:
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
//ssize_t=int,size_t=unsigned int
//接收count个字节数
size_t nleft=count;//剩余字节数
ssize_t nread;//已经接收字节数
char *bufp=(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;//读到EOF,对方关闭
bufp+=nread;
nleft-=nread;
}
return count;
}

ssize_t writen(int fd,void *buf,size_t count)
{
size_t nleft=count;//剩余发送字节数
ssize_t nwritten;//已经发送字节数
char *bufp=(char *)buf;
while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)//被中断
continue;
return -1;
}
else if(nwritten==0)//对等方关闭
continue;//读到EOF,对方关闭
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1){
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续
continue;
return ret;//返回读取的字节数
}

}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
int ret;
int nread;
char *bufp=buf;
int nleft=maxline;//读取遇到\n返回,不会超过maxline
while(1){
ret=recv_peek(sockfd,bufp,nleft);
if(ret<=0)
return ret;
nread=ret;
int i;//接下来判断接收的缓冲区是否有\n
for(i=0;i<nread;i++){
if(bufp[i]=='\n'){
ret=readn(sockfd,bufp,i+1);
if(ret!=i+1)
exit(EXIT_FAILURE);//偷窥方法
return ret;
}
}
if(nread>nleft){
//偷窥到的数据不能大于maxline
exit(EXIT_FAILURE);
}
nleft-=nread;//剩余字节数
ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//继续偷窥
}
return 1;
}

//发送定长包
int main()
{
int sock;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
ERR_EXIT("socket");

//IPV4地址结构
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");
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/


if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect");
//getsockname

char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){//从文件读取一行,送到缓冲区
writen(sock,&sendbuf,strlen(sendbuf));//将接收到的数据发送出去
int ret=readline(sock,recvbuf,sizeof(recvbuf));//函数从打开的文件,设备中读取数据
if(ret==-1)
{
ERR_EXIT("readline");
}
else if(ret==0)
{
printf("client_close\n");
break;
}

fputs(recvbuf,stdout);//发送数据到文件
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;


}

'的更常见,如HTTP协议。常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行'\n'的比用'
常见的应用层协议都是带有可变长字段的,字段之间的分隔符用换行'\n'的比用'\0'的更常见,如HTTP协议。

可变长字段的协议用readn来读就很不方便了,为此我们实现一个类似于fgets的readline函数。

在readline函数中,我们先用recv_peek”偷窥“ 一下现在缓冲区有多少个字符。
然后查看是否存在换行符'\n',如果存在,则使用readn连通换行符一起读取。
如果不存在,则也先将前面的数据读取进bufp, 且移动bufp的位置,回到while循环开头。
再从当前bufp位置窥看,注意,当我们调用readn读取数据时,那部分缓冲区是会被清空的。
因为readn调用了read函数,还需注意一点是,如果第二次才读取到了'\n'。
则先用count保存了第一次读取的字符个数,然后返回的ret需加上原先的数据大小。

使用 readline函数也可以认为是解决粘包问题的一个办法,即以'\n'为结尾当作一条消息

代码:

#include<unistd.h>//
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
//ssize_t=int,size_t=unsigned int
//接收count个字节数
size_t nleft=count;//剩余字节数
ssize_t nread;//已经接收字节数
char *bufp=(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;//读到EOF,对方关闭
bufp+=nread;
nleft-=nread;
}
return count;
}
ssize_t writen(int fd,void *buf,size_t count)
{
size_t nleft=count;//剩余发送字节数
ssize_t nwritten;//已经发送字节数
char *bufp=(char *)buf;
while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)//被中断
continue;
return -1;
}
else if(nwritten==0)//对等方关闭
continue;//读到EOF,对方关闭
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1){
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续执行。
continue;
return ret;//返回读取的字节数
}

}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
int ret;
int nread;
char *bufp=buf;
int nleft=maxline;//读取遇到\n返回,不会超过maxline
while(1){
ret=recv_peek(sockfd,bufp,nleft);
if(ret<=0)
return ret;
nread=ret;
int i;//接下来判断接收的缓冲区是否有\n
for(i=0;i<nread;i++){
if(bufp[i]=='\n'){
ret=readn(sockfd,bufp,i+1);
if(ret!=i+1)
exit(EXIT_FAILURE);//偷窥方法
return ret;
}
}
if(nread>nleft){
//偷窥到的数据不能大于maxline
exit(EXIT_FAILURE);
}
nleft-=nread;//剩余字节数
ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//继续偷窥
}
return 1;
}
void do_service(int conn)
{
char recvbuf[1024];
while(1){
memset(recvbuf,0,sizeof(recvbuf));//初始化recvbuf
int ret=readline(conn,recvbuf,1024);//一行一行接收数据。
if(ret==-1)
{
ERR_EXIT("readine");
}
if(ret==0)
{
printf("client close\n");
break;
}

fputs(recvbuf,stdout);//读取一行数据,就将其输出到标准输出
writen(conn,recvbuf,strlen(recvbuf));//buf中数据被复制到了TCP发送缓冲区
}
}
int main(void)
{
int listenfd;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((listenfd=socket(AF_INET,SOCK_STREAM,0))<0)
ERR_EXIT("socket");

//IPV4地址结构
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");*/
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/
servaddr.sin_addr.s_addr=htonl(INADDR_ANY);
int on=1;
if(setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on))<0)
ERR_EXIT("setsockopt");

if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind");


if(listen(listenfd,SOMAXCONN)<0)
ERR_EXIT("listen");

struct sockaddr_in peeraddr;
socklen_t peerlen =sizeof(peeraddr);//typedef int socklen_t
int conn;//已连接套接字
pid_t pid;
while(1)
{
if((conn=accept(listenfd,(struct sockaddr*)&peeraddr,&peerlen))<0)
ERR_EXIT("accept");

printf("ip=%s port=%d\n",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
if(pid==0)
{
close(listenfd);
do_service(conn);
exit(EXIT_SUCCESS);//将子进程退出,要不它会fork()
}
else
close(conn);
}
return 0;
}


客户端:
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
ssize_t readn(int fd,void *buf,size_t count)
{
//ssize_t=int,size_t=unsigned int
//接收count个字节数
size_t nleft=count;//剩余字节数
ssize_t nread;//已经接收字节数
char *bufp=(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;//读到EOF,对方关闭
bufp+=nread;
nleft-=nread;
}
return count;
}

ssize_t writen(int fd,void *buf,size_t count)
{
size_t nleft=count;//剩余发送字节数
ssize_t nwritten;//已经发送字节数
char *bufp=(char *)buf;
while(nleft>0)//一般而言,write缓冲区大于发送数据缓冲区,不阻塞
{
if((nwritten=write(fd,bufp,nleft))<0)
{
if(errno==EINTR)//被中断
continue;
return -1;
}
else if(nwritten==0)//对等方关闭
continue;//读到EOF,对方关闭
bufp+=nwritten;
nleft-=nwritten;
}
return count;
}
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
while(1){
int ret=recv(sockfd,buf,len,MSG_PEEK);
if(ret==-1&&errno==EINTR)//操作被信号中断,recv认为链接正常,继续
continue;
return ret;//返回读取的字节数
}

}
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
int ret;
int nread;
char *bufp=buf;
int nleft=maxline;//读取遇到\n返回,不会超过maxline
while(1){
ret=recv_peek(sockfd,bufp,nleft);
if(ret<=0)
return ret;
nread=ret;
int i;//接下来判断接收的缓冲区是否有\n
for(i=0;i<nread;i++){
if(bufp[i]=='\n'){
ret=readn(sockfd,bufp,i+1);
if(ret!=i+1)
exit(EXIT_FAILURE);//偷窥方法
return ret;
}
}
if(nread>nleft){
//偷窥到的数据不能大于maxline
exit(EXIT_FAILURE);
}
nleft-=nread;//剩余字节数
ret=readn(sockfd,bufp,nread);//将nread数据从缓冲区移除
if(ret!=nread)
exit(EXIT_FAILURE);
bufp+=nread;//继续偷窥
}
return 1;
}

//发送定长包
int main()
{
int sock;
/*if((listenfd=socket(AF_INET,SOCK_STEAM,IPPOTO_TCP))<0) */
if((sock=socket(AF_INET,SOCK_STREAM,0))<0)
ERR_EXIT("socket");

//IPV4地址结构
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");
/*inet_aton("127.0.0.1",&servaddr.sin_addr);*/


if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect");
//getsockname

char sendbuf[1024]={0};
char recvbuf[1024]={0};
while(fgets(sendbuf,sizeof(sendbuf),stdin) !=NULL){//从文件读取一行,送到缓冲区
writen(sock,&sendbuf,strlen(sendbuf));//将接收到的数据发送出去
int ret=readline(sock,recvbuf,sizeof(recvbuf));//函数从打开的文件,设备中读取数据
if(ret==-1)
{
ERR_EXIT("readline");
}
else if(ret==0)
{
printf("client_close\n");
break;
}

fputs(recvbuf,stdout);//发送数据到文件
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
return 0;


}

'的更常见,如HTT



你的当前访问异常,请进行认证后继续阅读剩余内容。

分享到: