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.
Related
I have a function that makes an http request, it can take some time for the server to respond. During the wait time I would like to display a please wait dialog to the user.
I have tried creating the please wait dialog and then sending the http request to the server. The please wait dialog returns instantly, no error and nothing displayed, the http request is then made but no please wait dialog is displayed.
If I do not perform the http request and allow execution to continue the dialog is displayed.
I think maybe the solution would be to create the please wait dialog in a new thread ?
My class, clsAlert is derived from clsDialog, in the constructor:
clsAlert::clsAlert(QString strText, QWidget* pParent, bool blnAutoCleanup) : QDialog(pParent), ui(new Ui::clsAlert) {
ui->setupAlert(this);
ui->lblText->setText(strText);
... set-up geometry ...
show();
}
Usage example:
clsAlert* pPW = new clsAlert(clsAlert::mscszPleaseWait,mpParent);
int intServer = intCreateSocket(szDestURL, pOutbio);
... a lot more source ...
pPW->cleanup();
In the end I fixed this in my case by adding a call to:
QCoreApplication::processEvents();
Immediately after my call to:
show();
I have been stuck on this for the past 5 days, I have no idea how to proceed.
Overview:
I have a client UI which interacts with a data handler library, and the data handler library utilizes a network manager library, which is where my problem lies.
More Info
Firstly, QT provides a basic example for interactions between a QTcpServer (Fortune Server)and a QTcpSocket (Fortune Client).
I thus implemented this code into an extremely basic example of my own, which works like a charm and has no issues.
My own adaption of fortune client and server for the record (basic)
Quick Explaination:
Server application runs, click on start server, then on the client side, enter text in field and click connect to server and text is displayed, easy!
Problem:
Implementing the code above into my network manager library, does not fire the QTcpSocket::readyRead() in the server application above.
It connects to the server, where the QTcpServer::newConnection() is fired, as expected, straight after which the client writes to the socket but the readyRead() on the server socket does not fire, however in the example given it does.
Note:
The same port and ip address is used in this server-client application example and my current application, and the server is also running.
Further Information:
From the above code, I copied over directly from the client. Only 2 things were changed/modified:
String that is sent to server
return types for method
This was copied into my network mannager ::write() method. When running my application, and instance of QMainWindow is passed via data handler class and creates an instance of my network manager class which inherits QObject and implements the Q_OBJECT macro.
Code Examples:
//client_UI Class (snippet):
data_mananger *dman = new data_mananger(this); //this -> QMainWindow
ReturnObject r = dman->NET_AuthenticateUser_GetToken(Query);
//data_manager library (snippet)
data_mananger::data_mananger(QObject *_parent) :
parent(_parent)
{}
ReturnObject data_mananger::NET_AuthenticateUser_GetToken(QString Query){
//Query like "AUTH;U=xyz#a;P=1234"
//convert query string to char
QByteArray ba = Query.toLatin1();
//send query and get QList return
ReturnCode rCode = networkManager.write(ba);
//...
}
//netman library (snippet)
//.h
class NETMANSHARED_EXPORT netman : public QObject
{
Q_OBJECT
public
netman();
netman(QObject *_parent);
//...
private:
QTcpSocket *tcp_con;
//...
};
//cpp
netman::netman(QObject *_parent) :
parent(_parent)
{
tcp_con = new QTcpSocket(parent);
}
return;
}
serverIP.setAddress(serverInfo.addresses().first().toIPv4Address());
}
ReturnCode netman::write(QByteArray message, int portNumber){
tcp_con->connectToHost(QHostAddress("127.0.0.1"), 5000);
if (!tcp_con->waitForConnected())
{
qDebug(log_lib_netman_err) << "Unable to connect to server";
return ReturnCode::FailedConnecting;
}
if (!tcp_con->isValid()) {
qDebug(log_lib_netman_err) << "tcp socket invalid";
return ReturnCode::SocketError;
}
if (!tcp_con->isOpen()) {
qDebug(log_lib_netman_err) << "tcp socket not open";
return ReturnCode::SocketError;
}
// QByteArray block(message);
QByteArray block;
QDataStream out(&block,QIODevice::WriteOnly);
out.setVersion(QDataStream::Qt_4_0);
out << QString("Hello world");
if (!tcp_con->write(block)){
qDebug(log_lib_netman_err) << "Unable to send data to server";
return ReturnCode::WriteFailed;
}
else{
qDebug(log_lib_netman_info) << "Data block sent";
return ReturnCode::SentSuccess;
}
}
Conclusion:
The core code of the client side has been fully implemented, yet I cannot see why this error occurs.
I would very much appreciate help/advice!
Add a tcp_con->flush() statement to the end of your write function.
Why/how this works
You weren't getting a readyRead signal in your receiver because the written data was being buffered into the socket but not actually transmitted 'over the wire'. The flush() command causes the buffer to be transmitted. From the docs
This function writes as much as possible from the internal write
buffer to the underlying network socket, without blocking. If any data
was written, this function returns true; otherwise false is returned.
How are you supposed to know
In my case a lot of experience/frustration with serial ports and flushing. It's the equivalent of "have you rebooted it?" in the socket debugging toolbox.
If everything else is working fine, you may not have to flush, but it's kind of application specific and depends on the lifetime of the socket, the TCP window size, socket option settings, and various other factors. That said, I always flush because I like having complete control over my sockets, and I want to make sure data is transmitted when I want it to be. I don't think it's a hack, but in some cases it could be indicative of some other problem. Again, application specific.
Why might the buffer not be flushing itself?
I'm pretty sure no flush is needed in the fortune server example because they disconnectFromHost at the end of the sendFortune() function, and from the Qt documentation:
Attempts to close the socket. If there is pending data waiting to be
written, QAbstractSocket will enter ClosingState and wait until all
data has been written.
The socket would disconnect if it were destroyed as well, but from what I can see of your code you aren't doing that either, and the buffer isn't full, so probably nothing is actually stimulating the buffer to flush itself.
Other causes can be:
flow control isn't returned to the event loop (blocking calls, etc), so the buffer flush is never performed.
Transmit is occuring inside of a loop, which seems like it will exit (e.g. while(dataToTransmit)), but in fact the condition never becomes false, which leads to the event loop being blocked.
Nagles algorithm: the buffer may be waiting for more data before it flushes itself to keep network throughput high. You can disable this by setting the QAbstractSocket::LowDelayOption, but it may adversely affect your throughput... it's normally used for latency-sensative applications.
I have a need for a very simple server program which executes one of five different activities, based on client connections.
Since this is for a demo, I don't need any complex network handling, my intention was just to open up five server sockets (say 10001 thru 10005 inclusive) and simply await incoming connections.
Upon the server receiving an incoming connection on (for example) the first socket 10001, it would immediately accept and close the connection, then execute the first action. Ditto for the other sockets. That way, I could demo the actions from another window simply by executing:
telnet 127.0.0.1 10001
Normally, I would use select() with a very short timeout value (i.e., not too onerous on the event processing thread) to await and detect which port was being connected to but, since this is a Qt application, I'm not sure that will work so well with the Qt event model.
What would be the best way of doing this with Qt (5.5, if it matters)? Is the use of a small-timeout select() actually going to work or do I need to go heavyweight with five separate QTcpServer objects, each with their own infrastructure (callbacks and such)?
If I properly understand, you want handle all requests in one place. In Qt you can use signal/slot for it. Connect signals from all QTcpServer objects to one slot, something like:
// Method to fully start a server on specific port.
QTcpServer *MyClass::StartServer(int port) {
QTcpServer *server = new QTcpServer();
server->listen(QHostAddress::Any, port);
connect(server, SIGNAL(newConnection()), this, SLOT(HandleConn()));
return server;
}
// Method to start all servers, serverArr is an array of QTcpServer
// items (in the object, obviously).
void MyClass::StartAllServers() {
for (size_t i = 0; i < sizeof(serverArr) / sizeof(*serverArr); i++)
serverArr[i] = StartServer(10000 + i);
}
// Callback to handle connections.
void MyClass::HandleConn() {
// This will call doAction with parameter based on which QTcpServer
// has caused the callback.
QTcpServer *source = static_cast<QTcpServer*>(sender());
for (size_t i = 0; i < sizeof(serverArr) / sizeof(*serverArr); i++)
if (source == serverArr[i])
doAction(i);
// Action done, so just accept and close connection.
QTcpSocket *socket = source->nextPendingConnection();
socket->close();
}
I am using QTcpSocket to send data between a client and a server.
The server works perfectly fine on Linux, yet on Windows it will only receive one message and not more after that. readyRead() is just never emitted again.
Does anyone know what could be the problem?
Some of the code which I deemed to be important for this question:
server sending:
void Server::sendData(Client *client, QString data)
{
if (client->socketDescriptor == socketDescriptor) {
data = data + CRLF;
// Have also tried to use socket->flush(), same problem.
socket->write(data.toUtf8());
socket->waitForBytesWritten();
}
}
server receiving:
// this slot is connected to the readyRead() signal
void Server::readData()
{
// on Windows this is called exactly once for each client
// every additional messages just don't seem to arrive at the server
QString msg = QString(socket->readAll());
emit receivedData(this, &msg);
}
I just don't get why it works on Linux, yet not on windows...
I've modified the threaded fortune-server from Qt examples.
The client connects to the server and then sends a header to authenticate.
tcpSocket = new QTcpSocket();
tcpSocket->connectToHost(addr, port);
QByteArray block = "someheader";
int x = tcpSocket->write(block);
qDebug() << x;
The client seems OK here and qDebug prints the actual size of block.
On the server side I've predefined incomingConnection and I start thread to each new connection.
void Server::incomingConnection(int socketDescriptor) {
const QString &str = vec[qrand() % vec.size()];
SpellThread *thread = new SpellThread(socketDescriptor, str);
connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater()));
qDebug() << " -- incoming connection";
thread->start();
}
I'm connecting sock to check is there something to read. (sock here is QTcpServer*)
void SpellThread::run() {
qDebug() << " -- in spellthread";
connect(sock, SIGNAL(readyRead()), this, SLOT(checkBytes()));
//....
qDebug() << " -- end spellthread";
}
The first problem is that when I'm sending data from the client, readyRead is not fired. (I've added debug message in checkBytes)
Messages are:
-- incoming connection
-- in spellthread
-- end spellthread
Although the client prints the actual size of header length.
The second problem is that checkBytes currently is very bad-designed. First it checks is header OK and sets a flag, then it gets the size of message and sets another flag and finally it gets the real message. This is very clumsy. I first tried to escape signals and instead use sock->waitForReadyRead(). However it always returns false. (From the docs: "Reimplement this function to provide a blocking API for a custom device. The default implementation does nothing, and returns false.").
So how to really make a client/server application in Qt with multiple clients and multiple reads/writes? I really want suggestions to improve design of my application and to solve my current two problems.
You can't use slots or socket signals with a thread without calling QThread::exec() to start an event loop within that thread/the run function.
Since your checkBytes slot belongs to QThread, it wouldn't be executed by the thread (there is a detailed article about QThreads here)
The closest example that seems to already do what you want is the Network Chat (particularly the two classes Server and Connection).
----------Edit
If you need to use threads (without any slot), the QTcpSocket object must belongs to the same thread as the one where you call waitForReadyRead. For example, with:
SpellThread::SpellThread(int socketDescriptor, const QString & str) {
tcpSocket = new QTcpSocket(); // There should be no parent to be able
// to move it to the thread
tcpSocket->moveToThread(this);
...
Or by creating the QTcpSocket object inside the run function so that it automatically belongs to that thread (it was briefly explained in the fortune example).
If you allocate the QTcpSocket dynamically, and because it won't have a parent, you should also delete it manually.