【问题标题】:How to stop writing to socket (AF_LOCAL/UNIX, SOCK_STREAM) if client can't cope (buffer++)?如果客户端无法应对(缓冲区++),如何停止写入套接字(AF_LOCAL/UNIX、SOCK_STREAM)?
【发布时间】:2014-04-06 08:51:22
【问题描述】:

我有一个小型应用程序,它使用 Unix 域套接字与客户端通信数据。 套接字的类型是 SOCK_STREAM 并且设置为阻塞模式(默认)。当客户端懒惰并且无法处理我正在写入套接字的数据时,我在特定情况下遇到了一些麻烦——缓冲区趋于变满,我将阻塞在 write() ,我想避免的事情。

我尝试通过在 write() 之前调用它来使用 select/pselect,以查看是否可以执行 write()。事情只进行了一半,从某种意义上说,我被告知我不能 write() 了(当缓冲区达到一定大小时 select 返回 0),但是之后,当客户端能够再次读取时,select/pselect 不会t 通知这个(我希望返回 1,然后我可以执行 write())。

你对这件事有什么想法吗?

谢谢!

编辑:

服务器:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#define SV_SOCK_PATH "/tmp/srv_sk_stream"
#define BACKLOG 1
#define MAXWRITE 1000
#define TOMSEC 1000

int main(int argc, char* argv[]) {
    struct sockaddr_un addr;
    int sfd, cfd = -1;
    ssize_t numRead;
    char buffer[30];
    int i = 0;
    ssize_t retValWrite = -1;
    numRead = sizeof(buffer);
    if(argc !=2 ) {printf("Give the sleep value argument (msec)!\n"); return EXIT_FAILURE;}
    int sleepTime = atoi(argv[1]) * TOMSEC;
    fd_set writeFdSet;
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 0;
    struct timespec ts;
    ts.tv_sec = 0;
    ts.tv_nsec = 0;

    /* create socket */
    sfd = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sfd == -1) {perror("FAILED creating a socket!"); return EXIT_FAILURE;}
    if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT) {perror("FAILED removing old socket fd! - "); return EXIT_FAILURE;}

    /* prepare it */
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);

    /* bind it */
    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {perror("FAILED bind! - "); return EXIT_FAILURE;}

    /* start to listen */
    if (listen(sfd, BACKLOG) == -1) {perror("FAILED listen! - "); return EXIT_FAILURE;}

    while(i<MAXWRITE) {
        int bytesToRead = 0;
        retValWrite = -13;


        /* block in accept until a client connects (only one) */
        if (-1 == cfd) {
            cfd = accept(sfd, NULL, NULL);
            if (cfd == -1) {perror("FAILED accept! - "); return EXIT_FAILURE;}
            FD_ZERO(&writeFdSet);
            FD_SET(cfd, &writeFdSet);
        }

        //ioctl(cfd,FIONREAD,&bytesToRead);
        //printf("---------> SND_BUFF has %d bytes left to be read\n", bytesToRead);

        sprintf(buffer, "PING FROM SERVER %d", i);
        /* write to the client's socket */
        int retValSelect = 0;
        errno = 0;
        retValSelect = select(cfd+1, NULL, &writeFdSet, NULL, &tv);
        //perror("ERRNO from pselect: ");
        if (retValSelect > 0) {
            int retValFdIsSet = 0;
            retValFdIsSet = FD_ISSET(cfd, &writeFdSet);
            //perror("FD_ISSET - ");
            if(retValFdIsSet) {
                retValWrite = write(cfd, buffer, numRead);
                if (retValWrite == 0 ) {
                    printf("Written 0 bytes\n");
                } else if (retValWrite < 0) {
                    perror("Error writiing to socket!\n");
                }

                ++i;
                printf("Written: %d \n", i);

                usleep(sleepTime);
            }
        } else  {
            printf("Wait for it... %d\n", count);
        }
    }

    /* close client socket */
    if (close(cfd) == -1) {perror("FAILED close srv socket! - "); return EXIT_FAILURE;}
    if (remove(SV_SOCK_PATH) == -1 && errno != ENOENT) {perror("FAILED removing old socket fd! - "); return EXIT_FAILURE;}

    return EXIT_SUCCESS;
}

客户:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <errno.h>
#define SV_SOCK_PATH "/tmp/srv_sk_stream"
#define MAXREAD 1000
#define TOMSEC 1000

int main(int argc, char** argv)
{
    struct sockaddr_un addr;
    int sfd;
    ssize_t numRead;
    char pingStr[30];
    numRead = sizeof(pingStr);
    int readCount = 0;
    if(argc !=2 ) {printf("Give the sleep value argument (msec)!\n"); return EXIT_FAILURE;}
    int sleepTime = atoi(argv[1]) * TOMSEC;

    /* create socket */
    sfd = socket(AF_UNIX, SOCK_STREAM, 0);

    if(sfd == -1) {perror("FAILED creating a socket! - "); return EXIT_FAILURE;}

    /* prepare it */
    memset(&addr, 0, sizeof(struct sockaddr_un));
    addr.sun_family = AF_UNIX;
    strncpy(addr.sun_path, SV_SOCK_PATH, sizeof(addr.sun_path) - 1);

    /* connect */
    if (connect(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_un)) == -1) {perror("FAILED connecting to socket! - "); return EXIT_FAILURE;}

    while (readCount < MAXREAD) {
        char strRead[numRead];
        int bytesRead=0;
        int bytesToRead=0;

        ioctl(sfd,FIONREAD, &bytesToRead);
        printf("---------> RCV_BUFF has %d bytes left to be read\n", bytesToRead);

        /* read */
        bytesRead = read(sfd, &strRead, numRead);
        if(bytesRead == 0) {
            perror("Read 0 bytes from socket! - ");
        }
        else if (bytesRead < 0) {
            perror("FAILED reading from socket! - ");
        }
        ++readCount;
        printf("%d - READ: %s\n", readCount, strRead);
        usleep(sleepTime);
    }

    /* close */
    if (close(sfd) == -1) {perror("FAILED close clt socket! - "); return EXIT_FAILURE;}

    return EXIT_SUCCESS;
}

服务器输出(以 100 毫秒写入)

./srvstream 100
Written: 1 
Written: 2 
Written: 3 
Written: 4 
Written: 5 
Written: 6 
Written: 7 
Written: 8 
Written: 9 
Written: 10 
Written: 11 
Written: 12 
Written: 13 
Written: 14 
Written: 15 
Written: 16 
Written: 17 
Written: 18 
Written: 19 
Written: 20 
Written: 21 
Written: 22 
Written: 23 
Written: 24 
Written: 25 
Written: 26 
Written: 27 
Written: 28 
Written: 29 
Written: 30 
Written: 31 
Written: 32 
Written: 33 
Written: 34 
Written: 35 
Written: 36 
Written: 37 
Written: 38 
Written: 39 
Written: 40 
Written: 41 
Written: 42 
Written: 43 
Written: 44 
Written: 45 
Written: 46 
Written: 47 
Written: 48 
Written: 49 
Written: 50 
Written: 51 
Written: 52 
Written: 53 
Written: 54 
Written: 55 
Written: 56 
Written: 57 
Written: 58 
Written: 59 
Written: 60 
Written: 61 
Written: 62 
Written: 63 
Written: 64 
Written: 65 
Written: 66 
Written: 67 
Written: 68 
Written: 69 
Written: 70 
Written: 71 
Written: 72 
Written: 73 
Written: 74 
Written: 75 
Written: 76 
Written: 77 
Written: 78 
Wait for it... 0
Wait for it... 0
Wait for it... 0
Wait for it... 0
Wait for it... 0
------->8-------- stays this way even after the client reports consumes the whole buffer

客户端(1 秒读取):

./cltstream 1000
---------> RCV_BUFF has 0 bytes left to be read
1 - READ: PING FROM SERVER 0
---------> RCV_BUFF has 270 bytes left to be read
2 - READ: PING FROM SERVER 1
---------> RCV_BUFF has 540 bytes left to be read
3 - READ: PING FROM SERVER 2
---------> RCV_BUFF has 810 bytes left to be read
4 - READ: PING FROM SERVER 3
---------> RCV_BUFF has 1080 bytes left to be read
5 - READ: PING FROM SERVER 4
---------> RCV_BUFF has 1350 bytes left to be read
6 - READ: PING FROM SERVER 5
---------> RCV_BUFF has 1620 bytes left to be read
7 - READ: PING FROM SERVER 6
---------> RCV_BUFF has 1890 bytes left to be read
8 - READ: PING FROM SERVER 7
---------> RCV_BUFF has 2100 bytes left to be read
9 - READ: PING FROM SERVER 8
---------> RCV_BUFF has 2070 bytes left to be read
10 - READ: PING FROM SERVER 9
---------> RCV_BUFF has 2040 bytes left to be read
11 - READ: PING FROM SERVER 10
---------> RCV_BUFF has 2010 bytes left to be read
12 - READ: PING FROM SERVER 11
---------> RCV_BUFF has 1980 bytes left to be read
13 - READ: PING FROM SERVER 12
---------> RCV_BUFF has 1950 bytes left to be read
14 - READ: PING FROM SERVER 13
---------> RCV_BUFF has 1920 bytes left to be read
15 - READ: PING FROM SERVER 14
---------> RCV_BUFF has 1890 bytes left to be read
16 - READ: PING FROM SERVER 15
---------> RCV_BUFF has 1860 bytes left to be read
17 - READ: PING FROM SERVER 16
---------> RCV_BUFF has 1830 bytes left to be read
18 - READ: PING FROM SERVER 17
---------> RCV_BUFF has 1800 bytes left to be read
19 - READ: PING FROM SERVER 18
---------> RCV_BUFF has 1770 bytes left to be read
20 - READ: PING FROM SERVER 19
---------> RCV_BUFF has 1740 bytes left to be read
21 - READ: PING FROM SERVER 20
---------> RCV_BUFF has 1710 bytes left to be read
22 - READ: PING FROM SERVER 21
---------> RCV_BUFF has 1680 bytes left to be read
23 - READ: PING FROM SERVER 22
---------> RCV_BUFF has 1650 bytes left to be read
24 - READ: PING FROM SERVER 23
---------> RCV_BUFF has 1620 bytes left to be read
25 - READ: PING FROM SERVER 24
---------> RCV_BUFF has 1590 bytes left to be read
26 - READ: PING FROM SERVER 25
---------> RCV_BUFF has 1560 bytes left to be read
27 - READ: PING FROM SERVER 26
---------> RCV_BUFF has 1530 bytes left to be read
28 - READ: PING FROM SERVER 27
---------> RCV_BUFF has 1500 bytes left to be read
29 - READ: PING FROM SERVER 28
-----------8<------------ -- continues this way until RCV_BUFF has 0 bytes left and then blocks

【问题讨论】:

  • 一旦对等方从套接字读取了足够多的数据,Select 应该返回套接字是可写的,以便它有空间写入更多。如果你不是这种情况,我会建议一个编码错误,但没有代码很难说。
  • 刚刚添加了我的代码。谢谢!
  • 服务器代码对我来说没有多大意义:它将接受一个新客户端,然后只发出一次写入 - 因为在发送缓冲区无法满之前它没有向套接字写入任何内容,并且因此 select 将返回套接字在所有情况下都是可写的。所以写入会成功,即使缓冲区中的所有内容都不会被写入(但不会阻塞)。你真的能用你展示的代码重现你的问题吗?
  • 添加了我的输出。服务器等待一个客户端连接,然后尝试写入 MAXWRITE 次。如果 select 返回 0,它将跳过调用 write()。
  • 您在接受后设置 writeFdSet 一次(顺便说一句,您在循环内执行接受很烦人)。但是选择更改 writeFdSet,例如如果 Fd 不可写,它将从集合中删除它。因此,对于所有剩余的选择,您只期望什么(空 writeFdSet)。另一件事是您等待超时为 0,例如执行繁忙循环。您应该使用 NULL 作为超时(等待成功或中断)或将其设置为您想要等待的最长时间。

标签: c sockets unix blocking unix-socket


【解决方案1】:

如果您处于阻塞模式,如果发送缓冲区已满,write() 和 send() 将不会返回 -1/EAGAIN/EWOULDBLOCK。他们会阻止。如果你想处理这些情况,你需要非阻塞模式。

【讨论】:

  • 我仍然希望有一种机制可以避免在客户端缓冲区已满时调用 write/send。另外,我不能依赖客户端告诉我“嘿,给我一些东西,我的缓冲区现在是空的”,我更愿意在服务器端处理它。
  • select() 会告诉您缓冲区何时已满,但它不会告诉您您将要写入的(未知)数据量何时会超过缓冲区,这就是导致阻塞。它不能知道。只有你自己知道。
猜你喜欢
  • 2017-07-31
  • 1970-01-01
  • 1970-01-01
  • 2012-03-27
  • 1970-01-01
  • 2013-10-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多