如何实时查看tcp的发送缓冲区和接收缓冲区堆积的大小


对于发送缓冲区和接收缓冲区的总的大小,我们可以使用套接字选项分别获得。

//获得发送缓冲区大小
socklen_t sendbuflen = 0;
socklen_t len = sizeof(sendbuflen);
getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);  

//获得接收缓冲区的大小
socklen_t recvbuflen = 0;
socklen_t len = sizeof(recvbuflen);
getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuflen, &len);
那么,有没有什么方法,可以让我们获得缓冲区中待处理的数据大小呢?对于发送缓冲区来说,是获得待发送的数据大小,而对于接收缓冲区来说,是获得待读取的数据大小。方法当然是有的。

    调用ioctl方法,设置适当的选项,就可以达到我们想要的目的。

//查看接收缓冲区中未读取数据的大小
 ioctl(fd, SIOCINQ, &bytes);

//查看发送缓冲区中未发送数据的大小
ioctl(fd, SIOCOUTQ, &bytes);

    可以看出,两个选项的区别就是IN和OUT的区别。那么,这两个选项分别表示什么意义呢?man tcp可以查看。我贴一下描述。

       SIOCINQ
              Returns  the  amount  of queued unread data in the receive buffer.  The socket must not be in LISTEN state, otherwise an
              error (EINVAL) is returned.
        返回套接字接收缓冲区中未读取的数据大小。描述符不能处于LISTEN状态,否则返回错误。
     SIOCOUTQ
              Returns the amount of unsent data in the socket send queue.  The socket must not be in LISTEN state, otherwise an  error
              (EINVAL) is returned.
            返回套接字发送缓冲区中未发送的数据大小。同理,描述符不能处于LISTEN状态,否则返回错误。

    根据这两个方法,我们可以写代码模拟快速发送,缓慢接收的这么一个过程,从而观察缓冲区的使用情况。

base.h

#ifndef _BASE_H
#define _BASE_H
#include <iostream>

class Socket
{
public:
    Socket();

    virtual int CreateSock(const char * ip, int port, int nlisten = 5) {}
    
    virtual int Accept() {}

    static int Recv(int fd, std::string & msg, unsigned len = 0);
    static int Send(int fd, const char * pbuff, int len);

    static int SmallToBigEndian(unsigned char *pData, unsigned int uDataLen);

    static int BigToSmallEndian(unsigned char *pData, unsigned int uDataLen);

    int GetSockfd() {return sockfd;}
protected:
    int sockfd;
};

class SocketServer: public Socket
{
public:
    SocketServer() {};

    virtual int CreateSock(const char * ip, int port, int nlisten = 5);

    virtual int Accept();
};

class SocketClient : public Socket
{
public:    
    SocketClient(){};

    virtual int CreateSock(const char * ip, int port, int nlisten = 5);
};

#endif
base.cpp
#include "base.h"
#include "common.h"
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h> 

using namespace std;


Socket::Socket():
    sockfd(-1)
{
    
}

int Socket::Recv(int fd, string & msg, unsigned len)
{
    char buff[MAX_BUFF] = {0};
    
    int count = 0;
    
    int readlen;
    unsigned maxlen = MAX_BUFF;
    //读取指定长度的数据
    if (len > 0)
    {
        readlen = min(len, maxlen);
    }
    else
    {
        readlen = MAX_BUFF;
    }

    int sumlen = 0;

    while(1)
    {
        memset(buff, 0, sizeof(buff));

        int ret= read(fd, buff, readlen);
       
        if (0 == ret)
        {
            //没有数据可读,
            return 1;
        }
        else if (ret > 0)
        {
            msg += string(buff, ret);

            sumlen += ret;

            //判断是否读完指定字节的数据
            if (len > 0 && sumlen >= len)
            {
                return 0;
            }
        }
        else
        {
            if (errno == EAGAIN)
            {
                return 0;
            }

            return 1;
        }
    }
}

int Socket::Send(int fd, const char * pbuff, int len)
{
    int pos = 0, ret = 0;
    
    while(len > 0)
    {
        ret = write(fd, pbuff + pos, len);

        if (-1 == ret)
        {
            return -1;
        }

        if (ret > 0)
        {
            len -= ret;
            pos += ret;
        }
        else if (0 == ret)
        {
            break;
        }
    }


    return 0;
}

int Socket::SmallToBigEndian(unsigned char *pData, unsigned int uDataLen)
{
    unsigned char *pStart = pData;
    unsigned char *pEnd   = pData + uDataLen - 1;
    unsigned char cTmp;
     
    while(pEnd > pStart)
    {
        cTmp    = *pStart;
        *pStart = *pEnd;
        *pEnd   = cTmp;
 
        ++pStart;
        --pEnd;
    }
     
    return 0;
}

int Socket::BigToSmallEndian(unsigned char *pData, unsigned int uDataLen)
{
    return SmallToBigEndian(pData, uDataLen);
}

int SocketServer::CreateSock(const char * ip, int port, int nlisten)
{
    //first-create socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);

    if (-1 == fd)
    {
        return -1;
    }

    //second- bind

    //bind need exactly server address,so we create it.
       //at first-change char ip to numeric
    struct sockaddr_in servaddr;
    if (strcmp("*", ip) == 0)
    {
        //在任意地址上进行监听
        servaddr.sin_addr.s_addr = INADDR_ANY;
    }
    else
    {
        inet_aton(ip, &servaddr.sin_addr);
    }

    servaddr.sin_port = htons(port);  //host to network short

    //支持端口复用
    int  optval=1;
    setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,(char*)&optval,sizeof(optval));

    int ret = bind(fd, (sockaddr *)&servaddr, sizeof(servaddr));

    if (-1 == ret)
    {
        return -1;
    }
    
    ret = listen(fd, nlisten);

    
    if (-1 == ret)
    {
        return -1;
    }

    sockfd = fd;

    //I think we should separate it from listen. because we can use epoll to produce this thing
    return 0;
}


int SocketServer::Accept()
{
    struct sockaddr_in cliaddr;
    socklen_t len = sizeof(cliaddr);

    return accept(sockfd, (sockaddr *)&cliaddr, &len);
}

int SocketClient::CreateSock(const char *ip, int port, int nlisten)
{
    //first-create socket
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    
    if (-1 == fd)
    {   
        cerr<<"create sockfd error"<<endl;
        return -1;
    }
    
    //second- bind
    
    //bind need exactly server address,so we create it.
       //at first-change char ip to numeric
    struct sockaddr_in servaddr;
    servaddr.sin_family = AF_INET;
    inet_aton(ip, &servaddr.sin_addr);
    servaddr.sin_port = htons(port);  //host to network short

    int ret = connect(fd, (sockaddr *)&servaddr, sizeof(servaddr));

    if (ret)
    {
        cerr<<"connect fail. reason:"<<strerror(errno)<<endl;
        return -1;
    }

    sockfd = fd;

    //设置socket异步
    fcntl(sockfd, F_SETFL, O_NONBLOCK);

    return fd;
}
接下来,就是负责发送数据的客户端

sock_cli.cpp

#include <iostream>
#include "base.h"
#include "cstring"
#include <sys/time.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstdio>
#include <sys/ioctl.h>
#include <linux/sockios.h>

using namespace std;

//设定,客户端发送数据,服务器接收数据,所有数据,间隔1s,依次发送,当对方接收窗口为0时,继续发送下一个数据
int cli_send()
{
    //创建socket
    Socket* sock = new SocketClient;
    
    const char* ip = "192.168.100.234";
    int port = 8888;

    int ret = sock->CreateSock(ip, port);

    if (ret == -1)
    {
        cerr<<"create socket error."<<endl;
        return -1;
    }

    //开始发送数据
    char msg[512] = {0};

    memset(msg, 'a', sizeof(msg)-1);

    //循环发送数据,获取发送缓冲区的大小
    int fd = sock->GetSockfd();
    //获取发送缓冲区大小是多少
    socklen_t sendbuflen = 0;
    socklen_t len = sizeof(sendbuflen);
    getsockopt(fd, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);    
    
    cout<<"原始的发送缓冲区长度是:"<<sendbuflen<<endl;
    int value = 0;

    while(true)
    {
        int ret = send(fd, msg, sizeof(msg), 0);

        if (ret == -1)
        {
            cout<<"发送失败,关闭套接字"<<endl;
            close(fd);
            return 1;
        }
        
        ioctl(fd, SIOCOUTQ, &value);       

        cout<<"未发送的缓冲区数据 length:"<<value<<endl;

        sleep(1);
    }
}

int main()
{
    cli_send();
}
负责接收数据的服务端。因为是测试性质,所以服务端只处理一个连接的客户端。

sock_ser.cpp

#include <iostream>
#include "base.h"
#include "cstring"
#include <sys/time.h>
#include <sys/socket.h>
#include <cstdlib>
#include <cstdio>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>

using namespace std;

//设定,客户端发送数据,服务器接收数据,所有数据,间隔1s,依次发送,当对方接收窗口为0时,继续发送下一个数据
int ser_recv()
{
    //创建socket
    Socket* sock = new SocketServer;
    
    const char* ip = "*";
    int port = 8888;

    int ret = sock->CreateSock(ip, port);

    if (ret == -1)
    {
        cerr<<"create server socket error."<<strerror(errno)<<endl;
        return -1;
    }

    //开始接收数据
    char msg[120] = {0};

    //循环发送数据,获取发送缓冲区的大小
    int fd = sock->GetSockfd();
    
    //获取当前时间
    int bytes = 0;
    //获取套接字的默认接收缓存区的大小
    socklen_t recvbuflen = 0;
    socklen_t len = sizeof(recvbuflen);
    getsockopt(fd, SOL_SOCKET, SO_RCVBUF, (void*)&recvbuflen, &len);
    
    cout<<"原始的缓冲区长度是:"<<recvbuflen<<endl;
    int connfd = sock->Accept();

    while(true)
    {
        int ret = recv(connfd, msg, sizeof(msg), 0);

        if (ret == -1)
        {
            cout<<"error happened. reason:"<<strerror(errno)<<endl;
            return 2;
        }
     
        cout<<"recv data length:"<<ret<<endl;
        //查看接收缓冲区中未读取的大小
        int res = ioctl(connfd, SIOCINQ, &bytes);
        
        if (res)
        {
            cout<<"error happened. reason:"<<strerror(errno)<<endl;
            return 1;
        }

        cout<<"have these bytes data:"<<bytes<<endl;

        sleep(5);
    }
}

int main()
{
    ser_recv();
}

执行的结果部分截图如下:

客户端发送:

服务端接收:

 在抓到的报文中,我们还发现,在接收端发出tcp zero window时,我们的客户端还在往发送缓冲区中添加数据,只是因为接收端零窗口,所以数据没有发送给接收端。还复现了接收端出现零窗口时,发送端一直发送零窗口探测报文。截图如下



评论

发表评论