Chinaunix首页 | 论坛 | 博客
  • 博客访问: 563486
  • 博文数量: 104
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 1559
  • 用 户 组: 普通用户
  • 注册时间: 2014-08-21 00:58
个人简介

锻炼精神,首先要锻炼肉体

文章分类

全部博文(104)

文章存档

2018年(1)

2016年(1)

2015年(101)

2014年(1)

我的朋友

分类: C/C++

2015-03-27 18:00:46

send ,recv 函数使用来在 client/server 对等端发送和接收数据的

通过前面的学习我们已经知道:
当 Server 端从请求缓冲队列中获取一个连接请求并将其 accept(接收)
之后,server 的运行系统会为其分配一块用来执行请求操作的缓冲空间。
而这块缓冲空间是通过server端调用 accept 函数的返回值---- 一个新的套接字描述符来指向的。

当Client 端向 Server 端发送请求信息之后,connect 方法成功执行返回 0 的时候,
系统会为其接收来自 Server 端的返回信息也分配一块缓冲空间,而这块缓冲空间是通过
Client 端一开始通过 socket 方法返回的套接字描述符来唯一指定的。

这个地方我理解起来用了一些时间,原因是,我一直认为的是: client server 的缓冲空间是二者共享的,
所以他们的套接字描述符应该是相同的,在仔细思考全部流程之后,这种想法显然是错误的。

大体的思路是这样的,
1.首先知道,套接字描述符是用来标定系统为当前的进程划分的一块缓冲空间的,类似于文件描述符,不过二者有些区别;
2.其次应该知道的是,这块缓冲空间并不是一开始就被系统划分给进程的;
3. 对于 server 端而言,划分系统缓冲空间的时刻是: 当server 决定接收来自 client 的连接请求,
   即 accept 方法成功执行,返回一个 > 0 的整数(也就是新的套接字描述符),
  系统才会为其分配缓冲空间,自然, 这块缓冲空间是通过 accept 新的套接字描述符来指定的 ;
4. 对于 client 端而言,划分系统缓冲空间的时刻是: 当 client 端执行 connect 函数正确的时候,
   connect 函数正确执行,说明此 client 端的连接请求已经被 server 端接收,剩下的就需要系统为 client 划分缓冲空间,
  用来接收来自于 server 端的返回结果。 这个时候,系统才会为其分配缓冲空间,
  而该缓冲空间使用 client 一开始创建套接字的 socket 函数的返回值标定即可。


值得记下的一点便是,send 是将内存中的数据放入到缓冲区中,
而 recv 是将缓冲区中的数据提取到内存中,而内存便是我们使用各种变量和数据类型的地方。
send 参数便是普通变量参数; 而 recv 参数是 值-结果 参数, 值-结果参数常用作函数的返回值,
有的是双向的,有的是单向的
双向指的是:该参数传入的值会参与到函数的计算中,同时会将函数的运行结果赋给该参数作为函数的返回值。
单向指的是:该参数传入的数值是什么并不重要,重要的是该参数在被传入函数中,函数执行之后,该参数中的数值。

如果将函数理解为一口井,那么单纯的 send 参数便是向井中投入个苹果或是鸭梨这种实体类型。
而 值-结果 参数便是向井中投入一个装东西的空桶,桶中原本是什么并不重要,重要的是桶提上来之后,
里面装着的是什么,当然这个描述是针对单向而言的。
双向的话,就是桶里放个苹果,然后提上来一桶水。(请原谅我这土傻俗的比喻,谢谢)




send 发送数据函数 API 描述

#include
#include
 ssize_t send ( int s , const void *msg , size_t len , int flags ) ;

参数:
  1. s 套接字描述符
      如果是 client 端调用 send 方法的话,s 便是 connect 正确执行之后,首次调用 socket 函数返回的套接字描述符。
      如果是 server 端调用 send 方法的话, s 便是 accept 正确执行之后的返回的新的套接字描述符
  2. msg 是要从内存  ----> 放入缓冲区中的数据,通常都是将字符串指针强制转为 void *得到的
  3. len  是 msg 的长度
  4. flags 控制选项,对应一系列系统定义的宏,用来指定以何种方式来发送缓冲区的数据。
      常用的取值为 0 ,在特殊一点就是带外数据 (flags= MSG_OOB) 其中 OOB = out of band,
      带外数据一般用于紧急命令的传送,希望通信的对等端优先接收和处理的信息。
返回值:
   如果返回值为负数,说明将内存中的数据移动到缓冲区中操作失败。
   如果返回值为 >=0 的数值,说明移动成功,返回的数值便是将内存中的多少个字节
   移动到了缓冲区中。而且这种情况仅仅说明数据移动成功,并不能表明成功发送缓冲区中
   的数据到对等端 。
 

recv 接收数据函数 API 描述
   
#include
#include
ssize_t recv ( int s , void *buf , size_t len , int flags ) ;

参数:
    1. s 套接字描述符,如果在 client 端调用 recv 函数,则是 connect 成功执行之后的,首次调用 socket 函数的返回值、
                                 如果在 server 端调用 recv 函数,则是 accept 成功执行之后返回的新的套接字描述符
    2. buf : 指向内存用于存放来自缓冲空间的指针 ,也就是接收来自缓冲区的数据,并将该数据存放到有 buf 指针指向的空间中
    3. len : buf 指向的内存空间的大小
    4. flags : 控制选项,对应一系列宏变量,这些宏变量用来限制或是指定接收数据的类型                   
                   flags = 0 接收所有数据
                  flags = MSG_OBB 仅仅接收带外数据
                  flags = MSG_PEEK : 只查看数据而不读出数据
                  flags = MSG_WAITWALL : 为了减少从缓冲区频繁少量的读取数据到内存操作,
                                                           只在 buf 指向的空间中存满数据的核实后,才返回。
 

示例代码
client 端代码

点击(此处)折叠或打开

  1. //clientTest.cpp
  2. //g++ clientTest.cpp -o client

  3. #include <stdio.h>
  4. #include <string.h> // memset

  5. #include <sys/types.h> // AF_INET ,SOCK_STREAM
  6. #include <sys/socket.h> // socket, connect
  7. #include <netinet/in.h> // sockaddr_in
  8. #include <arpa/inet.h> // inet_aton
  9. #include <unistd.h> // close

  10. #define SERVER_PORT 1027
  11. #define SERVER_IP "10.0.2.15"


  12. int getSocketDone ()
  13. {
  14.   int ret = socket (AF_INET , SOCK_STREAM , 0 ) ;
  15.   if ( ret < 0 )
  16.      perror ("socket") ;
  17.   return ret ;
  18. }

  19. int getConnectDone ( int sock_fd )
  20. {
  21.    struct sockaddr_in serv_addr ;
  22.    int ret ;
  23.   
  24.   memset ( &serv_addr, 0 , sizeof(serv_addr) ) ;
  25.   serv_addr.sin_family = AF_INET ;
  26.   serv_addr.sin_port = htons (SERVER_PORT) ;
  27.   ret = inet_aton ( SERVER_IP , &serv_addr.sin_addr ) ;
  28.   
  29.   if ( ret < 0 )
  30.      perror ("inet_aton") ;
  31.   ret = connect ( sock_fd , (struct sockaddr*)(&serv_addr) ,
  32.         sizeof( struct sockaddr_in )) ;
  33.   if ( ret < 0 )
  34.      perror ("connect") ;

  35.   return ret ;
  36. }

  37. int setSendMessage ( int sock_fd , const char *msg , int length )
  38. {
  39.    int ret = send ( sock_fd , msg , length, 0 ) ;
  40.    
  41.    if ( ret < 0 )
  42.      perror ("send") ;

  43.    return ret ;
  44. }

  45. int getRecvMessage ( int sock_fd , char *msg )
  46. {
  47.    int ret = recv ( sock_fd , msg ,sizeof(msg) , 0 ) ;

  48.    if( ret < 0 )
  49.      perror ( "send" ) ;
  50.   
  51.   return ret ;
  52. }

  53. int main ( int c , char ** v )
  54. {
  55.    int sock_fd , ret , len;
  56.    char msg [1024] ;
  57.   
  58.    sock_fd = getSocketDone () ;
  59.   
  60.    if ( sock_fd > 0 )
  61.      printf ("success get socket descriptor : %d \n" , sock_fd) ;
  62.    else
  63.      goto error ;
  64.   
  65.   if ( !getConnectDone ( sock_fd ) )
  66.     printf ("success connect \n") ;
  67.   else
  68.     goto error ;
  69.   
  70.   printf("in put message send to server \n") ;
  71.   scanf ("%s" , msg) ;
  72.   len = strlen ( msg ) ;
  73.   
  74.   
  75.   if ( (ret = setSendMessage ( sock_fd , msg , len ) )> 0 )
  76.     printf("success send %d bytes message to server \n" , ret ) ;

  77.   else
  78.   {
  79.    perror ("send error ") ;
  80.     goto error ;
  81.   }
  82.   memset ( msg , 0 , sizeof ( msg ) ) ;
  83.   len = sizeof ( msg ) ;

  84.   printf ("waiting response from server \n") ;
  85.   while ( (ret = getRecvMessage ( sock_fd ,msg )) <= 0 );
  86.   

  87.   printf ("success receive %d bytes message from server \n %s \n",
  88.         ret , msg ) ;
  89.   goto success ;

  90.   error :
  91.      printf ("failed in main \n") ;
  92.      goto success ;
  93.  success:
  94.     return 0 ;
  95. }

// Server 端代码

点击(此处)折叠或打开

  1. //serverTest.cpp
  2. // g++ serverTest.cpp -o server

  3. #include <stdio.h>
  4. #include <string.h> // memset

  5. #include <sys/types.h> // AF_INET , SOCK_STREAM
  6. #include <sys/socket.h> // socket , bind , connect , accept
  7. #include <netinet/in.h> // sockaddr_in
  8. #include <unistd.h>

  9. #define SERVER_PORT 1027
  10. #define LISTEN_Q_lENGTH 10

  11. int getSocketDone ()
  12. {
  13.  return socket(AF_INET , SOCK_STREAM, 0 ) ;
  14. }

  15. int getBindDone (int sock_fd )
  16. {
  17.   struct sockaddr_in serv_addr ;
  18.   int ret ;
  19.  
  20.   memset ( &serv_addr , 0 , sizeof( struct sockaddr_in )) ;
  21.   serv_addr.sin_family = AF_INET ;
  22.   serv_addr.sin_port = htons (SERVER_PORT) ;
  23.   serv_addr.sin_addr.s_addr = htonl ( INADDR_ANY ) ;
  24.   
  25.   ret = bind ( sock_fd , (struct sockaddr*)(&serv_addr) ,
  26.             sizeof(struct sockaddr_in ) ) ;
  27.   return ret ;
  28. }

  29. int getListenDone ( int sock_fd )
  30. {
  31.   return listen (sock_fd , LISTEN_Q_lENGTH ) ;
  32. }

  33. int getAcceptDone ( int sock_fd_queue , struct sockaddr_in *client_addr ,
  34.              int *client_len )
  35. {
  36.   int sock_fd_conn;
  37.   
  38.   *client_len = sizeof (struct sockaddr_in ) ;
  39.   sock_fd_conn = accept ( sock_fd_queue , (struct sockaddr*)client_addr ,
  40.             (socklen_t*) client_len ) ;

  41.  return sock_fd_conn ;
  42. }

  43. int getRecvMessage ( int sock_fd , char *msg , int len )
  44. {
  45.    int ret = recv ( sock_fd , msg , len , 0 ) ;
  46.    return ret ;
  47. }

  48. int setSendMessage ( int sock_fd , const char *msg , int len )
  49. {
  50.    int ret = send ( sock_fd , msg , strlen(msg) , 0 ) ;
  51.   return ret ;
  52. }


  53. int main ( int c , char **v )
  54. {
  55.   int sockfd_queue , sockfd_conn , ret , client_len , msg_len ;
  56.   struct sockaddr_in client_addr ;
  57.   char msg[1024] ;
  58.   
  59.   sockfd_queue = getSocketDone () ;
  60.   if ( sockfd_queue > 0 )
  61.     printf ("socket success\n") ;
  62.   else
  63.   {
  64.     perror ("socket error") ;
  65.         goto error ;
  66.   }

  67.   if ( !getBindDone ( sockfd_queue ) )
  68.   {
  69.     printf ("bind success\n") ;
  70.   }
  71.   else
  72.   {
  73.     perror ( "bind error ") ;
  74.     goto error ;
  75.   }

  76.   if ( !getListenDone ( sockfd_queue ) )
  77.   {
  78.       printf ("listen success");
  79.   }
  80.   else
  81.   {
  82.       perror ("listen error") ;
  83.       goto error ;
  84.   }

  85.   if ( (sockfd_conn = getAcceptDone ( sockfd_queue , &client_addr , &client_len ) ) > 0 )
  86.   {
  87.     printf ("accept success new sockfd_conn :%d \n" , sockfd_conn) ;
  88.   }
  89.   else
  90.   {
  91.     perror ("accept error") ;
  92.     goto error ;
  93.   }
  94.  
  95.  if ( (ret = getRecvMessage(sockfd_conn , msg , sizeof(msg)) ) > 0)
  96.  {
  97.     printf ("recv success receive %d bytes \n" , ret) ;
  98.         printf ("message contents : %s\n" , msg ) ;
  99. }
  100.  
  101.  else
  102.  {
  103.     perror ("recv error ") ;
  104.         goto error ;
  105.  }

  106. // send response to client
  107. memset ( msg , 0 , sizeof(msg) ) ;
  108. printf ("input response to client \n") ;
  109. scanf("%s" , msg) ;
  110.  
  111. if ( (ret = setSendMessage ( sockfd_conn , msg , strlen(msg))) > 0 )
  112. {
  113.    printf ("success send %d bytes message\n" , ret ) ;
  114.    goto success ;
  115. }

  116. else
  117. {
  118.   perror ("server send error ") ;
  119.   goto error ;
  120. }

  121.    error :
  122.     perror ("error exit ") ;
  123.         goto success ;
  124.   success :
  125.     return 0 ;
  126. }
运行顺序
首先运行 server(以阻塞的方式运行)

socket success
bind success
listen successaccept success new sockfd_conn :4 



然后打开一个新的 terminal 运行 client ,在client中输入想要发送给 server 端的信息
success get socket descriptor : 3 
success connect 
in put message send to server  
[request_from_client] (此为用户输入的信息)
success send 21 bytes message to server 
waiting response from server 

切换到 server 运行的 terminal 看到发送过来的 client,在server 运行的terminal 端输入想要回复 client 端的信息
recv success receive 21 bytes  
message contents : [request_from_client]
input response to client 
[response_from_server] (此为用户输入的信息)
success send 22 bytes message

然后切换到 client 运行的 terminal 看到来自 server 端的回复信息
success receive 8 bytes  message from server 
 [respons 
(是的,你没有看错,这个地方是因为没有对 client 端的 recv 方法进行处理,
  只要读到数据便返回,相应正确的处理应该是通过一个循环反复读取,直到将缓冲区中的数据全部读取到
  msg 对应的内存空间中的时候,才返回, 即通过 while 循环, 循环条件是 recv (...) >=0 
 一旦将全部输入读入, 便会有 recv(...) <0 , 具体的实现在后面的博文中在进行介绍 )
 这个地方 Unix 系统编程中的 “重启库” 中的源码有着详细的描述

client/server 端通信结束

阅读(6797) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~