recv和send

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wk_bjut_edu_cn/article/details/82319051

函数原型 

#include <sys/socket.h>
ssize_t recv(int sckfd,void *buff,size_t nbytes,int flags);
ssize_t send(int sckfd,const void *buff,size_t nbytes,int flags);

返回值

若成功则为读入或写出的字节数,若出错则为-1。

参数说明

recv和send的前三个参数等同于read和write的3个参数。flags参数值或为0,或者为常量值的逻辑或,最常用的是:

flags 说明 recv send
MSG_OOB 发送或接收带外数据
MSG_PPEK 窥看外来消息

recv和send只能用于套接口IO,不能用于文件IO以及其它的IO,而read、write可以用于任意的IO

利用recv实现readline的功能,readline函数能够实现按行读取,直到遇到\n为止,说明这算是一条消息。这也就能够解决粘包问题

服务端

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

#include <stdlib.h>
#include <stdio.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)
  {
          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;
  
                  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)
            {
                    if((nwritten=write(fd,bufp,nleft))<0)
                    {
                            if(errno==EINTR)
                                    continue;
                            return -1;
                    }
                    else if(nwritten==0)
                            continue;
  
                    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)
			continue;
		return ret;
	}
}

//去看客户端的讲解
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
	int ret;
	int nread;
	char *bufp=buf;
	int nleft=maxline;
	while(1)
	{
		ret=recv_peek(sockfd,bufp,nleft);
		if(ret<0)
			return ret;
		else if(ret==0)
			return ret;
		
		nread=ret;
		int i;
		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)
			exit(EXIT_FAILURE);

		nleft-=nread;
		ret=readn(sockfd,bufp,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));
                 int ret=readline(conn,&recvbuf,1024);
		 if(ret==-1)
		 	ERR_EXIT("readline");
		 if(ret==0)
		 {
 		 	printf("client close\n");
			break;
		 }
	
                 fputs(recvbuf,stdout);
                 writen(conn,recvbuf,strlen(recvbuf));
	 }

}
int main(void)
{
	int listenfd;
	if((listenfd=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
		/*if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) */
		ERR_EXIT("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);
	/*servaddr.sin_addr.s_addr=inet_addr("127.0.0.1");*/
	/*inet_aton("127.0.0.1,&servaddr.sin_addr");*/
	
	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);
	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);
		}
		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 <stdlib.h>
#include <stdio.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)
{
	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;

		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)
	{
		if((nwritten=write(fd,bufp,nleft))<0)
		{
			if(errno==EINTR)
				continue;
			return -1;
		}
		else if(nwritten==0)
			continue;

		bufp+=nwritten;
		nleft-=nwritten;
	}
	return count;
}

//这个函数能够从套接口接收数据,通过MSG_PEEK指定并不将数据从缓冲移除
//并不说一定要接收len个字符,只要偷看到数据就返回,没有数据就阻塞
ssize_t recv_peek(int sockfd,void *buf,size_t len)
{
	while(1)
	{
		//它仅仅只是从套接口缓冲区当中接收数据到buf当中,
        //并没有将数据从套接口缓冲区移除,而read函数在接收的同时将数据移除了
		int ret=recv(sockfd,buf,len,MSG_PEEK);
		if(ret==-1 && errno==EINTR)
			continue;
		return ret;
	}
}

/*
   实现的是按行读取,也就是读取知道遇到\n为止,这算是一条消息
   这种方法也可以解决粘包问题,通过\n表明了消息与消息之间的边界
 */
//只能用于套接口,因为是用recv来实现的
//maxline:一行最大的字节数,读取的时候不是必须读maxline,
//遇到\n就停止了,maxline只是最大限度
ssize_t readline(int sockfd,void *buf,size_t maxline)
{
	int ret;
	//接收到的字节数
	int nread;
	char *bufp=buf;
	int nleft=maxline;
	while(1)
	{
		ret=recv_peek(sockfd,bufp,nleft);
		//表示失败
		if(ret<0)
			return ret;
		//表示对方关闭套接口
		else if(ret==0)
			return ret;

		nread=ret;
		int i;
		for(i=0;i<nread;i++)
		{
			if(bufp[i]=='\n')
			{ 
				//刚刚的recv_peek只是偷窥了一下缓冲区的数据,并没有将其移走,
                //可以使用readn方法将数据从缓冲区移除,读走这些数据
				ret=readn(sockfd,bufp,i+1);
				if(ret!=i+1)
					exit(EXIT_FAILURE);
				return ret;
			}
		}
		//下面这些是没有遇到\n时的处理,读走偷窥出来的部分,然后继续偷窥
		if(nread>nleft)
			exit(EXIT_FAILURE);

		nleft-=nread;
		//nread里面还没有遇到换行符,也把它从套接口缓冲区移除
		ret=readn(sockfd,bufp,nread);
		if(ret!=nread)
			exit(EXIT_FAILURE);

		//下一次继续偷窥,直到遇到\n为止
		bufp+=nread;
	}
	//程序运行到这个位置就是出错了
	return -1; 
}

int main(void)
{
	int sock;
	if((sock=socket(PF_INET,SOCK_STREAM,IPPROTO_TCP))<0)
		/*if((listenfd=socket(PF_INET,SOCK_STREAM,0))<0) */
		ERR_EXIT("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=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");

	//本地的地址
	struct sockaddr_in localaddr;
	socklen_t addrlen=sizeof(localaddr);
	//getsockname()获得本地地址
	if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen)<0)
		ERR_EXIT("getsockname");
	printf("ip=%s port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));

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

猜你喜欢

转载自blog.csdn.net/wk_bjut_edu_cn/article/details/82319051