如何在 QTcpServer 使用的套接字上设置 SO_REUSEADDR?

Posted

技术标签:

【中文标题】如何在 QTcpServer 使用的套接字上设置 SO_REUSEADDR?【英文标题】:how to set SO_REUSEADDR on the socket used by QTcpServer? 【发布时间】:2017-11-13 15:36:47 【问题描述】:

我一直使用 QTcpServer 子类作为 http 服务器,但现在我需要重用服务器端口。

我尝试在 QTcpSocket 中设置ShareAddress | ReuseAddressHint,它似乎有效,因为同一个端口可以绑定两次。但是我没有找到从现有 QTcpServer 对象中获取 QTcpSocket 对象的方法。

我也是用socketDescriptor()来获取native socket的,因为我想用linux C方式来setsockopt,但是不知道怎么用linux C代码和Qt代码一起设置socket选项.(我一直沿用Qt风格。)

我在 ubuntu 和 Qt5.4 上。我被困住了...... 任何帮助,将不胜感激。提前致谢。

【问题讨论】:

【参考方案1】:

因为 SO_REUSEPORT 需要在调用 bind/listen 之前设置,所以您需要先创建描述符,设置所有需要的标志,绑定、监听并将您的描述符转发到 QTcpServer 以供将来使用。

这是一个在任何接口上侦听端口 9999 的示例

mytcpserver.h

#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H

#include <QObject>
#include <QTcpSocket>
#include <QTcpServer>

class MyTcpServer : public QObject

    Q_OBJECT
public:
    explicit MyTcpServer(QObject *parent = 0);

public slots:
    void newConnection();

private:
    QTcpServer *server;
;

#endif // MYTCPSERVER_H

mytcpserver.cpp

#include "mytcpserver.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>

MyTcpServer::MyTcpServer(QObject *parent):QObject(parent)

    this->server = new QTcpServer(this);
    connect(server, &QTcpServer::newConnection, this, &MyTcpServer::newConnection);

    // open server and listen on given port
    int sockfd = 0;
    struct sockaddr_in serv_addr;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    
        qDebug() << "ERROR: IoT Omega Daemon can't open socket";
        exit(EXIT_FAILURE);
    

    int flag = 1;
    if(setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(int)) == -1)
    
        qDebug() << "ERROR: Can't set SO_REUSEADDR";
        exit(EXIT_FAILURE);
    

    //set Address,IFace, Port...
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(9999);


    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(sockaddr_in)) < 0)
    
        qDebug() << "ERROR: can't bind socket";
        exit(EXIT_FAILURE);
    

    if(listen(sockfd,SOMAXCONN) < 0)
    
        qDebug() << "ERROR: can't listen on port";
        exit(EXIT_FAILURE);
    

    //forward our descriptor with SO_REUSEPORT to QTcpServer member
    server->setSocketDescriptor(sockfd);


void MyTcpServer::newConnection()

    qDebug() << "NEW CONNECTION " << __LINE__;

    QTcpSocket * socket = server->nextPendingConnection();

    socket->write("Hello client\r\n");
    socket->flush();
    socket->waitForBytesWritten(3000);

    socket->close();

也许您想进一步优化您的套接字使用?例如,将 SO_LINGER 超时设置为零以避免大量连接处于 TIME_WAIT 状态?也许您不需要等待 ACK(TCP_NODELAY)?

那么你的 newConnection 函数可以如下所示:

void MyTcpServer::newConnection()

    qDebug() << "NEW CONNECTION " << __LINE__;

    QTcpSocket * socket = server->nextPendingConnection();
    int flag = 1;
    struct linger l = 1,0;

    if(setsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_REUSEADDR, (const char *)&flag, sizeof(int)) < 0)
    
        qDebug() << "ERROR: Can't set SO_REUSEADDR";
    

    if(setsockopt(socket->socketDescriptor(), IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int)) < 0)
    
        qDebug() << "ERROR: can't fork set TCP_NODELAY";
    

    socket->write("Hello client\r\n");
    socket->flush();
    socket->waitForBytesWritten(3000);

    if(setsockopt(socket->socketDescriptor(), SOL_SOCKET, SO_LINGER, (const char *)&l,sizeof(l)) < 0)
    
        qDebug() << "ERROR: Can't set SO_LINGER";
    

    socket->close();

这将完成这项工作并释放任何已使用的套接字资源,以便之后可以立即重用它们。对重载微服务器等很有用。

【讨论】:

你救了我。添加setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, &amp;flag, sizeof(int))后,我可以在其他套接字中重新绑定相同的端口。

以上是关于如何在 QTcpServer 使用的套接字上设置 SO_REUSEADDR?的主要内容,如果未能解决你的问题,请参考以下文章

QT QTcpServer::incomingConnection(qintptr handle) 没有触发?

不能让 QTcpSocket/QTcpServer 一起工作

QTcpServer:使用自定义选项绑定套接字

QTcpServer::incomingConnection(qintptr socketDescriptor) 是不是可以连接指定的套接字?

23TCP通信

如何安全地删除 QT::QTcpSocket?