socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)
socket的阻塞模式和非阻塞模式
無論是Windows還是Linux,默認創建socket都是阻塞模式的
在Linux中,可以再創建socket是直接將它設置為非阻塞模式
int socket (int __domain, int __type, int __protocol)將__type增加SOCK_NOBLOCK
不僅如此,在Linux上直接利用accept函數返回的代表與客戶端通信的socket也提供了一個拓展函數accept4,直接將accept4返回的socket設置為非阻塞的
send和recv函數在阻塞和非阻塞模式下的表現
send和recv函數并不是直接向網絡上發送數據和接收數據
send函數是將應用層發送緩沖區的數據拷貝到內核緩沖區中
recv函數是將內核緩沖區的數據拷貝到應用緩沖區
可以用下面這張圖來描述:
通過上圖我們可以知道,不同的程序進行網絡通信時,發送的一方會將內核緩沖區的數據通過網絡傳輸給接收方的內核緩沖區。
在應用程序A與應用程序B建立TCP連接后,假設A不斷調用send函數,會將數據不斷拷貝到對應的內核緩沖區,如果應用程序不調用recv函數,那么在應用程序B的內核緩沖區被填滿后,A的緩沖區也隨后被填滿,此時如果A繼續調用send函數會有什么后果呢?
- 當socket處于阻塞模式時,繼續調用send/recv函數,程序會阻塞在send/recv調用處
- 當socket處于非阻塞模式時,繼續調用send/recv函數,會返回錯誤碼
socket阻塞模式下send函數的表現
代碼來自《C++服務器開發精髓》
服務端代碼:
#include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <string.h>int main(int argc, char* argv[]) {//1.創建一個偵聽socketint listenfd = socket(AF_INET, SOCK_STREAM, 0);if (listenfd == -1){std::cout << "create listen socket error." << std::endl;return -1;}//2.初始化服務器地址struct sockaddr_in bindaddr;bindaddr.sin_family = AF_INET;bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);bindaddr.sin_port = htons(3000);if (bind(listenfd, (struct sockaddr *)&bindaddr, sizeof(bindaddr)) == -1){std::cout << "bind listen socket error." << std::endl;close(listenfd);return -1;}//3.啟動偵聽if (listen(listenfd, SOMAXCONN) == -1){std::cout << "listen error." << std::endl;close(listenfd);return -1;}while (true){struct sockaddr_in clientaddr;socklen_t clientaddrlen = sizeof(clientaddr);//4. 接受客戶端連接int clientfd = accept(listenfd, (struct sockaddr *)&clientaddr, &clientaddrlen);if (clientfd != -1){ //只接受連接,不調用recv收取任何數據std:: cout << "accept a client connection." << std::endl;}}//7.關閉偵聽socketclose(listenfd);return 0; }客戶端代碼:
/*** 驗證阻塞模式下send函數的行為,client端* zhangyl 2018.12.17*/ #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <unistd.h> #include <iostream> #include <string.h>#define SERVER_ADDRESS "127.0.0.1" #define SERVER_PORT 3000 #define SEND_DATA "helloworld"int main(int argc, char* argv[]) {//1.創建一個socketint clientfd = socket(AF_INET, SOCK_STREAM, 0);if (clientfd == -1){std::cout << "create client socket error." << std::endl;return -1;}//2.連接服務器struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS);serveraddr.sin_port = htons(SERVER_PORT);if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1){std::cout << "connect socket error." << std::endl;close(clientfd);return -1;}//3. 不斷向服務器發送數據,或者出錯退出int count = 0;while (true){int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);if (ret != strlen(SEND_DATA)){std::cout << "send data error." << std::endl;break;} else{count ++;std::cout << "send data successfully, count = " << count << std::endl;}}//5. 關閉socketclose(clientfd);return 0; }先啟動server在啟動client,客戶端會不斷向服務端發送helloworld,每次發送成功后會打印計數器,運行一頓時間后,停止打印,計數器不再增加
當程序不再有輸出,說明阻塞在某個函數gdb看一看
(gdb) bt #0 0x00007ffff7d03690 in __libc_send (fd=3, buf=0x555555556045, len=10, flags=0) at ../sysdeps/unix/sysv/linux/send.c:28 #1 0x00005555555553bb in main (argc=1, argv=0x7fffffffdf28) at client.cpp:42 (gdb)果然是send函數
上面這個例子證明了如果一端一直發送數據,另一端不接收數據,內核緩沖區很快就會被填滿,發生阻塞. 其實這里所說的內核緩沖區就是TCP窗口
我們現在利用tcpdump工具查看一下這種情況下TCP窗口的大小
22:01:57.543364 IP 127.0.0.1.53382 > 127.0.0.1.3000: Flags [S], seq 1832090129, win 65495, options [mss 65495,sackOK,TS val 451488646 ecr 0,nop,wscale 7], length 0 22:01:57.543379 IP 127.0.0.1.3000 > 127.0.0.1.53382: Flags [S.], seq 1797517498, ack 1832090130, win 65483, options [mss 65495,sackOK,TS val 451488646 ecr 451488646,nop,wscale 7], length 0 22:01:57.543386 IP 127.0.0.1.53382 > 127.0.0.1.3000: Flags [.], ack 1797517499, win 512, options [nop,nop,TS val 451488646 ecr 451488646], length 0 ... 22:02:11.342670 IP 127.0.0.1.3000 > 127.0.0.1.53382: Flags [.], ack 1832177322, win 0, options [nop,nop,TS val 451502445 ecr 451488936], length 0win就是TCP窗口的大小可以看出,逐漸減小最后變為零
socket非阻塞模式下send函數的表現
就是返回一個錯誤碼,不阻塞了,略…
socket阻塞模式下recv函數的表現
阻塞了就…
socket非阻塞模式下recv函數的表現
recv在沒有數據可讀的情況下,會立即返回,返回值為-1
非阻塞模式下send和recv函數返回值總結
| 大于0 | 成功發送或接受n字節 |
| 等于零 | 對方關閉連接 |
| 小于零 | 出錯,被信號中斷,TCP窗口太小導致數據發送不出去,或者當前網卡緩沖區已經無數據可以接受 |
詳細介紹:
返回值大于0
當send和recv函數返回值大于0時,表示發送或者接收多少字節.需要注意的是,在這種情況下,**判斷send返回值是否等于要發送的字節數,而不是簡單地判斷返回值是否大于零
int n = send(socketfd,buf,buf_length,0);if (n>0){printf("send successfully");}很多新手就會寫出以上的代碼(比如我…)雖然返回值大于零,但由于對端TCP窗口已滿,搜易我們所期望發送的字節,并沒有全部被對方接收,所以n的大小在區間(0,buf_length)內
解決辦法:
- 在返回值等于buf_length時才認為正確
- 在一個循環中調用send函數,如果一次性發送不完,記錄偏移量,接著從偏移量處發送
返回值等于0
- 對端關閉了連接
- 特殊情況:send函數主動發送了0字節
小于零
出錯啦唄
TCP網絡編程的基本流程
Linux與C++11多線程編程(學習筆記)
Linux select函數用法和原理
socket的阻塞模式和非阻塞模式(send和recv函數在阻塞和非阻塞模式下的表現)
connect函數在阻塞和非阻塞模式下的行為
獲取socket對應的接收緩沖區中的可讀數據量
總結
以上是生活随笔為你收集整理的socket的阻塞模式和非阻塞模式(send和recv函数在阻塞和非阻塞模式下的表现)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java 8 in Action】St
- 下一篇: 程序员幽默:一整天都在修复 bug 是啥