Linux网络编程学习笔记(四)

一、read()、write()和recv()、send()

       1.recv()和read()区别

           功能相同,都能从套接口读数据到buf,但recv()只能用于套接口IO,read()可用于任何IO,读之后将数据从缓冲区请除,recv()多一个flag选项,指定接收行为:MSG_OOB     接收紧急数据                         MSG_PEEK接收缓冲区数据但不清除缓冲区,偷看缓冲区数据

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

//仅从套接口缓冲区拿数据放到buf,未将数据从套接口缓冲区清除,而read()会将数据清除
//因为可能因为异常返回-1,所以返回值定义成有符号数
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;
    else 
     return ret;
  }
}

     2.readline()的实现

      实现按行读取,知道遇到\n算一条消息,可以解决粘包问题,只要遇到\n表示前面是条合法消息,比如ftp协议 。

//因为readline()用recvpeek()封装的,所以readline()只能用于套接口
ssize_t readline(int sockfd,void* buf,size_t maxline)
{
    int ret;
    char* bufp=buf;   //因为这个缓冲区开头函数中一直在变,可以保留buf,创建一个buf的副本
     int nleft=maxline;
     int nread;

   while(1)
  {
    ret=recv_peek(sockfd,bufp,nleft);
    if(ret<0)
      return ret;      //此处不用再判断是否是信号中断,因为在recv()以判断

    else if(ret==0)
      return ret;//对方关闭套接口。这里与readn()不同,readn()只要返回的ret不等于指定字节数就说明对方关闭了套接口

     nread=ret;
     int i;
     for(i=0;i<nread;++i)
      {
        if(bufp[i]=='\n')               //偷窥到的字符串中含有行的终止符'\n'
         {
            ret=readn(sockfd,bufp,i+1);   //用readn()把\n和她前面的字符读走
            if(ret!=i+1)
               error_handling("readline");
           
           return ret;
        }
      
      }
    nleft-=nread;       //走到这里,是偷窥到的字符串中没有\n,那就将已经偷窥到的放进buf,然后接着上次偷窥到的位置继续读,找\n
    ret=readn(sockfd,bufp,nread);
    if(ret<0)
     error_handling("readline_readn");
   
    bufp+=nread;
      }
    return -1;
  } 

       3.用readline()实现客户、服务器解决粘包问题

//客户端


#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>


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

//仅从套接口缓冲区拿数据放到buf,未将数据从套接口缓冲区清除,而read()会将数据清除
//因为可能因为异常返回-1,所以返回值定义成有符号数
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;
    else 
     return ret;
  }
}

//因为readline()用recvpeek()封装的,所以readline()只能用于套接口
ssize_t readline(int sockfd,void* buf,size_t maxline)
{
    int ret;
    char* bufp=buf;   //因为这个缓冲区开头函数中一直在变,可以保留buf,创建一个buf的副本
     int nleft=maxline;
     int nread;

   while(1)
  {
    ret=recv_peek(sockfd,bufp,nleft);
    if(ret<0)
      return ret;      //此处不用再判断是否是信号中断,因为在recv()以判断

    else if(ret==0)
      return ret;//对方关闭套接口。这里与readn()不同,readn()只要返回的ret不等于指定字节数就说明对方关闭了套接口

     nread=ret;
     int i;
     for(i=0;i<nread;++i)
      {
        if(bufp[i]=='\n')               //偷窥到的字符串中含有行的终止符'\n'
         {
            ret=readn(sockfd,bufp,i+1);   //用readn()把\n和她前面的字符读走
            if(ret!=i+1)
               error_handling("readline");
           
           return ret;
        }
      
      }
    nleft-=nread;       //走到这里,是偷窥到的字符串中没有\n,那就将已经偷窥到的放进buf,然后接着上次偷窥到的位置继续读,找\n
    ret=readn(sockfd,bufp,nread);
    if(ret<0)
     error_handling("readline_readn");
   
    bufp+=nread;
      }
    return -1;
  } 
	

 

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 sockaddr_in localaddr;
          socklen_t addrlen=sizeof(localaddr);
          if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen)<0)
               error_handling("getsockname");
          printf("ip=%s port=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));
             
       // struct packet sendbuf;
      //  struct packet recvbuf;
        char sendbuf[1024]={0};
        char recvbuf[1024]={0};
       
	int n;
	
	while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
	{
                written(sock,&sendbuf,strlen(sendbuf));
                
                int ret=readline(sock,recvbuf,1024);
                if(ret==-1)
                  error_handling("readline");
                 
                 else if(ret==0)
                 {
                    printf("client close\n");
                  break;}
               
                 fputs(recvbuf,stdout);
                  memset(&sendbuf,0,sizeof(sendbuf));
                   memset(&sendbuf,0,sizeof(sendbuf));
          }
    close(sock);
    return 0;
          
}
		

      

//服务器端
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
 
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;
}

//仅从套接口缓冲区拿数据放到buf,未将数据从套接口缓冲区清除,而read()会将数据清除
//因为可能因为异常返回-1,所以返回值定义成有符号数
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;
    else 
     return ret;
  }
}


//因为readline()用recvpeek()封装的,所以readline()只能用于套接口
ssize_t readline(int sockfd,void* buf,size_t maxline)
{
    int ret;
    char* bufp=buf;   //因为这个缓冲区开头函数中一直在变,可以保留buf,创建一个buf的副本
     int nleft=maxline;
     int nread;

   while(1)
  {
    ret=recv_peek(sockfd,bufp,nleft);
    if(ret<0)
      return ret;      //此处不用再判断是否是信号中断,因为在recv()以判断

    else if(ret==0)
      return ret;//对方关闭套接口。这里与readn()不同,readn()只要返回的ret不等于指定字节数就说明对方关闭了套接口

     nread=ret;
     int i;
     for(i=0;i<nread;++i)
      {
        if(bufp[i]=='\n')               //偷窥到的字符串中含有行的终止符'\n'
         {
            ret=readn(sockfd,bufp,i+1);   //用readn()把\n和她前面的字符读走
            if(ret!=i+1)
               error_handling("readline");
           
           return ret;
        }
      
      }
    nleft-=nread;       //走到这里,是偷窥到的字符串中没有\n,那就将已经偷窥到的放进buf,然后接着上次偷窥到的位置继续读,找\n
    ret=readn(sockfd,bufp,nread);
    if(ret<0)
     error_handling("readline_readn");
   
    bufp+=nread;
      }
    return -1;
  } 
	

           
 
int main()
{
	int listenfd=socket(AF_INET,SOCK_STREAM,0);
	if(listenfd<0)
	error_handling("socket");
	
       // struct packet sendbuf;
        //struct packet recvbuf;
      //  memset(&sendbuf,0,sizeof(sendbuf));

	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=readline(conn,&recvbuf,1024);
            if(ret==-1)
             {
               error_handling("readn");
              }

              else if(ret==0)
              {
                printf("client close\n");
                break;
               }
                        
              fputs(recvbuf,stdout);
              written(conn,recvbuf,strlen(recvbuf));
	}
	
        close(conn);close(listenfd);
	return 0;
	}
 
	

二、getsockname()和getpeername()

       sock 连接本地与对等方若要获取套接口本地这端的地址用getsockname()

      若要获取对等方的getpeername()

   例如:

  再本问上面的例子代码,的客户端的connect()后添加

 struct sockaddr_in localaddr;
 socklen_t addrlen=sizeof(localaddr);
  if(getsockname(sock,(struct sockaddr*)&localaddr,&addrlen)<0)
          error_handling("getsockname");
        printf("ip=%sport=%d\n",inet_ntoa(localaddr.sin_addr),ntohs(localaddr.sin_port));

  客户端代码其他部分以及服务器端程序不变,运行,客户端显示的本地端口号与服务器端显示的客户端端口号相同

注意:(1)getsockname()和getpeername()都要在建立好的连接上

              (2)  客户端要在正确调用connect()后才能用,服务器端要在正确调用accept()后才能用

               (3)  (struct sockaddr*)&peeraddr和&peerlen两个参数要么都空指针,要么都不空

三、geyhostname()、gethostbyname()

       geyhostname()       获取主机名

        gethostbyname()     通过主机名获取主机上所有IP地址

      举例说明gethostname()用法

#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>

void error_handling(char* message)
{
	fputs(message,stderr);
	fputc('\n',stderr);
	exit(1);
}

int main()
{
  char host[100]={0};
  if(gethostname(host,sizeof(host))<0)
     return -1;
   printf("%s\n",host);
  return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_34793133/article/details/81660083
今日推荐