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

时间:2019-01-16 12:05:38

标签: c++ qt sockets

因此,这里有一个复杂的客户端-服务器应用程序,其中的一部分为监听和进一步的网络交互打开了端口。使用QTcpServer + QTcpSocket(详细信息将在下面说明)。

除Windows 8.1 Pro之外,应用程序的此“网络层”在任何地方都可以正常工作。只是不要问,为什么我们的客户端决定将其用作服务器...发生了错误的进程重新启动,该进程在特定端口上打开了TCP连接以进行侦听,导致该端口有时不适合随后的任何尝试将其绑定。看起来像是一种魔术,但其行为如下:

  • 进程已侦听了端口8554(“建立连接”)
  • 通过此套接字连接客户端时,进程崩溃或以某种方式终止
  • 进程重新启动,并尝试再次监听该端口。失败,显示“已使用”。
  • 我停止了服务器,并尝试通过netstat -an检查端口。它是免费的。
  • 我等待一段时间,尝试通过powershell检查端口,例如:

    $Listener = [System.Net.Sockets.TcpListener]8554
    $Listener.Start()
    

    否,这会导致相同的错误,例如“已在使用中”。

  • 我可以将套接字绑定到其他端口,powershell片段也可以在它们上使用。但是我们服务器的紧急重启也会“破坏”它们,情况是一样的。
  • 端口“断开”后,Windows重新启动是唯一的解决方法。
  • 绑定到“任何地址”,即0.0.0.0:8554的行为如上所述。绑定到精确的IP(例如10.11.12.123:8554更好),请尝试在“断开的端口”上绑定FileZilla时检查它。

现在是编码问题。为绑定提供精确的IP似乎是一个坏主意,至少在我们的体系结构中是如此,因此我决定在Windows上使用 SO_REUSEADDR 。但是,看来我必须在绑定/监听调用之前 设置此选项,这会导致大量QTcpServer自定义用法。请记住,该应用程序是跨平台的(如果要使用Qt方法以外的其他方法,则需要WinSock + sys / socket +一些#define。)。 Aaaand还有我最喜欢的QTcpServer自定义旧版本,看看:

QTcpServer2.h

#pragma once

#include <QTcpServer>
#include <QMutex>

class QTcpServer2 : public QTcpServer
{
  QMutex         mConnectionMutex;
  QList<qintptr> mSocketDescriptors;

private:
  virtual void incomingConnection(qintptr socketDescriptor) override;

public:
  bool TakeIncomingSocketDescription(qintptr& socketDescriptor);

public:
  QTcpServer2();
};

QTcpServer2.cpp

#include <QMutexLocker>

#include "QTcpServer2.h"

void QTcpServer2::incomingConnection(qintptr socketDescriptor)
{
  QMutexLocker lock(&mConnectionMutex);
  mSocketDescriptors.append(socketDescriptor);
}

bool QTcpServer2::TakeIncomingSocketDescription(qintptr& socketDescriptor)
{
  QMutexLocker lock(&mConnectionMutex);
  if (mSocketDescriptors.empty()) {
    return false;
  }
  socketDescriptor = mSocketDescriptors.takeFirst();
  return true;
}

QTcpServer2::QTcpServer2()
{ }

用法:

bool NetServer::DoInitConnection()
{
  mNetServer = QSharedPointer<QTcpServer2>(new QTcpServer2);
  if (!mNetServer->listen(QHostAddress::AnyIPv4, mPort)) {
    Log.Fatal(QString("Listen port fail (port: %1)").arg(mPort), true);
    return false;
  }
  return true;
}

叹息... 是的,我知道未决的连接机制,有关信号插槽的想法,但目前尚无代码。 TakeIncomingSocketDescription用于在某处传递套接字描述符。和线程访问,是的。无论如何,这部分代码需要重构,我非常需要您的忠告:在这里自定义套接字绑定的合适方法是什么?假设保留了子类,描述符列表也不是-很容易摆脱。

Qt Sources向我显示了以下内容:

/*! \internal
*/
void QTcpServerPrivate::configureCreatedSocket()
{
#if defined(Q_OS_UNIX)
    // Under Unix, we want to be able to bind to the port, even if a socket on
    // the same address-port is in TIME_WAIT. Under Windows this is possible
    // anyway -- furthermore, the meaning of reusable on Windows is different:
    // it means that you can use the same address-port for multiple listening
    // sockets.
    // Don't abort though if we can't set that option. For example the socks
    // engine doesn't support that option, but that shouldn't prevent us from
    // trying to bind/listen.
    socketEngine->setOption(QAbstractSocketEngine::AddressReusable, 1);
#endif
}

这正是我想要的,但是无法从Qt类接口访问这些内部。 QTcpServer隐式执行bind调用,所以我不能在那里传递QAbstractSocket::ReuseAddressHint

可能有一些整洁的解决方案,可能没有。我将不胜感激。

1 个答案:

答案 0 :(得分:0)

花点时间考虑所有可能的解决方案,尽管根本没有很多。

  1. Here我发现了一些看起来很像我的问题的东西。对代码进行了改编,只要在那里提供...好吧,只是从那个答案中获得了大部分=)
  2. @ G.M。的评论使我想到,在这种情况下,套接字api应该仅在标头中有所不同

手册告诉我 SO_LINGER 标志通常是由于体系结构不佳造成的(大多数情况下客户端应关闭连接,而不是服务器),经过几次实验后,此标志并没有太大用处就我而言。

绑定到确切的IP会导致多种复杂性,而不是解决方案恕我直言。

因此,在进行了少量修改之后,套接字侦听类变成了这样的东西:

QTcpServer2.h

#pragma once

#include <QTcpServer>
#include <QMutex>

class QTcpServer2 : public QTcpServer
{
  QMutex            mConnectionMutex;
  QList<qintptr>    mSocketDescriptors;
  const int         mPort;
  bool              mConnectionSuccessful;

private:
  /*override */virtual void incomingConnection(qintptr socketDescriptor) Q_DECL_OVERRIDE;

public:
  bool TakeIncomingSocketDescription(qintptr& socketDescriptor);

  bool isConnectionSuccessfull() const
  { return mConnectionSuccessful; }

public:
  QTcpServer2(int port);
};

QTcpServer.cpp

#include "QTcpServer2.h"

#include <QMutexLocker>

#ifdef Q_OS_WIN
    #include <Windows.h>
    #pragma comment(lib, "ws2_32.lib")
#endif

#ifdef Q_OS_LINUX
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netinet/tcp.h>
#endif

void QTcpServer2::incomingConnection(qintptr socketDescriptor)
{
    QMutexLocker lock(&mConnectionMutex);
    mSocketDescriptors.append(socketDescriptor);
}

bool QTcpServer2::TakeIncomingSocketDescription(qintptr& socketDescriptor)
{
    QMutexLocker lock(&mConnectionMutex);
    if (mSocketDescriptors.empty()) {
        return false;
    }
    socketDescriptor = mSocketDescriptors.takeFirst();
    return true;
}

QTcpServer2::QTcpServer2(int port)
    : QTcpServer()
    , mPort(port)
    , mConnectionSuccessful(false)
{
    // open server and listen on given port
    int sockfd = 0;
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));

    sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        qDedug() << "QTcpServer2: socket couldn't be opened successfully!";
        return; //RET
    }

#ifdef Q_OS_WIN
    // Not required in Linux, won't make any good 
    int flag = 1;
    if(::setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<char*>(&flag), sizeof(int)) < 0)
    {
        qDedug() << "QTcpServer2: Can't set SO_REUSEADDR";
        return; //RET
    }
#endif

    //set Address,IFace, Port...
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(static_cast<ushort>(mPort));


    if (::bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(sockaddr_in)) < 0)
    {
        qDedug() << "QTcpServer2: can't bind socket" ;
        return; //RET
    }

    if(::listen(sockfd, SOMAXCONN) < 0)
    {
        qDedug() << "QTcpServer2: can't listen on port";
        return; //RET
    }

    //forward our descriptor with SO_REUSEPORT to QTcpServer member
    setSocketDescriptor(sockfd);
    mConnectionSuccessful = true;
    qDedug() << "QTcpServer2: socket success =)";
}

只要“套接字的魔术问题”仅出现在Windows 8.1上,#define就使 SO_REUSEADDR 仅在Windows上使用。在Linux上,Qt已经自行设置了该标志(如问题所示),因此此小补丁仅改进了Windows的行为以匹配所需的行为,这在Linux上没有问题。

希望其他平台上不需要这样的东西。