簡體   English   中英

Unix 域套接字和寫緩沖區溢出

[英]Unix domain socket and write buffer overflow

我在我的一個應用程序中使用 Unix 域 sockets 並且在寫入套接字時我觀察到一個非常奇怪的行為。 簡短描述:有 2 個應用程序 - 客戶端和服務器,每個應用程序同時讀取和寫入,即套接字以全雙工模式工作。 為了防止一些溢出,我檢查了寫緩沖區:

ioctl(socket_handler, TIOCOUTQ, &pending)

但有時我得到的結果很奇怪 - 768、1536、2304 等。一方面,這個數字看起來不像我寫的字節數,另一方面看起來有點可疑 - 0x300、0x600 等,在換句話說,它看起來像一個標志或錯誤代碼。 文檔對此只字未提。

為了保持一致並防止出現問題,我提供了一個完整的代碼示例來重現該問題:

#include <iostream>
#include <string>
#include <sys/socket.h>
#include <sys/un.h>
#include <linux/sockios.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>
#include <poll.h>

static int sock;
static socklen_t len;
static struct sockaddr_un addr;
static std::string path = "@testsock";
static pthread_t thread;
static bool running;
static pollfd polls[2] = {};
static char buf[200];
static int err;

bool InitSocket()
{
    sock = socket(AF_UNIX, SOCK_STREAM,0);
    memset(&addr, 0, sizeof(addr));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, path.c_str(), path.size());
    *addr.sun_path = '\0';
    len = static_cast<socklen_t>(__builtin_offsetof(struct sockaddr_un, sun_path) + path.length());
    int opt_value = 1;
    err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, static_cast<void *>(&opt_value), sizeof(int));
    int on = 1;
    err = ioctl(sock, FIONBIO, &on);
    return true;
}

bool InitServer()
{
    polls[0].fd = sock;
    polls[0].events = POLLIN;

    err = bind(sock, reinterpret_cast<struct sockaddr *>(&addr), len);
    err = listen(sock, 10);
    return true;
}

bool InitClient()
{
    polls[1].fd = sock;
    polls[1].events = POLLIN;

    err = connect(sock, reinterpret_cast<struct sockaddr *>(&addr), len);
    return true;
}

uint32_t GetRand(uint32_t min, uint32_t max)
{
    return static_cast<uint32_t>(rand()) % (max - min) + min;
}
void *ReadThread(void*)
{
    while(running)
    {
        int status = poll(polls, 2, 1000);
        if(status > 0)
        {
            for(int i = 0;i < 2;i ++)
            {
                if(polls[i].revents == 0)
                    continue;

                if(i == 0)
                {
                    struct sockaddr_un cli_addr;
                    socklen_t clilen = sizeof(cli_addr);
                    int new_socket = accept(sock, reinterpret_cast<struct sockaddr *>(&cli_addr), &clilen);
                    int on = 1;
                    ioctl(new_socket, FIONBIO, &on);
                    polls[1].fd = new_socket;
                    polls[1].events = POLLIN;
                    std::cout << "client connected" << std::endl;
                }
                else
                {
                    ssize_t readBytes = read(polls[1].fd, &buf, 100);
                    if(readBytes > 0)
                    {
                        //std::string str(buf, readBytes);
                        std::cout << "         read " << readBytes << " bytes: " << std::endl;
                        int p = GetRand(10, 100); //
                        usleep(p * 1000);         // removing these lines solves the problem, but ... 
                    }
                    else
                    {
                        std::cout << "read error" << std::endl;
                        running = false;
                    }
                }
            }
        }
    }
    return nullptr;
}



int main(int argc, char *argv[])
{       
    if(argc < 2)
        return 1;

    srand(static_cast<unsigned int>(time(nullptr)));
    InitSocket();
    std::string app_type(argv[1]);
    if(app_type == "-s")
        InitServer();
    else if(app_type == "-c")
        InitClient();
    else
        return 1;

    running = true;
    pthread_create(&thread, nullptr, &ReadThread, nullptr);

    if(app_type == "-s")
    {
        while(polls[1].fd == 0) usleep(100);
    }

    char sendbuf[100];
    for(int i = 0;i < 100;i ++)
    {
        int length = GetRand(10, 100);
        int delay = GetRand(1, 10);
        for(int j = 0;j < length;j ++)
        {
            sendbuf[j] = GetRand('a', 'z');
        }

        unsigned long pending = 0;
        err = ioctl(polls[1].fd, TIOCOUTQ, &pending);
        std::cout << "pending " << pending << std::endl;

        err = send(polls[1].fd, sendbuf, length, MSG_NOSIGNAL);
        std::cout << "write " << length << " bytes" << std::endl;
        usleep(delay * 1000);
    }

    running = false;
    pthread_join(thread, nullptr);

    if(app_type == "-s")
        close(polls[0].fd);
    close(polls[1].fd);

    return 0;
}

運行代碼:服務器:

test_app -s 

然后是客戶端應用程序(在另一個控制台窗口中):

test_app -c 

一個可能的 output:

服務器:

client connected
         read 83 bytes: 
pending 0
write 66 bytes
pending 0
write 18 bytes
pending 768          <----- ????
write 17 bytes
pending 1536         <----- ????
write 79 bytes
         read 100 bytes: 
pending 2304         <----- ????
write 51 bytes
pending 3072
write 25 bytes
         read 100 bytes: 
pending 2304
write 96 bytes
pending 3072
write 19 bytes

客戶端:

pending 0
write 83 bytes
         read 66 bytes: 
pending 0
write 19 bytes
pending 768
write 94 bytes
pending 1536
write 52 bytes
pending 2304
write 11 bytes
pending 3072
write 39 bytes
pending 3072
write 35 bytes
         read 100 bytes: 
pending 3840
write 20 bytes
pending 2304
write 92 bytes
pending 3072
write 53 bytes
pending 3840
write 88 bytes
pending 4608
write 84 bytes
         read 100 bytes: 

正如我注意到在讀取線程中添加人為延遲后會出現此問題。 在我的實際應用程序中,我處理一條消息,這會產生一個小的延遲。 但我絕對不明白這些數字從何而來(待定)以及讀取線程中的延遲如何影響這一點?

我怎樣才能避免這種情況? 我怎樣才能得到真正的寫緩沖區,而不是這些奇怪的數字?

我使用 Unix 域 sockets [...和]為了防止一些溢出,我檢查了寫緩沖區

這不是你的責任,也不是,據我所知,在你的權力范圍內。 特別是,並非所有 ioctl 都適用於所有設備。 TIOCOUTQ是用於終端設備的 ioctl——宏名稱中的前導“T”是助記符——它未列在適用於 Linux 中 sockets 的 ioctl 中(因為您在評論中提到目標是 Linux)。

kernel 確保緩沖區不會溢出。 當緩沖區空間不足時,它將阻止寫入請求直到有足夠的空間可用或執行短寫入或兩者兼而有之。 或者,如果套接字處於非阻塞模式,則否則會阻塞的寫入將改為失敗並EAGAIN

我怎樣才能避免這種情況?

避免在它們不適用的設備上使用 ioctl。

我怎樣才能得到真正的寫緩沖區,而不是這些奇怪的數字?

據我所知,您不能這樣做,而且您似乎不太可能真正需要這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM