まえがき
この記事は主に初心者を対象としています。 記述の目的は、ネットワークとソケットを最初に理解するための、ソケットの迅速かつ詳細な説明です。 あるときは似たようなものを探していましたが、詳細な例が必要でした。 標準的な例では、qtに付属するfortuneサーバー/クライアントは、非常に貧弱なソケット機能を示しています。
したがって、サーバーは次のことができます。
- 任意のアドレス、ポートを「聞く」
- 顧客を名前で認証する
- 一般的なプライベートサーバーメッセージを送信する
- ユーザーリストを送信
理解のために、これらはGooeyアプリケーションになります。
qtには、ソケットを操作するためのQTcpSocketクラスとQTcpServerクラスがあります。 シグナルとスロットを使用して、非ブロッキング(非同期モード)でそれらを操作できます。 これは、サーバーへの接続に顕著な時間がかかる場合、GUIはブロックせず、イベントの処理を続行し、接続(またはエラー)が発生すると、特定のスロットが呼び出されることを意味します(現在の場合、connected()信号に接続されています)。
お客様
簡単なものから始めましょう-クラスを継承しませんが、QTcpSocketを使用します。
//dialog.h class Dialog : public QDialog { private slots: // void onSokConnected(); void onSokDisconnected(); // readyRead , ( ) void onSokReadyRead(); void onSokDisplayError(QAbstractSocket::SocketError socketError); private: QTcpSocket *_sok; // quint16 _blockSize;// QString _name;// };
一般に、ソケットを操作するときは、データをバイトのセットとして見る必要があります。そうしないと、情報を部分的にしか表示できないという問題が発生する場合があります(完全なメッセージが到着せず、次のメッセージは前のメッセージとともに表示されます)。 これらのトラブルを回避するために、データストリーム(QDataStream)を使用して、最初の2バイトが現在のブロックのサイズであり、3バイト目がサーバーへのクライアントコマンド(またはサーバー応答)であり、残りはコマンドに応じたデータであるソケット間でブロックを転送します。 tcpプロトコルはすべてのパケットの配信を保証するので、処理する前にブロックのフルサイズを安全に待つことができます。
//dialog.cpp Dialog::Dialog(QWidget *parent) :QDialog(parent),ui(new Ui::Dialog) { // _sok = new QTcpSocket(this); // connect(_sok, SIGNAL(readyRead()), this, SLOT(onSokReadyRead())); connect(_sok, SIGNAL(connected()), this, SLOT(onSokConnected())); connect(_sok, SIGNAL(disconnected()), this, SLOT(onSokDisconnected())); connect(_sok, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(onSokDisplayError(QAbstractSocket::SocketError))); } // , , connectToHost() void, , onSokDisplayError void Dialog::on_pbConnect_clicked() { _sok->connectToHost(ui->leHost->text(), ui->sbPort->value()); } void Dialog::onSokConnected() { // QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); // 2 . MyClient , - // - out << (quint16)0 << (quint8)MyClient::comAutchReq << ui->leName->text(); _name = ui->leName->text(); // out.device()->seek(0); // out << (quint16)(block.size() - sizeof(quint16)); _sok->write(block); } void Dialog::onSokReadyRead() { // QDataStream in(_sok); // 2 if (_blockSize == 0) { // 2 2 if (_sok->bytesAvailable() < (int)sizeof(quint16)) return; // (2 ) in >> _blockSize; } // if (_sok->bytesAvailable() < _blockSize) return; else // _blockSize = 0; //3 - quint8 command; in >> command; switch (command) { // , , MyClient::comUsersOnline case MyClient::comUsersOnline: { QString users; in >> users; if (users == "") return; // , ( QStringList) QStringList l = users.split(","); // ui->lwUsers->addItems(l); } break; // case MyClient::comPublicServerMessage: { // QString message; in >> message; AddToLog("[PublicServerMessage]: "+message, Qt::red); } ... } }
それはすべてクライアントにあります-主なポイントが説明され、残りは非常に簡単です。
サーバー
サーバーではすべてがより複雑になります。GUI(ダイアログ)、サーバー(myserver)、クライアント(myclient)を分離します。 ソケットに慣れていない人は、どのクライアントがサーバーにいるかを理解できないかもしれません。 したがって、サーバーにサーバーにソケットを接続すると、原則として、「クライアント」ソケットが作成され、これが配列に追加されます(結合配列を使用することをお勧めしますが、簡単にするためにQListを使用します)。
//dialog.cpp Dialog::Dialog(QWidget *parent) :QDialog(parent), ui(new Ui::Dialog) { // . - parent, - , myclient _serv = new MyServer(this, this); // connect(this, SIGNAL(messageFromGui(QString,QStringList)), _serv, SLOT(onMessageFromGui(QString,QStringList))); ... // 127.0.0.1:1234 if (_serv->doStartServer(QHostAddress::LocalHost, 1234)) {...} else {...} }
QTcpServerからクラスを継承します。これは、着信接続がインターセプトされる仮想関数incomingConnectionをオーバーライドするために必要です(入力パラメーターはソケット記述子です)
//myserver.h class MyServer : public QTcpServer { public: bool doStartServer(QHostAddress addr, qint16 port); void doSendToAllUserJoin(QString name); // void doSendToAllUserLeft(QString name); void doSendToAllMessage(QString message, QString fromUsername); // void doSendToAllServerMessage(QString message);// void doSendServerMessageToUsers(QString message, const QStringList &users); // void doSendMessageToUsers(QString message, const QStringList &users, QString fromUsername); QStringList getUsersOnline() const; // bool isNameValid(QString name) const; // bool isNameUsed(QString name) const; // protected: void incomingConnection(int handle); private: QList<MyClient *> _clients; // QWidget *_widget; // myclient }; //myserver.cpp void MyServer::incomingConnection(int handle) { // , ( ), - parent MyClient *client = new MyClient(handle, this, this); // , if (_widget != 0) { connect(client, SIGNAL(addUserToGui(QString)), _widget, SLOT(onAddUserToGui(QString))); connect(client, SIGNAL(removeUserFromGui(QString)), _widget, SLOT(onRemoveUserFromGui(QString))); ... } _clients.append(client); } /* , _clients, , */ void MyServer::doSendServerMessageToUsers(QString message, const QStringList &users) { // QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out << (quint16)0 << MyClient::comPrivateServerMessage << message; out.device()->seek(0); out << (quint16)(block.size() - sizeof(quint16)); // ( , users ) for (int j = 0; j < _clients.length(); ++j) if (users.contains(_clients.at(j)->getName())) _clients.at(j)->_sok->write(block); }
クライアントクラスの場合、おそらくインターフェイスのみを説明する価値があります。ここでは、類推によってすべてが明確になっています。 MyClientはQTcpSocketから継承する必要はありません。別の方法で行うことができます。
//myclient.h class MyClient : public QObject { // MyServer _sok friend class MyServer; public: static const QString constNameUnknown; static const quint8 comAutchReq = 1; static const quint8 comUsersOnline = 2; ... static const quint8 comErrNameUsed = 202; void setName(QString name) {_name = name;} QString getName() const {return _name;} bool getAutched() const {return _isAutched;} void doSendCommand(quint8 comm) const; void doSendUsersOnline() const; signals: // void addUserToGui(QString name); void removeUserFromGui(QString name); void messageToGui(QString message, QString from, const QStringList &users); // QList void removeUser(MyClient *client); // private slots: void onConnect(); void onDisconnect(); void onReadyRead(); void onError(QAbstractSocket::SocketError socketError) const; private: QTcpSocket *_sok; // MyServer *_serv; // quint16 _blockSize; // QString _name; // bool _isAutched; // };
2017年更新 :
悲しいかな、ソースコードはすでに永遠に失われています
2018年更新 :
ご存知のように、これまでインターネットにアップロードされたすべてのものは永遠にそこに残ります。 vladpower ソースのおかげで再び利用可能です