How to write Client-Server application and implement simple protocol in Qt

11,958

Networking with Qt is not that difficult.

Communication between two points is handled by a single class; in the case of TCP/IP, that would be the QTcpSocket class. Both the client and server will communicate with a QTcpSocket object.

The only difference with the server is that you start with a QTcpServer object and call listen() to await a connection...

QTcpServer* m_pTcpServer = new QTcpServer

//create the address that the server will listen on
QHostAddress addr(QHostAddress::LocalHost); // assuming local host (127.0.0.1)

// start listening
bool bListening = m_pServer->listen(addr, _PORT); //_PORT defined as whatever port you want to use

When the server receives a connection from a client QTcpSocket, it will notify you with a newConnection signal, so assuming you've made a connection to a socket in your own class to receive that signal, we can get the server QTcpSocket object to communicate with the client...

QTcpSocket* pServerSocket = m_pServer->nextPendingConnection();

The server will receive a QTcpSocket object for each connection made. The server socket can now be used to send data to a client socket, using the a write method...

pServerSocket->write("Hello!");

When a socket (either client or server) receives data, it emits the readyRead signal. So, assuming you have made a connection to the readyRead signal for the socket, a slot function can retrieve the data...

QString msg = pSocket->readAll();

The other code you'll need is to handle the connect, disconnect and error signals, which you should connect relevant slots for receiving these notifications.

Ensure you only send data when you know the connection has been made. Normally, I would have the server receive a connection and send a 'hello' message back to the client. Once the client receives the message, it knows it can send to the server.

When either side disconnects, the remaining side will receive the disconnect signal and can act appropriately.

As for the client, it will just have one QTcpSocket object and after calling connectToHost, you will either receive a connected signal if the connection was succesfully made, or the error signal.

Finally, you can use QLocalServer and QLocalSocket in the same way, if you're just trying to communicate between processes on the same machine.

Share:
11,958
Marko
Author by

Marko

Updated on June 04, 2022

Comments

  • Marko
    Marko almost 2 years

    Maybe this is stupid question, actually it's appeal, or Qt is just to complicated for me. Here's the thing: I'm used to java when writing client-server application, and it's very simple. I would like to do same things in C++ (I'm very familiar with C++ itself), and I choose to learn Qt. I tried to write some applications in qt, but with partial success.

    First thing that bothers me is signals and slots. I know how to use them in GUI programming but it confuses me with networking. And there's problem with blocking. When I call BufferedReader's readLine() method in java it blocks until it receives line from socket connection. In Qt I must make sure that there is line available every time, and handle it when there isn't one.

    And when I connect QSocket's error signal to some of my custom slots, the signal is emitted when server sends last line and closes the connection, and in client's slot/function that reads I never read that last line. That are some problems I faced so far.

    Slots and checking if there is data available makes me confused when I had to implements even the simplest protocols.

    Important part:

    I tried to find good example on the internet, but problem is that all examples are to complicated an big. Is there anyone how can show me how to write simple client-server application. Server accepts only one client. Client sends textual line containing command. If command is "ADD" or "SUB", server sends "SUP" indicating that command is supported. Otherwise it sends "UNS" and closes the connection. If client receives "SUP" it sends to more lines containing numbers to be subtracted or added. Server responds with result and closes connection.

    I know that C++ requires more coding, but in Java this would take only 5 minutes, so it shouldn't take to long to write it in C++ either.

    I'm sure this example would be very valuable to anyone who wants to learn networking in Qt.

    edit: This is my try to make the application (described above): here is the server part:

     #ifndef TASK_H
    #define TASK_H
    
    #include <QObject>
    #include <QTcpServer>
    
    class Task : public QObject
    {
        Q_OBJECT
    public:
        Task(QObject *parent = 0) : QObject(parent) {}
    
    
    
    public slots:
        void run();
        void on_newConnection();
        void on_error(QAbstractSocket::SocketError);
    
    signals:
        void finished();
    
     private:
        QTcpServer server;
    };
    
    
    #endif // TASK_H
    
    
    
    void Task::run()
    {
        connect(&server,SIGNAL(newConnection()),this,SLOT(on_newConnection()));
        connect(&server,SIGNAL(acceptError(QAbstractSocket::SocketError)),this,SLOT(on_error(QAbstractSocket::SocketError)));
        if(server.listen(QHostAddress::LocalHost, 9000)){
            qDebug() << "listening";
        }else{
            qDebug() << "cannot listen";
            qDebug() << server.errorString();
        }
    
    }
    
        void Task::on_newConnection(){
            std::cout << "handeling new connection...\n";
            QTcpSocket* socket = server.nextPendingConnection();
            QTextStream tstream(socket);
    
    
            while(!socket->canReadLine()){
                socket->waitForReadyRead((-1));
            }
    
            QString operation = tstream.readLine();
            qDebug() << "dbg:" << operation;
            if(operation != "ADD" && operation != "SUB"){
                tstream << "UNS\n";
                tstream.flush();
                socket->disconnect();
                return;
            }
            tstream << "SUP\n";
            tstream.flush();
            double op1,op2;
            while(!socket->canReadLine()){
                socket->waitForReadyRead((-1));
            }
            op1 = socket->readLine().trimmed().toDouble();
            qDebug() << "op1:" << op1;
            while(!socket->canReadLine()){
                socket->waitForReadyRead(-1);
            }
            op2 = socket->readLine().trimmed().toDouble();
            qDebug() << "op2:" << op2;
            double r;
            if(operation == "ADD"){
                r = op1 + op2;
            }else{
                r = op1 - op2;
            }
    
            tstream << r << "\n";
            tstream.flush();
            qDebug() << "result is: " << r;
            socket->disconnect();
    
        }
    
        void Task::on_error(QAbstractSocket::SocketError ){
            qDebug() << "server error";
            server.close();
        }
    

    This is client side (header is similar to server's so I wont post it):

    void Task::run()
    {
    
    
        QTcpSocket socket;
        std::string temp;
        socket.connectToHost(QHostAddress::LocalHost,9000);
    
        if(socket.waitForConnected(-1))
            qDebug() << "connected";
        else {
            qDebug() << "cannot connect";
            return;
        }
    
        QTextStream tstream(&socket);
    
        QString op;
        std::cout << "operation: ";
        std::cin >> temp;
        op = temp.c_str();
        tstream << op << "\n";
        tstream.flush();
        qDebug() << "dbg:" << op << "\n";
    
        while(!socket.canReadLine()){
            socket.waitForReadyRead(-1);
        }
        QString response = tstream.readLine();
        qDebug() << "dbg:" << response;
        if(response == "SUP"){
    
            std::cout << "operand 1: ";
            std::cin >> temp;
            op = temp.c_str();
            tstream << op + "\n";
    
            std::cout << "operand 2: ";
            std::cin >> temp;
            op = temp.c_str();
            tstream << op + "\n";
    
            tstream.flush();
    
            while(!socket.canReadLine()){
                socket.waitForReadyRead(-1);
            }
            QString result = tstream.readLine();
    
            std::cout << qPrintable("result is: " + result);
    
        }else if(response == "UNS"){
            std::cout << "unsupported operatoion.";
        }else{
            std::cout << "unknown error.";
        }
        emit finished();
    }
    
    1. What I could do better?
    2. What are some good practices in similar situations?
    3. When using blocking (not signal/slot mechanism), what is the best way to handle event when other side closes the connection?
    4. Can someone rewrite this to make it look more professional (I just what to see how it supposed to look like, because I think that my solution is far from perfect) ?
    5. Can someone rewrite this using signals and slots?

    Thanks you. Sorry for my English, and probably stupidity :)