【问题标题】:accept function waits while packet has been sent接收功能等待数据包已发送
【发布时间】:2020-01-09 12:00:47
【问题描述】:

有一个源(相机)将通知(运动警报)发送到服务器的端口 8085,我为其订阅了其 IP。也许在这样的谈判中,我不能称它为“服务器”,但它是我们的服务器,它服务于其他一些东西。 我写了两个程序来接收和回显当前的运动状态,现在只在屏幕上。收到的数据包是 XML 格式的,我可以解析并找到所需的信息。为了测试,我只提取标记为 UtcTime 的时间。另一台机器使用 Onvif 设备管理器订阅了同一台相机,这样我就可以检查我是否错过了一些“时间”。其中一个程序使用 GoLang,一个使用 C++。前者按预期工作,而后者则没有。也许这个(C++)是我第一次接触套接字编程。在 C++ 中我没有使用任何额外的库,并且我使用了传统的套接字编程方法,也不需要在客户站点使用任何额外的库。问题是 ODM 回显新时间(或说通知消息)以及 GoLang 代码,而 C++ 代码坚持接受功能(接受...消息)。我给你们两个调查。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "strings"
)

func count() (f func() int) {
    var counter int
    f = func() int {
        counter++
        return counter
    }
    return
}

func main() {
    http.HandleFunc("/", Server)
    http.ListenAndServe(":8085", nil)
}

func Server(w http.ResponseWriter, r *http.Request) {
    body, err := ioutil.ReadAll(r.Body)
    if err != nil {
        log.Fatal(err)
    }
    str := string(body)
    for {
        index := strings.Index(str, "UtcTime")
        if index == -1 {
            break
        }
        part := str[index+20 : index+28]
        fmt.Printf("%s\n", part)
        str = str[index+28:]
    }
}

这是 GoLang 中正常运行的代码。现在 C++ 代码不能按预期工作并坚持接受:

#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string>

#define DBG printf("%s:%d\n", __FILE__, __LINE__)
#define DIE die(__FILE__, __LINE__)

void die(const char *file, int line)
{
    printf("%s:%d: %s\n", file, line, strerror(errno));
    exit(1);
}

std::string extractTime(const char *utc)
{
    char buf[80];
    memcpy(buf, utc + 11, 8);
    buf[9] = 0;
    return buf;
}

int main()
{
    printf("creating socket...\n");
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1)
        DIE;
    int reuseaddr = 1;
    if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) == -1)
        DIE;

    printf("binding...\n");
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8085);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(sock, (sockaddr *)&addr, sizeof(addr)) == -1)
        DIE;

    printf("listening...\n");
    if (listen(sock, SOMAXCONN) == -1)
        DIE;
    socklen_t size = sizeof(addr);
    while (true)
    {
        printf("accepting...\n");
        int new_sock = accept(sock, 0, 0);
        if (new_sock < 0)
            DIE;
        const int buf_size = 80;
        char buf[buf_size * 2 + 1];
        memset(buf, 0, buf_size);
        int read_size;
        std::string time;
        while (true)
        {
            memcpy(buf, buf + buf_size, buf_size);
            read_size = recv(new_sock, buf + buf_size, buf_size, 0);
            if (read_size < 0)
                DIE;
            buf[buf_size + read_size] = 0;
            char *p = strstr(buf, "UtcTime");
            if (p && (p - buf < buf_size))
            {
                char buf2[80];
                char *p2 = strstr(p + 9, "\"");
                if (p2)
                {
                    memcpy(buf2, p + 9, p2 - p - 9);
                    buf2[p2 - p - 9] = 0;
                    time = extractTime(buf2);
                    printf("%s\n", time.c_str());
                }
            }
            if (strstr(buf, "</SOAP-ENV:Envelope>"))
                break;
        }
    }
    return 0;
}

注意事项:

  1. accept 功能棒,我按 Ctrl+C 终止程序。下次我运行程序 (C++) 时,我得到了以前无法得到的数据包!
  2. 操作系统是 Linux (Ubuntu 12.04)。 GoLang 代码在同一操作系统中工作。

更新: C++ 代码现在使用单独的进程来接收数据包:

#include <sys/types.h>
#include <sys/socket.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string>

#define DBG printf("%s:%d\n", __FILE__, __LINE__)
#define DIE die(__FILE__, __LINE__)

void die(const char *file, int line)
{
    printf("%s:%d: %s\n", file, line, strerror(errno));
    exit(1);
}

std::string extractTime(const char *utc)
{
    char buf[80];
    memcpy(buf, utc + 11, 8);
    buf[9] = 0;
    return buf;
}

void receive(int s2)
{
    const int buf_size = 80;
    char buf[buf_size * 2 + 1];
    memset(buf, 0, sizeof(buf));
    int read_size;
    std::string time;
    FILE *fp = fopen("dump.bin", "ab");
    if (!fp)
        DIE;
    while (true)
    {
        memcpy(buf, buf + buf_size, buf_size);
        read_size = recv(s2, buf + buf_size, buf_size, 0);
        if (read_size < 0)
            DIE;
        if (read_size == 0)
            break;
        if (fwrite(buf + buf_size, buf_size, 1, fp) != 1)
            DIE;
        buf[buf_size + read_size] = 0;
        //printf("%d bytes received: '%s'\n", read_size, buf + buf_size);
        char *p = strstr(buf, "UtcTime");
        if (p && (p - buf < buf_size))
        {
            char buf2[80];
            char *p2 = strstr(p + 9, "\"");
            if (p2)
            {
                memcpy(buf2, p + 9, p2 - p - 9);
                buf2[p2 - p - 9] = 0;
                time = extractTime(buf2);
                printf("%s\n", time.c_str());
            }
        }
        if (read_size < buf_size)
            break;
    }
    fclose(fp);
    close(s2);
}

int main()
{
    printf("creating socket...\n");
    int s1 = socket(AF_INET, SOCK_STREAM, 0);
    if (s1 == -1)
        DIE;
    int reuseaddr = 1;
    if (setsockopt(s1, SOL_SOCKET, SO_REUSEADDR, &reuseaddr, sizeof(reuseaddr)) == -1)
        DIE;

    printf("binding...\n");
    sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8085);
    addr.sin_addr.s_addr = INADDR_ANY;
    if (bind(s1, (sockaddr *)&addr, sizeof(addr)) == -1)
        DIE;

    printf("listening...\n");
    if (listen(s1, 0) == -1)
        DIE;
    socklen_t size = sizeof(addr);
    while (true)
    {
        printf("accepting...\n");
        int s2 = accept(s1, 0, 0);
        if (s2 < 0)
            DIE;
        int pid = fork();
        if (pid == 0)
        {
            receive(s2);
            break;
        }
    }
    return 0;
}

更新: 我也检查了 C#:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;

namespace tcplistener
{
    class Program
    {
        static void Main(string[] args)
        {
            const int port = 8081;

            TcpListener tcpListener = new TcpListener(IPAddress.Any, port);
            tcpListener.Start();

            while (true)
            {
                Socket socketConnection = tcpListener.AcceptSocket();
                var buf = new byte[80000];
                var size = socketConnection.Receive(buf);
                Console.WriteLine("{0} bytes received", size);
                Console.WriteLine("{0}", Encoding.UTF8.GetString(buf, 0, size).Substring(0, 80));
                socketConnection.Disconnect(true);
            }
        }
    }
}

虽然这段代码也没有按预期工作,但我可以找到一些线索。你看我把端口改成了8081。这是因为8085已经满了!这就是我的结论,你应该确认我是否正确。第一次,当我运行程序时,我得到以下输出:

2919 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
5395 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
7871 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
13408 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
16503 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
18979 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
21455 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
23931 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
26407 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
28883 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
31359 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33835 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
43739 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
45260 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
36500 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
45260 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
42340 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http
33580 bytes received
POST /behnama-subscription HTTP/1.1
Host: 192.168.14.127:8081
SOAPAction: http

太棒了!似乎 Receive 没有读出(没有移开)它得到的东西。下次我再次调用 Receive 时,收到的数据包仍然存在。第三次读取新收到的数据包以及两个旧数据包!缓冲区大小逐渐增长,直到它变得足够大。在此之后,大小仍然很高,但它可能会每次减少或增长!直到我不再收到任何新数据包。接受不返回,端口变满且无用。如果我再次运行该程序,我将不再得到任何答案,并且它会停留在 AcceptSocket 上,或者立即接受整个缓冲区,并且对 AcceptSocket 的第二次调用仍然存在。 所以乍一看,我可能会说问题似乎是由于接收到的数据包没有被recv释放。我认为 C++ 程序也会发生同样的情况。但是 GoLang 代码呢?我认为它可能会起作用,因为缓冲区的大小不受 C++ 或 C# 代码中的限制。也许如果我让它运行,系统内存就会变满。缓冲区的大小可能是障碍。无论如何,假设我的假设是正确的,你会怎么想?我的代码中没有导致缓冲区空闲的问题是什么?我能做什么?

【问题讨论】:

  • 使用strace 来验证您声称的声明是否属实,并且这会阻止accept()。显示的代码可能实际上并没有按照您的想法执行。所示的 C++ 代码存在缺陷,并且由于访问未初始化的内存而导致未定义的行为。处理输入缓冲区的逻辑被破坏,它将尝试解析未初始化的内存。所有这些缓冲机制的目的都不清楚,而且似乎没有任何有用的目的。
  • 使用这个例子 [geeksforgeeks.org/…
  • @SamVarshavchik:我不知道 strace,但是当我将 printf 放在 accept 之后并且它不打印任何内容时,这意味着它被卡在了接受中,不是吗?关于未初始化的缓冲区,你是对的,我更新了代码。但当然它不会改变代码的行为。处理输入缓冲区的逻辑不是解析不完全可用的损坏字符串。很清楚,这是一个很好的机制。
  • @yaodav:链接好像坏了。它说“这里什么都没有!”
  • @hamidi geeksforgeeks.org/… 立即尝试

标签: c++ linux sockets go camera


【解决方案1】:

您的 C++ 变体有一些有趣的缺陷:

  • 您初始化 buf 的前 80 个 (buf_size) 字符,然后从未初始化的下 80 个字符(第一个 memcpy)中读取。
  • memcpy 80 字节从缓冲区的后半部分到前半部分,无论有多少有用/已初始化的字节实际上在 缓冲区中(任何一半)。
  • 您调用accept 一次,以获取连接的套接字,然后读取直到出错并退出。这会破坏侦听套接字,其余连接不被接受。如果您得到 EOF(read_size 为 0),则您(可能)永远旋转而不会退出,因为每个未来 read 也将返回 0。
  • 如果您在缓冲区中的任意位置找到字符串 "UtcTime",则搜索从 9 个字节后开始出现的双引号,即使 "UtcTime" 距离缓冲区末尾不到 9 个字节。

我怀疑如果你解决所有这些问题,事情可能会更好。一般来说,服务器应该分离一个线程或进程来处理每个新的传入连接,并让主线程/进程返回accept 以等待额外的套接字。 (这一切在 Go 中编码要容易得多,它内置了所有正确的东西。)

【讨论】:

  • 感谢您的回答。 1.关于读取未初始化的缓冲区是正确的。我初始化了第一部分并使用 memcpy 将未初始化的部分复制到它上面。我更改了代码以初始化整个缓冲区。 2. 貌似是个哑副本,其实并没有什么问题。第二部分包括以 \0 结尾的读取缓冲区。因此, strstr 可以正常工作,即使之前读取的其他字符也被复制。 3. 我将代码更改为在单独的进程中调用recv,但它并没有改变行为。因为问题不是调用下一个accept延迟造成的
  • 此外,我更改了代码以检查 read_size,如果它等于 0,则退出循环。 4.如果格式为UtcTime="...,相机每次都会发送UtcTime,那为什么我不应该指望它呢?引号在U之后总是8个字符。我更新问题以包含这些更改。
  • 您收到的数据包可能少于整个数据包。您正在使用的 Go 例程 (io.ReadAll) 将整个输入读取到缓冲区中,而不是尝试使用滑动窗口;这可能是一个更好的策略。
  • 1. recv 被调用,直到整个缓冲区被读取。这应该导致相同的结果。 2. 我也使用了那个策略。使用了足够大的缓冲区(80K),结果相同。此代码中的其他内容无法正常工作,似乎隐藏在我们的视线之外。
  • 也许值得把它分解成更可测试的东西。让家长倾听并接受; fork (就像您现在所做的那样)并让孩子读取 EOF 并将数据保存到一个普通文件中,然后退出(更新代码中的一个小错误:您应该退出以便孩子不会尝试接受( ) - 尽管这实际上应该有效)。然后,只针对纯文件尝试“提取时间”代码,而不是针对通过“listen-and-accept”调用循环的内容。
猜你喜欢
  • 1970-01-01
  • 2012-11-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-10-03
  • 2012-05-20
  • 2011-05-30
  • 1970-01-01
相关资源
最近更新 更多