How to write Client-Server applications and implements simple protocol



  • 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.


  • Moderators

    Did you see the following example
    "Fortune Server":http://qt-project.org/doc/qt-5.0/qtnetwork/fortuneserver.html
    "Fortune Client":http://qt-project.org/doc/qt-5.0/qtnetwork/fortuneclient.html

    These are quite simple and easy.



  • Yes, I looked it for a second and still decided to ask someone to make the above example.
    But I acctually returned to Fortune Client/Server example and I'm looking at it right now. Maybe it'll help me figure it out.
    It would still be nice if some one could make this example. It's super simple. It does not have to be GUI program, nor multithreaded. For beginers, including GUI and threads where it's not necessary, makes things a bit more complicated because it distracts reader from main problem.



  • Hi. I think the better and easy solution to have a perfect and easy client - server application is to use Delphi - Fpc and Indy libraries. Indy are a group of components that can do everything (ftp, http, smtp, ntp ...).
    If you want you can do the same with Qt also. But you have to write all yourself.

    In my case in the past I have written a client - server component (200 rows more less) for my Qt applications. Is very easy to use, server and client side. If you want you can study QTcpSocket object and try yourself.

    If you have no time to spend I can sell to you my source code (But I think is better if you write it yourself. In many cases the client - server feature of an application is very important.).

    Regards.



  • This is a small example for a client that does something similar to what you want to do. It's taken from a discussion in "this forum thread":http://qt-project.org/forums/viewthread/26873/ . Hope it helps.

    @
    void MainWindow::Connect()
    {
    socket = new QTcpSocket(this);
    connect(socket, SIGNAL(connected()), this, SLOT(clientConnected()));
    connect(socket, SIGNAL(disconnected()), this, SLOT(clientDisconnected()));
    connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(handleSocketError(QAbstractSocket::SocketError)));
    socket->connectToHost(*serverAddr,*serverPort);
    }

    void MainWindow::clientConnected()
    {
    //you are now connected, continue with your execution
       connect(socket, SIGNAL(bytesWritten(qint64), this, SLOT(handleBytesWritten(qint64))));
       connect(socket, SIGNAL(readyRead(), this, SLOT(handleReadyRead())));
       socket->write("REG\n");
       socket->flush();
    }
     
    void MainWindow::handleBytesWritten(qint64 a_bytesSize)
    {
    // handle the bytesWritten signal if you need to
    }
     
    void MainWindow::handleReadyRead()
    {
    // handle the readyRead signal
    // for example
       QString tempString = socket->readAll();
       if(tempString == "ACK" && !m_bUserNameSend )
       {
           socket->write(userName->toUtf8());
           socket->flush();
           // use a member variable to remember that you send the username if you need to
           m_bUserNameSend = true;
       }
       else
       {
          if(tempString == "ACK" && m_bUserNameSend)
          {
          // DO THE NEXT STEP.
          }
       }
    }
     
    void MainWindow::clientDisconnected()
    {
    // your client disconnected, handle this if you want to
    }
     
    void MainWindow::handleSocketError(QAbstractSocket::SocketError a_error)
    {
    // something went wrong, check the SocketError and handle the error accordingly
    }
    

    @



  • This is my try to make the application: 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();
    }
    

    @

    And this is client part (header is similar to server's so I'll post only implementation):

    @
    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();
    

    }
    @

    I still have no idea how could I do same thing using QTcpSocket's signals instead of wait* functions. But I'm happy to accomplish this.
    Can someone tell me, how this could be done better?
    For me it seems pretty complicated. To many code for checking if data had arrived, and problem with QTextStream.
    [ I think this is important: ]
    QTextStream is nicer to work with that pure QTcpSocket, especially when writing data. But it does not work well with QTcpSocket, because it does not have canReadLine() function, so I use QTcpSocket's one. But as you can see, I send two lines that should contain numbers. After I read one line with QTextStream it takes other line from QTcpSockets buffer too, leaving it empty. So next time I check canReadLine(), it returns false. While actually there is unreaded line in QTextStreams buffer. Result is - it waits for new data infinitely. But I still use QTextStream for sending data, because it's easier.

    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 :)


Log in to reply
 

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