1. 程式人生 > >send和recv只是核心緩衝區和應用程式緩衝區之間的搬運工---嚴格來講send和recv並不具備傳送和接收功能

send和recv只是核心緩衝區和應用程式緩衝區之間的搬運工---嚴格來講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的程式為:

  1. #include <stdio.h>
  2. #include <winsock2.h> // winsock介面
  3. #pragma comment(lib, "ws2_32.lib") // winsock實現
  4. int main()
  5. {
  6. WORD wVersionRequested; // 雙位元組,winsock庫的版本
  7. WSADATA wsaData; // winsock庫版本的相關資訊
  8. wVersionRequested = MAKEWORD(1, 1); // 0x0101 即:257
  9. // 載入winsock庫並確定winsock版本,系統會把資料填入wsaData中
  10. WSAStartup( wVersionRequested, &wsaData );
  11. // AF_INET 表示採用TCP/IP協議族
  12. // SOCK_STREAM 表示採用TCP協議
  13. // 0是通常的預設情況
  14. unsigned int sockSrv = socket(AF_INET, SOCK_STREAM, 0);
  15. SOCKADDR_IN addrSrv;
  16. addrSrv.sin_family = AF_INET; // TCP/IP協議族
  17. addrSrv.sin_addr.S_un.S_addr = inet_addr("0.0.0.0"); // socket對應的IP地址
  18. addrSrv.sin_port = htons(8888); // socket對應的埠
  19. // 將socket繫結到某個IP和埠(IP標識主機,埠標識通訊程序)
  20. bind(sockSrv,(SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
  21. // 將socket設定為監聽模式,5表示等待連線佇列的最大長度
  22. listen(sockSrv, 5);
  23. // sockSrv為監聽狀態下的socket
  24. // &addrClient是緩衝區地址,儲存了客戶端的IP和埠等資訊
  25. // len是包含地址資訊的長度
  26. // 如果客戶端沒有啟動,那麼程式一直停留在該函式處
  27. SOCKADDR_IN addrClient;
  28. int len = sizeof(SOCKADDR);
  29. unsigned int sockConn = accept(sockSrv,(SOCKADDR*)&addrClient, &len);
  30. while(1)
  31. {
  32. getchar(); // 阻塞一下
  33. send(sockConn, "tcp", strlen("tcp") + 1, 0); // send來啦
  34. }
  35. closesocket(sockConn);
  36. closesocket(sockSrv);
  37. WSACleanup();
  38. return 0;
  39. }
      先啟動服務端B.

      下面我們來看客戶端A:

  1. #include <winsock2.h>
  2. #include <stdio.h>
  3. #pragma comment(lib, "ws2_32.lib")
  4. int main()
  5. {
  6. WORD wVersionRequested;
  7. WSADATA wsaData;
  8. wVersionRequested = MAKEWORD(1, 1);
  9. WSAStartup( wVersionRequested, &wsaData );
  10. SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
  11. SOCKADDR_IN addrSrv;
  12. addrSrv.sin_addr.S_un.S_addr = inet_addr("192.168.1.101");
  13. addrSrv.sin_family = AF_INET;
  14. addrSrv.sin_port = htons(8888);
  15. connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
  16. while(1)
  17. {
  18. getchar();
  19. send(sockClient, "cpp", strlen("cpp") + 1, 0);
  20. }
  21. closesocket(sockClient);
  22. WSACleanup();
  23. return 0;
  24. }
      好, 再啟動客戶端A.

      我們在客戶端A上安裝wireshark並啟動抓包, 實驗發現: 

     1. 當A向B傳送資料時, A上的wireshark可以抓到對應的包, 因為資料經過了A的網絡卡。(不管B是否有去recv)

     2. 當B向A傳送資料時, A上的wireshark也可以抓到對應的包,    因為資料到達了A的核心緩衝區, 也經歷了A的網絡卡。(不管A是否有去recv)

      相信通過本文的討論, 又加深了對send和recv的認識。 未來, 路漫漫, 我們慢慢來。