简体   繁体   中英

Qt TCP client/server Chat Application. How to send a private message

I have a simple tcp client/server chat application that looks like this: 在此处输入图片说明 And here is my client source code:

#include "MainWindow.h"

// We'll need some regular expression magic in this code:
#include <QRegExp>

// This is our MainWindow constructor (you C++ n00b)
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
{
    // When using Designer, you should always call setupUi(this)
    // in your constructor. This creates and lays out all the widgets
    // on the MainWindow that you setup in Designer.
    setupUi(this);

    // Make sure that we are showing the login page when we startup:
    stackedWidget->setCurrentWidget(loginPage);

    // Instantiate our socket (but don't actually connect to anything
    // yet until the user clicks the loginButton:
    socket = new QTcpSocket(this);

    // This is how we tell Qt to call our readyRead() and connected()
    // functions when the socket has text ready to be read, and is done
    // connecting to the server (respectively):
    connect(socket, SIGNAL(readyRead()), this, SLOT(readyRead()));
    connect(socket, SIGNAL(connected()), this, SLOT(connected()));
}

// This gets called when the loginButton gets clicked:
// We didn't have to use connect() to set this up because
// Qt recognizes the name of this function and knows to set
// up the signal/slot connection for us.
void MainWindow::on_loginButton_clicked()
{
    // Start connecting to the chat server (on port 4200).
    // This returns immediately and then works on connecting
    // to the server in the background. When it's done, we'll
    // get a connected() function call (below). If it fails,
    // we won't get any error message because we didn't connect()
    // to the error() signal from this socket.
    socket->connectToHost(serverLineEdit->text(), 4200);
}

// This gets called when the user clicks the sayButton (next to where
// they type text to send to the chat room):
void MainWindow::on_sayButton_clicked()
{
    // What did they want to say (minus white space around the string):
    QString message = sayLineEdit->text().trimmed();

    // Only send the text to the chat server if it's not empty:
    if(!message.isEmpty())
    {
        socket->write(QString(message + "\n").toUtf8());
    }

    // Clear out the input box so they can type something else:
    sayLineEdit->clear();

    // Put the focus back into the input box so they can type again:
    sayLineEdit->setFocus();
}

// This function gets called whenever the chat server has sent us some text:
void MainWindow::readyRead()
{
    // We'll loop over every (complete) line of text that the server has sent us:
    while(socket->canReadLine())
    {
        // Here's the line the of text the server sent us (we use UTF-8 so
        // that non-English speakers can chat in their native language)
        QString line = QString::fromUtf8(socket->readLine()).trimmed();

        // These two regular expressions describe the kinds of messages
        // the server can send us:

        //  Normal messges look like this: "username:The message"
        QRegExp messageRegex("^([^:]+):(.*)$");

        // Any message that starts with "/users:" is the server sending us a
        // list of users so we can show that list in our GUI:
        QRegExp usersRegex("^/users:(.*)$");

        // Is this a users message:
        if(usersRegex.indexIn(line) != -1)
        {
            // If so, udpate our users list on the right:
            QStringList users = usersRegex.cap(1).split(",");
            userListWidget->clear();
            foreach(QString user, users)
                new QListWidgetItem(QPixmap(":/user.png"), user, userListWidget);
        }
        // Is this a normal chat message:
        else if(messageRegex.indexIn(line) != -1)
        {
            // If so, append this message to our chat box:
            QString user = messageRegex.cap(1);
            QString message = messageRegex.cap(2);

            roomTextEdit->append("<b>" + user + "</b>: " + message);
        }
    }
}
void MainWindow::onListWidgetItemClicked(const QModelIndex &index)
{

}

// This function gets called when our socket has successfully connected to the chat
// server. (see the connect() call in the MainWindow constructor).
void MainWindow::connected()
{
    // Flip over to the chat page:
    stackedWidget->setCurrentWidget(chatPage);

    // And send our username to the chat server.
    socket->write(QString("/me:" + userLineEdit->text() + "\n").toUtf8());
}

It works fine, but if one the users writes a message it appears on the textedit and everybody who is connected can see it. What I want to do is to be able to send a private message, to a particular user which I select from the users listwidget, something similar to Skype let's say.

For example if I'm user 1 and I select user 2 from the listwidget, I want to send him a message which user 3 won't be able to see.

I'm sorry for my bad english and for this stupid question but I can't figure out how to approach this problem. I would gladly accept any suggestions.

Judging by the implementation of on_sayButton_clicked and readyRead , your protocol only supports broadcast messages, as everything sent to the server is unconditionally sent to all currently connected users.

You will have to introduce a separate message type in the protocol in order to instruct the server to send the message only to the given user. It looks like you're distinguishing ordinary messages from control messages via testing for a specific token at the beginning of the string. If you want to take that further, you can specify private:username:message as the beginning of a packet that's supposed to be sent only to username . Then, the server can lookup the IP of the user with username , and send message only to its socket, probably with another extra token to identify that this is a private message, and not a one that's supposed to be displayed in the general chat window.

Keep in mind that your current implementation allows users to send server messages simply by entering appropriate strings in the input box. I would propose creating an entirely separate class that takes objects which represent messages on input, and sends them over a socket to the server. Analogously, it provides a signal with an object that represents the message when one is sent to the user. This way, you're abstracting away the serialization and deserialization of server messages from the GUI logic, and you can easily change the implementation of the client-server communication code without re-doing the GUI. If you decide to do this, you should re-use the same code between the server and the client, if possible : this will save you many headaches that may arise from the server and the client using different code for generating (or extracting) actual messages from the received packets.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM