龙空技术网

socket的阻塞模式和非阻塞模式下send函数和recv函数的表现

tang05709 511

前言:

而今同学们对“netstatsendq阻塞”可能比较关心,朋友们都需要分析一些“netstatsendq阻塞”的相关文章。那么小编同时在网上搜集了一些关于“netstatsendq阻塞””的相关资讯,希望我们能喜欢,咱们快快来了解一下吧!

阻塞模式指当某个函数执行成功的条件当前不满足时,该函数会阻塞当前执行线程,程序执行流在超时时间到达或执行成功的条件满足后恢复继续执行。

非阻塞模式则恰恰相反,即使某个函数执行成功的条件当前不能满足,该函数也不会阻塞当前执行线程,而是立即返回,继续执行程序流。

无论Windows或是Linux, 默认创建的socket都是阻塞模式的。

在Linux上,可使用fcntl函数或ioctl函数给创建的socket增加O_NONBLOCK标志来将socket设置为非阻塞模式。

int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlat = oldSocketFlag | O_NONBLOCK;fcntl(clientfd, F_SETFL, newSocketFlat);

在Windows上,可使用ioctlsocket函数将socket设置为非阻塞模式。将cmd参数设置为FIONBIO,将argp设置为非0。

u_long argp = 1;ioctlsocket(s, FIONBIO, &argp);

send函数在本质上并不是向网络上发送数据,而是将应用层发送缓冲区的数据拷贝到内核缓冲区中,至于数据什么时候会从网卡缓冲区中真的发送当网络中,要根据TCP/IP协议栈的行为来确定。如果socket设置了TCP_NODELAY选项,存放到内核缓冲区的数据就会被立即发出去。反之,一次放入内核缓冲区的数据包如果太小,则系统会在多个小的数据包凑成一个足够大的数据包后才会将数据发送出去。

recv函数在本质上并不是从网络上收取数据,而是将内核缓冲区中的数据拷贝到应用程序的缓冲区中。在拷贝后会将内核缓冲区的该部分数据移除。

当socket是阻塞模式时,继续调用send/recv函数,程序会阻塞在send/recv调用处。

当socket是非阻塞模式时,继续调用send/recv函数,send/recv函数不会阻塞程序执行流,而是立即出错并返回一个错误码,在Linux上该错误码是EDOULDBLOCK或EAGAIN,在Windows上该错误码为WSAEWOULDBLOCK。

非阻塞模式下send和recv函数返回值:

大于0: 表示发送或接收多少字节,在这种情形下,一定要判断send函数的返回值是不是期望的字节数,而不是简单的判断是否大于0。由于对端TCP可能因为缺少一部分字节就满了,所以n的值可能为(0, buf_length)。 当0 < n < buf_length时,虽然此时send函数调用成功,但有部分数据并没有被发送出去。所以要么在返回值n等于buf_length时才认为正确。要么在一个循环中调用send函数,如果数据一次性发送不完,则记录偏移量,下一次从偏移量处接着发送直到全部发送完毕。

bool sendData(const char* buf, int buf_length){    int send_bytes = 0;    int ret = 0;    while (true)    {        ret = send(hSocket, buf + send_bytes, buf_length - send_bytes, 0);           if (ret == -1)        {            if (errno == EWOULDBLOCK)            {                // 如果发送不出去,则应该缓存尚未发送出去的数据                break;                    }                else if (errno == EINTR)            {                continue;                    }            else             {                return false;                    }        }         else if (ret == 0)        {            return false;            }        send_bytes += ret;        if (send_bytes == buf_length)        {            break;            }     }    return true;}

等于0: 认为对端关闭了连接。特别注意,send函数主动发送0字节时也会返回0。recv函数只有在对端关闭了连接时才会返回0,对端发送0字节,本端的recv函数是不会收到0字节的数据的。

小于0: 表示调用send或recv函数出错。

服务端:blocking_server.cpp

#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h>#include <vector> int main(){    // create socket    int listenfd = socket(AF_INET, SOCK_STREAM, 0);    if (listenfd == -1)    {        std::cout << "create listen socket error." << std::endl;        return -1;    }    // init server address    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;    }    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);        int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);        if (clientfd != -1)        {             std::cout << "accept a client connection, fd:" << clientfd << std::endl;            break;        }    }    std::cout << "end" << std::endl;    close(listenfd);    return 0;}

send函数非阻塞客户端: blocking_client.cpp

#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 "hello world"int main(){    // 1. create socket    int clientfd = socket(AF_INET, SOCK_STREAM, 0);    if (-1 == clientfd)    {        std::cout << "create client socket error." << std::endl;        return -1;    }    // 2. client server    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. send message until error    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;        }     }    // 4. close socket    close(clientfd);    return 0;}

send函数阻塞客户端: nonblocking_client.cpp

#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h>#include <fcntl.h>#include <error.h>#define SERVER_ADDRESS "127.0.0.1"#define SERVER_PORT 3000#define SEND_DATA "hello world"int main(){    // 1. create socket    int clientfd = socket(AF_INET, SOCK_STREAM, 0);    if (-1 == clientfd)    {        std::cout << "create client socket error." << std::endl;        return -1;    }    // 2. client server    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;    }    // 设置为非阻塞模式    int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);    int newSocketFlat = oldSocketFlag | O_NONBLOCK;    if (fcntl(clientfd, F_SETFL, newSocketFlat) == -1)    {        std::cout << "set socket to nonblock error." << std::endl;        close(clientfd);        return -1;    }    // 3. send message until error    int count = 0;    while (true)    {        int ret = send(clientfd, SEND_DATA, strlen(SEND_DATA), 0);        if (ret == -1)        {            if (errno == EWOULDBLOCK)            {                std::cout << "send data error as tcp window size is too small" << std::endl;                continue;            }             else if (errno == EINTR)             {                std::cout << "send data interrupted by signal" << std::endl;                continue;            }             else            {                std::cout << "send data error." << std::endl;                break;            }        }        else if (ret == 0)        {            std::cout << "send data error." << std::endl;            close(clientfd);            break;        }         else        {            count++;            std::cout << "send data successfully, count = " << count << std::endl;        }    }    // 4. close socket    close(clientfd);    return 0;}

recv函数阻塞客户端:blocking_client_recv.cpp

#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 "hello world"int main(){    // 1. create socket    int clientfd = socket(AF_INET, SOCK_STREAM, 0);    if (-1 == clientfd)    {        std::cout << "create client socket error." << std::endl;        return -1;    }    // 2. client server    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. send message until error    char recvbuf[32] = {0};    int ret = send(clientfd, recvbuf, 32, 0);    if (ret > 0)    {        std::cout << "send data successfully" << std::endl;    }     else    {        std::cout << "send data error." << std::endl;    }    // 4. close socket    close(clientfd);    return 0;}

recv函数非阻塞客户端: nonblocking_client_recv.cpp

#include <sys/types.h>#include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h>#include <fcntl.h>#include <error.h>#define SERVER_ADDRESS "127.0.0.1"#define SERVER_PORT 3000#define SEND_DATA "hello world"int main(){    // 1. create socket    int clientfd = socket(AF_INET, SOCK_STREAM, 0);    if (-1 == clientfd)    {        std::cout << "create client socket error." << std::endl;        return -1;    }    // 2. client server    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;    }    // 设置为非阻塞模式    int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);    int newSocketFlat = oldSocketFlag | O_NONBLOCK;    if (fcntl(clientfd, F_SETFL, newSocketFlat) == -1)    {        std::cout << "set socket to nonblock error." << std::endl;        close(clientfd);        return -1;    }    while (true)    {        char recvbuf[32] = {0};        int ret = send(clientfd, recvbuf, 32, 0);        if (ret == -1)        {            if (errno == EWOULDBLOCK)            {                std::cout << "there is no data available now" << std::endl;                continue;            }             else if (errno == EINTR)             {                std::cout << "send data interrupted by signal" << std::endl;                continue;            }             else            {                std::cout << "send data error." << std::endl;                break;            }        }        else if (ret == 0)        {            std::cout << "peer close the socket." << std::endl;            close(clientfd);            break;        }         else        {            std::cout << "send data successfully" << std::endl;        }    }    // 4. close socket    close(clientfd);    return 0;}

标签: #netstatsendq阻塞