套接字编程,检查入口UDP缓冲区是否为空?

时间:2011-10-16 12:40:41

标签: c++ sockets udp mingw

编写将客户端发送到服务器的UDP客户端。在下面的代码中,当服务器发回几个数据包时,程序的行为并不像我期望的那样。我希望逐个process() 处理任何传入的数据包,直到条目缓冲区变空。我认为问题与阻止recv的行为有关。

#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <winsock.h>

using namespace std;

void process(const char *in, int size)
{
    fprintf(stdout, "%s\n", in);
}

int main()
{
    char quack_addr[] = "127.0.0.1";
    unsigned short quack_port = 9091;

    WSAData data;
    WSAStartup(MAKEWORD(2, 2), &data);

    sockaddr_in qserver;
    qserver.sin_family = AF_INET;
    qserver.sin_addr.s_addr = inet_addr(quack_addr);
    qserver.sin_port = htons(quack_port);
    SOCKET client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (client <= 0)
    {
        fprintf(stderr, "Error - Can not create socket.\n");
        exit(1);
    }

    while (true)
    {
        const int MAX = 1024;
        char sbuf[MAX];
        char rbuf[MAX];

        fprintf(stdout, ": ");
        fgets(sbuf, MAX, stdin);
        int slen = strlen(sbuf);

        int r = sendto(client,sbuf,slen,0,(sockaddr*)&qserver,sizeof(qserver));

        // Current code:
        // int rlen = recv(client, rbuf, MAX, 0);
        // if (rlen > 0)
        // {
        //     rbuf[rlen] = 0;
        //     process(rbuf, rlen);
        // }

        // Question starts here:
        //
        // While there is data in queue do:
        // {
        //    (break if there is no data)
        //    int rlen = recv(client, rbuf, MAX, 0);
        //    rbuf[rlen] = 0;
        //    process(rbuf, rlen);
        // }   
    }

    return 0;
}

如何在调用recv(...)之前检查缓冲区是否为空?

在这种情况下会出现问题:

  1. 用户在客户端程序(cmd1)中键入命令。
  2. 同时,服务器向客户端发送3个数据包(pkt1pkt2pkt3)。
  3. 在客户端按Enter后,我希望收到这3个数据包,可能结果对应cmd1process()所有这些数据包一个接一个。
  4. 但在第3阶段按Enter后,我收到了pkt1!在向服务器发送另一个命令后,我将收到pkt2,依此类推......!
  5. 我知道我的代码不足以处理这个问题,所以,我的问题是如何处理它?<​​/ strong>

    注意:我使用netcat -L -p 9091 -u作为UDP服务器

3 个答案:

答案 0 :(得分:2)

在调用recv()之前,使用select()(有合适的​​超时时间)检查传入数据。

类似以下内容(非便携式代码)

#include <winsock2.h>

...

/* somewhere after your sendto, or your first recv */
fd_set recv_set;
timeval tv = {1, 0}; /* one second */
FD_ZERO(&recv_set);
FD_SET(client, &recv_set);
while (select(0, &recv_set, NULL, NULL, &tv) > 0)
{
    /* recv... */
    FD_SET(client, &recv_set); /* actually redundant, since it is already set */
}

答案 1 :(得分:2)

我认为问题(你没有描述的不满意的行为)来自不同的来源。让我列出一些想法和评论c。/之前的说法:

(1)recvfrom()也会阻止。但是,您想要使用它。您的通信目前通过环回发送和接收,这对您的玩具程序来说很好(但是:见下文)。使用recv()接收UDP数据时,您不知道是谁发送的,因为套接字从未连接过()ed。使用recvfrom()为更严肃的程序中的一些最小错误检查做好准备

(2)当select()将程序暂停到i / o availibity时,它只会将套接字阻塞的任何问题都放到不同的级别。但这不是问题

(3)检查接收缓冲区是否为空,在recvfrom()中使用标志MSG_PEEK在适当的位置。它通常只用于处理稀缺的内存,但它应该可以完成这项工作。

(4)原因1为什么我相信你会看到你没有详细描述的问题: UDP数据报保留消息边界。这意味着recvfrom()将读取构成发送的任何消息的整个数据块。但是,如果您读入的缓冲区小于读取的数据,则会以静默方式丢弃任何剩余。所以请确保你有一个很大的缓冲区(理想情况下是65k)。

(5)原因2: 您收到发送到环回的任何数据。如果您当前也连接到某个网络(坐在互联网上),那么您捕获的内容可能实际上来自与您预期不同的来源。所以至少在休息阶段,断开连接。

阻止不应成为问题。干净编码时,您的基本逻辑是: Recvfrom()(阻止/等到准备好) 处理 如果缓冲区为空则查看 如果是的话退出 如果没有,则循环返回以接收更多,

你似乎想要这样做。由于你没有多线程,优化fie perfiormance或类似的,你不应该关心阻塞。如果您发现接收缓冲区太小,请使用

增加其大小

optname SO_RCVBUF

的setsockopt()

答案 2 :(得分:1)

iPhone有时会出错并且不允许我发表评论。谢谢,史蒂夫。这只是继续谈话。

我认为这意味着'取消'问题从这里开始'。部分答案,因为这仍然取决于我的第二个评论;这或多或少是预期的。假设服务器发送的三条消息已经排队,在您第一次按Enter后,您的数据包将被发送(从不阻止,因为sendto()不会阻止UDP),服务器接收(I假设,如上所述,回显并添加到FIFO接收缓冲区,您在其中读取了三个排队的消息。然后您的程序中有一个recv()接收第一个排队的消息,打印出来。您当前的逻辑是回到循环的顶部,期待另一个输入并等待它(所以这不会在套接字级别上被阻止,但是当你的程序请求输入时,例如简单地“输入”),然后转到第二个最初发送的消息(由服务器)并处理一个。再循环一次,并且所有三个都完成了。再次进入,并假设服务器回显你发送的内容,你应该开始接收输入的消息(如果你只输入enter,它可能是空的)。除了你杀了它之外,循环当前不会退出。