Qt上のマルチスレッドSOCKS 4サーバー

ネットワークアプリケーションのプログラミングに関する質問は、QtブランチのRunetフォーラムに時々表示されます。 これらの人々を苦しめる問題の1つは、サーバーを編成するアプローチです。 通常、次の3つのアプローチがリストされます。



固定数のワーカースレッドについて独自のイベントループを使用する場合、彼らは例を求めます。 以下は、それぞれが独自のイベント処理ループを持つスレッドのプールを持つサーバーの例です。



俳優:



これらの最も単純なTrinity Workerは、 QObjectを継承し、クライアントを作成する関数と、スレッドを 「正しく」 使用するための関数を1つだけ実装します。

class Worker: public QObject { Q_OBJECT public: Q_INVOKABLE void addClient(qintptr socketDescriptor); };
      
      





 void Worker::addClient(qintptr socketDescriptor) { new Client(socketDescriptor, this); }
      
      





サーバーは単純です:

 class Server: public QTcpServer { Q_OBJECT public: Server(size_t threads = 4, QObject * parent = nullptr); ~Server(); protected: virtual void incomingConnection(qintptr socketDescriptor); private: void initThreads(); private: size_t m_threadCount; QVector<QThread*> m_threads; QVector<Worker*> m_workers; size_t m_rrcounter; };
      
      





 Server::Server(size_t threads, QObject * parent) : QTcpServer(parent), m_threadCount(threads), m_rrcounter(0) { initThreads(); } Server::~Server() { for(QThread* thread: m_threads) { thread->quit(); thread->wait(); } } void Server::initThreads() { for (size_t i = 0; i < m_threadCount; ++i) { QThread* thread = new QThread(this); Worker* worker = new Worker(); worker->moveToThread(thread); connect(thread, &QThread::finished, worker, &QObject::deleteLater); m_threads.push_back(thread); m_workers.push_back(worker); thread->start(); } } void Server::incomingConnection(qintptr socketDescriptor) { Worker* worker = m_workers[m_rrcounter % m_threadCount]; ++m_rrcounter; QMetaObject::invokeMethod(worker, "addClient", Qt::QueuedConnection, Q_ARG(qintptr, socketDescriptor)); }
      
      





スレッドを作成し、デザイナーでワーカーを作成し、ワーカーをスレッドに移動します。 彼はそれぞれの新しい接続をワーカーに転送します。 彼はワーカーを「郵便で」、つまりラウンドロビンで選択します。



SOCKS 4は非常にシンプルなプロトコルで、必要なものは次のとおりです。

  1. IPアドレス、ポート番号を読み取ります。
  2. 「世界」との接続を確立します。
  3. 要求が確認されたというメッセージをクライアントに送信します。
  4. 誰かが接続を閉じるまで、あるソケットから別のソケットにデータを転送します。


 class Client: public QObject { Q_OBJECT public: Client(qintptr socketDescriptor, QObject* parent = 0); public slots: void onRequest(); void client2world(); void world2client(); void sendSocksAnsver(); void onClientDisconnected(); void onWorldDisconnected(); private: void done(); private: QTcpSocket m_client; QTcpSocket m_world; };
      
      





 namespace { #pragma pack(push, 1) struct socks4request { uint8_t version; uint8_t command; uint16_t port; uint32_t address; uint8_t end; }; struct socks4ansver { uint8_t empty = 0; uint8_t status; uint16_t field1 = 0; uint32_t field2 = 0; }; #pragma pack(pop) enum SocksStatus { Granted = 0x5a, Failed = 0x5b, Failed_no_identd = 0x5c, Failed_bad_user_id = 0x5d }; } Client::Client(qintptr socketDescriptor, QObject* parent) : QObject(parent) { m_client.setSocketDescriptor(socketDescriptor); connect(&m_client, &QTcpSocket::readyRead, this, &Client::onRequest); connect(&m_client,&QTcpSocket::disconnected, this, &Client::onClientDisconnected); connect(&m_world, &QTcpSocket::connected, this, &Client::sendSocksAnsver); connect(&m_world, &QTcpSocket::readyRead, this, &Client::world2client); connect(&m_world,&QTcpSocket::disconnected, this, &Client::onWorldDisconnected); } void Client::onRequest() { QByteArray request = m_client.readAll(); socks4request* header = reinterpret_cast<socks4request*>(request.data()); #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN const QHostAddress address(qFromBigEndian(header->address)); #else const QHostAddress address(header->address); #endif #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN const uint16_t port = qFromBigEndian(header->port); #else const uint16_t port = header->port; #endif //qDebug()<<"connection:"<<address<<"port:"<<port; m_world.connectToHost(address, port); disconnect(&m_client, &QTcpSocket::readyRead, this, &Client::onRequest); connect(&m_client, &QTcpSocket::readyRead, this, &Client::client2world); } void Client::sendSocksAnsver() { socks4ansver ans; ans.status = Granted; m_client.write(reinterpret_cast<char*>(&ans), sizeof(ans)); m_client.flush(); } void Client::client2world() { m_world.write(m_client.readAll()); } void Client::world2client() { m_client.write(m_world.readAll()); } void Client::onClientDisconnected() { m_world.flush(); done(); } void Client::onWorldDisconnected() { m_client.flush(); done(); } void Client::done() { m_client.close(); m_world.close(); deleteLater(); }
      
      





エポールとQt


叫びは雷のようなものです。

-人々にラム酒を与える

いずれかの必要性

人々はラム酒を飲む!





前のコードをコンパイルしてstrace -fで実行すると、ポーリング呼び出しが表示されます。 きっと彼の重い「fi」と言う人がいるでしょう、彼らはepollで「まあ、ついにロケット」になると言います。



Qtには、独自のイベントディスパッチャーを定義できるQAbstractEventDispatcherクラスがあります。 当然、異なるバックエンドを持つディスパッチャを作成してレイアウトした優秀な人々がいました。 以下にそれらの短いリストを示します。



main.cppでディスパッチャを使用する場合、規定

 QCoreApplication::setEventDispatcher(new QEventDispatcherEpoll); QCoreApplication app(argc, argv)
      
      





サーバー上のinitThreadsメソッドは次のようになります。

 void Server::initThreads() { for (size_t i = 0; i < m_threadCount; ++i) { QThread* thread = new QThread(this); thread->setEventDispatcher(new QEventDispatcherEpoll); Worker* worker = new Worker(); worker->moveToThread(thread); connect(thread, &QThread::finished, worker, &QObject::deleteLater); m_threads.push_back(thread); m_workers.push_back(worker); thread->start(); } }
      
      





そして、straceを再度実行すると、 epoll_プレフィックスが付いた大事な関数呼び出しが表示されます。



結論


結論は純粋に実用的です。



あなたがアプリケーションプログラマであり、Buninによる「ビッグ」データまたは高負荷のカテゴリのタスクがない場合は、必要なものとその方法を記述してください。 アプリケーションプログラマのタスクは、特定の品質の製品を生産し、特定の量のリソースを消費することです。 それ以外の場合、epollソケットでは不十分です。



PS



ソースコードはGitHubで入手できます



All Articles