socket之send和recv原理剖析

谈到网络socket编程, 我们不得不提两个基本也很重要的函数:send和recv.  对socket编程理解不深的童鞋容易产生这样一个错误的认识: send函数是用来发送数据, 而recv函数是用来接收数据的, 其实, 这种观点是稍微有点偏颇的, 掩盖了本质。

       下面, 我们看一幅图, 了解一下send和recv函数所处的位置(这幅图是我在网上找的, 不太清晰, 请凑合看一下):


         为了简便起见, 我们仅考虑单向的数据流, 即A(客户端)向B(服务端)发送数据。 在应用程序Program A中, 我们定义一个数组char szBuf[100] = "tcp"; 那么这个szBuf就是应用程序缓冲区(对应上图的Program A中的某块内存), send函数对应上面蓝色的Socket API, 内核缓冲区对应上面的黄色部分。 我们看到, send函数的作用是把应用程序缓冲区中的数据拷贝到内核缓冲区, 仅此而已。 内核缓冲区中的数据经过网卡, 经历网络传到B端的网卡(TCP协议), 然后进入B的内核缓冲区, 然后由recv函数剪切/复制到Program B的应用程序缓冲区。前面我们用过wireshark抓包, wireshark抓的正是流经网卡的数据。

        强调一下:

        1. 对于客户端A, 其发送的内核缓冲区和接收的内核缓冲区是不一样的, 互不干扰. 服务端B也同理。

        2. recv函数是剪切还是复制, 由最后一个参数决定, 我们在之前的博文已经讲述过了。

        

        下面, 我们不考虑recv函数, 仅仅玩转一下send(双向send), 并用wireshark抓包实验一下, 加深理解。 注意, Wireshark抓不了环回包, 所以, 需要在两台电脑上测试。

         服务端B的程序为:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <stdio.h>  
  2. #include <winsock2.h> // winsock接口  
  3. #pragma comment(lib, "ws2_32.lib") // winsock实现  
  4.   
  5. int main()  
  6. {  
  7.     WORD wVersionRequested;  // 双字节,winsock库的版本  
  8.     WSADATA wsaData;         // winsock库版本的相关信息  
  9.       
  10.     wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257  
  11.       
  12.   
  13.     // 加载winsock库并确定winsock版本,系统会把数据填入wsaData中  
  14.     WSAStartup( wVersionRequested, &wsaData );  
  15.       
  16.   
  17.     // AF_INET 表示采用TCP/IP协议族  
  18.     // SOCK_STREAM 表示采用TCP协议  
  19.     // 0是通常的默认情况  
  20.     unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0);  
  21.   
  22.     SOCKADDR_IN addrSrv;  
  23.   
  24.     addrSrv.sin_family = AF_INET; // TCP/IP协议族  
  25.     addrSrv.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // socket对应的IP地址  
  26.     addrSrv.sin_port = htons(8888); // socket对应的端口  
  27.   
  28.     // 将socket绑定到某个IP和端口(IP标识主机,端口标识通信进程)  
  29.     bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));  
  30.   
  31.     // 将socket设置为监听模式,5表示等待连接队列的最大长度  
  32.     listen(sockSrv, 5);  
  33.   
  34.   
  35.     // sockSrv为监听状态下的socket  
  36.     // &addrClient是缓冲区地址,保存了客户端的IP和端口等信息  
  37.     // len是包含地址信息的长度  
  38.     // 如果客户端没有启动,那么程序一直停留在该函数处  
  39.     SOCKADDR_IN addrClient;  
  40.     int len = sizeof(SOCKADDR);  
  41.     unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);  
  42.   
  43.     while(1)  
  44.     {  
  45.         getchar(); // 阻塞一下  
  46.         send(sockConn, "tcp", strlen("tcp") + 1, 0); // send来啦        
  47.     }  
  48.   
  49.     closesocket(sockConn);    
  50.     closesocket(sockSrv);  
  51.     WSACleanup();  
  52.       
  53.     return 0;  
  54. }  
      先启动服务端B.


      下面我们来看客户端A:

[cpp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. #include <winsock2.h>  
  2. #include <stdio.h>  
  3. #pragma comment(lib, "ws2_32.lib")  
  4.   
  5. int main()  
  6. {  
  7.     WORD wVersionRequested;  
  8.     WSADATA wsaData;  
  9.     wVersionRequested = MAKEWORD(1, 1);  
  10.       
  11.     WSAStartup( wVersionRequested, &wsaData );  
  12.   
  13.     SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);  
  14.   
  15.     SOCKADDR_IN addrSrv;  
  16.     addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.101");  
  17.     addrSrv.sin_family = AF_INET;  
  18.     addrSrv.sin_port = htons(8888);  
  19.     connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));  
  20.   
  21.     while(1)  
  22.     {  
  23.         getchar();  
  24.         send(sockClient, "cpp", strlen("cpp") + 1, 0);  
  25.     }  
  26.   
  27.     closesocket(sockClient);  
  28.     WSACleanup();  
  29.   
  30.     return 0;  
  31. }  
      好, 再启动客户端A.


      我们在客户端A上安装wireshark并启动抓包, 实验发现: 

     1. 当A向B发送数据时, A上的wireshark可以抓到对应的包, 因为数据经过了A的网卡。(不管B是否有去recv)

     2. 当B向A发送数据时, A上的wireshark也可以抓到对应的包,    因为数据到达了A的内核缓冲区, 也经历了A的网卡。(不管A是否有去recv)


      相信通过本文的讨论, 又加深了对send和recv的认识。 未来, 路漫漫, 我们慢慢来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值