Multiple Clients on QTcpServer



  • Hello,

    I have yet another issue with QTcpServer and sockets. The problem is as follows:

    My server class is inherited from QObject and I have an object of QTcpServer inside the class.
    My clients connect to the server, each socket from client connection is stored in a QList.
    For communication between clients and server I have established a kind of ping pong request: Client: send QString("I have new datapack_1") Server: send QString("OK I'm ready for datapack_1") Client: send QByteArray(stuff).

    Everything is fine when just one client is connected. But how can I figure out, which socket is just used for connection? I cannot use sender from QTcpServer, because it is protected and my class is not derived from QTcpServer.

    Any clue out there?

    remark: only the requesting client shall be updated. Not every connected client.

    Regards
    Vater


  • Moderators

    Are your client objects on the server side already classes derived from or wrapping QTcpSocket?
    If so, you need to forward the signal received from socket, presumably readyRead(), with an indication which socket has triggered it. The simplest thing is to use the socket's address.



  • Init QTcpSocketList
    @
    typedef QTcpSocketList QList<QTcpSocket*>
    @

    Now add list:
    @
    private:
    QTcpSocketList list;
    @

    And on signal - newConnection():
    @
    void MyTcpServer::slotNewConnection()
    {
    list.push_back( nextPendingConnection() );
    }
    @

    It will add all connection sockets to list.



  • Not sure, if I got your points. Maybe it helps if I post a few pieces of code.

    This is the header of the Server class. It is derived from QObject to use connect. Basic idea was to include UDP and TCP in one class. You can see, that tcp_socket is a QList.

    @
    namespace nsServer{

    class Server : public QObject{
    Q_OBJECT
    private:

    bool initialized;

    QTcpServer *tcp_server;
    QList<QTcpSocket *> tcp_socket;
    QNetworkSession *network_session;
    int n_sockets;

    bool new_client_connected;

    private slots:
    void NewConnection(void);
    void DisconnectSocket(void);
    void Receive(void);
    void NetworkSessionError(QNetworkSession::SessionError _error);
    @

    The signal newConnection is triggering the following function in which each new connection/socket is stored in the QList:
    @
    //------------------------------------------------------------------------------
    void Server::NewConnection(void){
    //------------------------------------------------------------------------------

    if (!initialized) return;

    while (tcp_server->hasPendingConnections()){
    QTcpSocket *socket = tcp_server->nextPendingConnection();

      connect(socket, SIGNAL(readyRead()),    this, SLOT(Receive()));
      connect(socket, SIGNAL(disconnected()), this, SLOT(DisconnectSocket()));
      tcp_socket.append(socket);
      n_sockets++;
    

    }

    has_connection = true;

    //store connection in client_array
    QString s = (tcp_socket.at(n_sockets-1)->peerAddress().toString() + ":" +
    QString::number(tcp_socket.at(n_sockets-1)->peerPort()));
    last_client = s.toStdString();

    }
    @

    Now in my send function, called from outside server-class, I would like to identify the last connected socket triggered. I think it can be done by identifying the sender-Object from tcp_server and casting the QObject to a QTcpSocket pointer. But this is only working correctly if the class Server (i.e. this pointer) is derived from TcpServer. That's my problem.

    @
    //------------------------------------------------------------------------------
    void Server::Send(QString _string, bool _to_all){
    //------------------------------------------------------------------------------

    if (!initialized) return;

    has_received = false;

    QByteArray byte_array;
    QDataStream out(&byte_array, QIODevice::WriteOnly);
    out.setVersion(QDataStream::Qt_4_7);

    //reserve memory of first bytes
    out << (quint16)0;
    //write string into stream
    out << _string.toAscii();
    //go back to beginning of stream
    out.device()->seek(0);
    //write length of string into first bytes
    out << (quint16)(byte_array.size() - sizeof(quint16));

    if (_to_all){
    foreach(QTcpSocket *socket, tcp_socket){
    socket->write(byte_array);
    // socket->disconnectFromHost();
    }
    }
    else {
    QTcpSocket *socket = qobject_cast<QTcpSocket *>(sender());
    if (!socket) return;
    socket->write(byte_array);
    }

    received_string.clear();
    received_byte_array.clear();

    int max_char;
    (_string.size() < 10)? max_char = _string.size():10;

    last_message = "String " + _string.left(max_char).toStdString() + "... sent to client.";
    sent_message = last_message;

    }
    @


  • Moderators

    @ connect(socket, SIGNAL(readyRead()), this, SLOT(Receive()));@

    All sockets are connected to the same slot. How do you determine from which socket to read?
    Are you searching every time the list to find the socket with the data received?
    However, if you have found the socket with the request, you know already the socket you have to send the answer to.


  • Moderators

    Instead of using QTcpSocket directly you could use:
    @
    class TcpSocket: public QObject
    {
    Q_OBJECT

        QTcpSocket * Socket;
    
    public:
    
        TcpSocket (QTcpSocket *);
        virtual ~TcpSocket ();
    
    private slots:
    
        void sltReadyRead ();
        void connected ();
        void disconnected ();
    
    signals:
    
        void dataReady (TcpSocket * sckt );
    

    };
    @
    and the implementation
    @
    TcpSocket :: TcpSocket (QTcpSocket * socket)
    : Socket ( socket )
    {
    connect (Socket, SIGNAL (readyRead()), this, SLOT (sltReadyRead()));
    }

    void TcpSocket :: sltReadyRead ()
    {
    emit dataReady (this);
    }
    @

    You need to use this class instead of QTcpSocket in the QList and modify the connect and the receive slot.
    Your Receive slot will know then from the data was received and where to send the answer.



  • It was quite a brain teaser. I solved it now in a different way. I have deleted the handshake between client and server and replaced it with an initial QString in each byte array, which is read and used for identification of incoming byte format. In the Receive function I am identifying the used socket by use of "bytesAvailable"

    @
    //------------------------------------------------------------------------------
    void Server::ReceivePackage(void){
    //------------------------------------------------------------------------------

    if (!initialized) return;

    int i_socket = -1;
    for (int i=0;i<(int)connected_sockets.size();++i){
    if (connected_sockets.at(i)->bytesAvailable() != 0){
    i_socket = i;
    break;
    }
    }
    if (i_socket == -1) return;

    quint16 byte_array_size = 0;
    QDataStream in(connected_sockets.at(i_socket));
    in.setVersion(QDataStream::Qt_4_7);

    //Read number of bytes to read
    in >> byte_array_size;
    //check if available bytes match
    if (connected_sockets.at(i_socket)->bytesAvailable() < byte_array_size){
    PRINT("Server","Error in Receive - Number of bytes manipulated!");
    return;
    }

    //Read identifyer and pull it to QString
    in >> received_string;

    //Read rest of byte array
    received_byte_array = connected_sockets.at(i_socket)->readAll();

    has_received = true;

    last_message = (received_string + " received.").toStdString();

    }
    @

    The sending function is sending always to every socket and the clients choose by themselves by anaylsing the identifying string if it is usefull or not.

    @
    //------------------------------------------------------------------------------
    void Server::SendPackage(QByteArray _byte_array){
    //------------------------------------------------------------------------------

    if (!initialized) return;

    has_received = false;

    foreach(QTcpSocket *socket, connected_sockets){
    socket->write(_byte_array);
    }

    received_string.clear();
    received_byte_array.clear();

    last_message = "Byte array sent to all sockets.";
    sent_message = last_message;

    }
    @

    My uncertainty is still the disconnect signal, which I've solved like mentioned in a help blog from qt by using qobject_cast. I do not know if sender() from QObject is returning the same pointer than QTcpServer. But: It works.

    @
    //------------------------------------------------------------------------------
    void Server::DisconnectSocket(void){
    //------------------------------------------------------------------------------

    QTcpSocket *client = qobject_cast<QTcpSocket *>(sender());

    if (!client)
    return;

    connected_sockets.removeAll(client);
    client->deleteLater();
    n_sockets--;
    }
    @

    readyRead signal pointing to NewConnection is solved with the while loop.
    @
    //------------------------------------------------------------------------------
    void Server::NewConnection(void){
    //------------------------------------------------------------------------------

    if (!initialized) return;

    while (tcp_server->hasPendingConnections()){
    QTcpSocket *socket = tcp_server->nextPendingConnection();

      connect(socket, SIGNAL(readyRead()),    this, SLOT(ReceivePackage()));
      connect(socket, SIGNAL(disconnected()), this, SLOT(DisconnectSocket()));
      connected_sockets.append(socket);
      n_sockets++;
    

    }

    new_client_connected = true;

    //store connection in client_array
    QString s = (connected_sockets.at(n_sockets-1)->peerAddress().toString() + ":" +
    QString::number(connected_sockets.at(n_sockets-1)->peerPort()));
    last_client = s.toStdString();

    }
    @

    It is still a bit buggy. However it is much more efficient using the byte array with identifying string instead of handshaking messages. If you think, there is still something totally wrong, I'm happy to hear your advise.

    Thanks
    Vater


  • Moderators

    It is depending on the number of of clients you have to serve. However, apparently, the number of clients is not high. Otherwise sending to all clients creates quite some overhead.
    I never used sender(), so I cannot comment. For disconnecting you could go through your list and check each socket for lost connection and remove, when necessary.
    However, when you are earmarking with the address or other means the signals as in the example I have provided in the post, you can get around these things.


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.