多网卡的QUdp 组播监听

本文探讨了如何利用组播技术在局域网中提供服务,例如注册中心URL的传播,并解决了多网卡环境下UDP组播可能接收不到消息的问题。通过遍历所有网卡并为每个符合条件的网卡绑定UDP套接字,确保了不同网络环境下的消息传递。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

局域网中提供一个服务,比如提供Qt RO网络中的注册中心的url, 可以考虑使用组播技术.

对于多网卡来说, udp加入组播地址,并绑定端口后,不一定能够收到客户端发送的查询消息,估计是因为消息所在的网络不一定就是绑定的网卡所在的网络,在虚拟机上测试时,会出现这种情况.

现象重现: 服务端在本机上执行, 客户端在虚拟机上执行,客户端向组播地址发送的消息,在NAT模式下,因为ip地址不在同一个网段,数据包不会继续传递,就会造成这种现象

解决的思路就是用每一个网口绑定一个udp就行.
迭代网口,如果网卡激活:IsUp,并且能够组播 CanMulticast,而且不是回环 interface.name().contains(“loopback”),那就构造一个udp,并使用该网卡进行绑定,加入组播

#include "widget.h"
#include "ui_widget.h"

#include <QDebug>
#include <QNetworkDatagram>
#include <QNetworkInterface>
#include <QTimer>
#define qout qDebug() << __FILE__ << __LINE__
Widget::Widget(QWidget* parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    auto allInterface = QNetworkInterface::allInterfaces();
    for (int i = 0; i < allInterface.size(); ++i) {
        auto interface = allInterface.at(i);
        if (interface.flags() & QNetworkInterface::IsUp) {
            if (interface.flags() & QNetworkInterface::CanMulticast) {
                if (!interface.name().contains("loopback")) {

                    auto udp = new QUdpSocket(this);

                    udp->bind(QHostAddress::AnyIPv4, 62224,
                        QAbstractSocket::ShareAddress | QAbstractSocket::ReuseAddressHint);

                    udp->joinMulticastGroup(QHostAddress("224.2.2.2"), interface);

                    connect(udp, &QUdpSocket::readyRead,
                        this, &Widget::readPendingDatagrams);

                    qout << interface;
                }
            }
        }
    }

    //    auto* timer = new QTimer(this);
    //    connect(timer, &QTimer::timeout, [this] {
    //        this->multiSendMsg();
    //    });
    //    timer->start(5000);
}

Widget::~Widget() { delete ui; }

// 1. 多播接收,对点发送
void Widget::readPendingDatagrams()
{
    auto udp = (QUdpSocket*)sender();
    while (udp->hasPendingDatagrams()) {
        QNetworkDatagram datagram = udp->receiveDatagram();
        qout << "receive msg" << datagram.data();
        qout << datagram.senderAddress() << datagram.senderPort();
        QString msg = "tcp://192.168.101.121:9999";
        udp->writeDatagram(
            msg.toUtf8(),
            datagram.senderAddress(),
            datagram.senderPort());
    }
}

<think>好的,用户问的是“监听SOCKET监听的什么”。我需要先理解用户的问题。监听Socket在TCP网络编程中是一个基础概念,可能用户刚开始学习网络编程,或者对Socket机制不太清楚。 首先,我需要回顾之前用户的历史对话。上一次他们询问了为什么连接和通信不在同一个Socket上,我详细解释了监听Socket和通信Socket的分工。现在这个问题是进一步的,用户可能想更深入理解监听Socket的具体作用。 接下来,我需要确定“监听”具体指什么。监听Socket在TCP服务器中的作用是接受新的连接请求,也就是三次握手的过程。当服务器调用listen()函数后,Socket进入监听状态,等待客户端的连接请求。这时候,监听Socket并不处理数据传输,而是负责接收连接,然后通过accept()创建新的通信Socket来处理数据交换。 用户可能对监听的具体机制有疑问,比如监听的端口、IP地址,或者监听队列的概念。需要解释清楚监听Socket绑定的端口和IP,以及如何处理多个并发连接请求。比如,监听队列的大小,backlog参数的作用,还有内核如何处理这些挂起的连接。 另外,用户可能对监听Socket和通信Socket的区别还有疑惑,需要再次强调两者的不同职责。监听Socket只是负责接受连接,而每个连接建立后,通信Socket负责具体的数据传输,这样设计的好处是提高并发能力和系统资源的有效管理。 还要考虑用户可能的误区,比如是否监听Socket会处理数据,或者为什么不能直接用监听Socket通信。需要指出监听Socket没有建立具体的连接,无法进行数据传输,必须通过accept()创建新的Socket来处理。 可能用户在实际编程中遇到了问题,比如服务器无法接收连接,或者backlog设置不当导致连接被拒绝。这时候需要解释监听队列的工作原理,以及如何合理设置backlog参数,避免连接丢失。 最后,要确保语言通俗易懂,结构清晰,分点解释,帮助用户逐步理解监听Socket的作用,结合之前的回答,保持一致性,同时补充新的细节,比如内核维护的连接队列,SYN队列和ACCEPT队列的区别,以及如何处理半连接和全连接状态。 总结,用户的真实需求是理解监听Socket的核心功能,需要从原理、工作流程、参数设置、实际应用等方面全面解释,确保用户不仅能知道监听什么,还能理解背后的机制和设计原因。</think>在TCP网络编程中,**监听Socket(Listening Socket)** 的核心职责是**监听特定网络端口上的连接请求**。以下是具体解析: --- ### 一、监听的核心对象 #### 1. **监听的端口** - **绑定机制**:通过`bind()`系统调用绑定到特定端口(如Web服务常用80端口) - **端口独占性**:一个端口同一时间只能被一个监听Socket绑定 - **示例**: ```c server_socket.bind(("0.0.0.0", 8080)) // 绑定到所有网卡的8080端口 ``` #### 2. **监听的IP地址** - 通配地址(`0.0.0.0`):监听所有网卡接口 - 特定IP(如`192.168.1.100`):仅监听指定网卡 --- ### 二、监听的具体内容 #### 1. **TCP三次握手请求** - 监听Socket专门接收`SYN`包(连接请求) - 内核自动完成握手过程: 1. 收到客户端`SYN` → 回复`SYN-ACK` 2. 收到客户端`ACK` → 建立完整连接 #### 2. **维护连接队列** - **半连接队列(SYN队列)** 存储已完成第一次握手(`SYN_RECV`状态)的请求 大小由`/proc/sys/net/ipv4/tcp_max_syn_backlog`控制 - **全连接队列(ACCEPT队列)** 存储已完成三次握手(`ESTABLISHED`状态)的连接 大小由`listen(backlog)`参数决定 --- ### 三、监听工作流程 ```plaintext 客户端 服务器监听Socket | | | SYN (seq=x) | | ---------------------------------> | → 放入半连接队列 | | | SYN-ACK (seq=y, ack=x+1) | | <--------------------------------- | | | | ACK (ack=y+1) | | ---------------------------------> | → 移入全连接队列 | | | accept() | → 从队列取出连接 ``` --- ### 四、关键特性 1. **非阻塞性监听** - 使用`select()`/`epoll()`可同时监控多个监听Socket - 示例代码: ```c while(1) { int ready = epoll_wait(epfd, events, MAX_EVENTS, -1); for (处理每个就绪事件) { if (是监听Socket事件) accept(); // 接受新连接 else recv(); // 处理已有连接数据 } } ``` 2. **多协议支持** - 同一端口可同时监听TCP和UDP(需分别创建Socket) - 但TCP/UDP监听Socket相互独立 --- ### 五、常见误区澄清 1. **监听 ≠ 数据接收** - 监听Socket仅处理连接建立,不参与后续数据传输 - 若强行用监听Socket发送数据: ```c send(listen_fd, data, len, 0); // 会返回ENOTCONN错误 ``` 2. **backlog参数误解** - 实际可用连接数 = `min(backlog, /proc/sys/net/core/somaxconn)` - Linux内核4.3+后采用`SYN Cookies`机制突破队列限制 --- ### 六、网络层实现细节 - **内核维护两个队列**: - **未完成连接队列**(SYN_RCVD状态) - **已完成连接队列**(ESTABLISHED状态) - **队列溢出处理**: - 若全连接队列满,内核会**忽略后续ACK包**(客户端将重试) - 使用`ss -lnt`命令可查看队列状态: ```bash State Recv-Q Send-Q Local Address:Port Peer Address:Port LISTEN 0 128 *:8080 *:* ``` - `Recv-Q`:当前ACCEPT队列中的连接数 - `Send-Q`:队列最大容量 --- ### 七、设计本质 监听Socket本质是**连接入口控制器**,其核心价值在于: 1. **连接准入控制**:通过队列机制实现流量整形 2. **资源预分配**:提前绑定端口/IP,避免连接建立时的资源竞争 3. **状态隔离**:确保新连接处理不影响已有连接 这种设计使得服务器能同时处理数万个连接请求(如Nginx的C10K问题解决方案),是构建高并发网络服务的基础。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值