This tutorial gives an example of using the event loop-based Qt socket classes.
Event loop-based means that you can work without threads and still have non-blocking input / output.
As always when working with Eventloop, you have to be careful not to block any function that is called by the Eventloop.
In the case of QSocket, this means that slots that are connected to QSocket signals should only be used for short work.
It also means that QSocket methods usually end immediately and the actual task is processed with a time delay, so that the result of the task can only be recognized by signals.
generation
The sample programs in the appendix are provided with qmake project files. To create the server and the client program, QMAKESPEC and QTDIR must be set as described in the basic tutorial.
Then it is created with a simple
#> make.
The main makefile then creates the two partial makefiles and thus the two programs.
Client code
The client has a simple, GUI constructed in the designer.
The first interesting part of the code is in the constructor of the ClientMainWindow class
Code:
m_socket = new QSocket (this);
QObject :: connect (m_socket, SIGNAL (connected ()), this, SLOT (slotConnected ()));
QObject :: connect (m_socket, SIGNAL (connectionClosed ()), this, SLOT (slotDisconnected ()));
QObject :: connect (m_socket, SIGNAL (error (int)), this, SLOT (slotError (int)));
QObject :: connect (m_socket, SIGNAL (readyRead ()), this, SLOT (slotRead ()));
A QSocket instance is generated here and its signals are connected to slots of the window class.
As indicated above, the success of a QSocket method is announced by a signal.
If, for example, a connect initiated by QSocket :: connectToHost is successful, the socket sends out its connected () signal.
If the connection attempt fails, wid error (int) is sent, whereby the integer parameter gives an indication of the reason for the failure (see the associated ClientMainWindow :: slotError method ).
Connecting to a server is very easy:
Code:
QString host = m_host-> text ();
int port = m_port-> value ();
m_socket-> connectToHost (host, port);
The QSocket :: connectToHost method initiates a connection establishment and returns immediately afterwards.
An IP address or a host name can be specified as the first parameter, the latter is then resolved internally using QDns .
It should be noted here that the event loop is not blocked after the call, for example by having an endless loop afterwards.
A QSocket is a QIODevice and can therefore be used as the target of a stream, for example.
Code:
QString input = m_input-> text ();
if (input.isEmpty ()) return;
if (m_socket-> state () == QSocket :: Connected)
{
QTextStream stream (m_socket);
stream << input << endl;
}
Here it is checked whether the socket is connected and, if so, a line of text is sent over the socket.
The endline is important in this case, because the protocol of this simple example is based on the fact that a transmission always ends with a newline, i.e. it consists of complete lines.
In the general case, if any data is transferred, it is always better to transfer only smaller bites and store the rest in a buffer.
You can then use the bytesWritten signal to track when all the data of the current bite has been transferred and you can send the next part.
You can now read from the socket again via QIODevice Make functionality or, as in the example, function of the socket itself.
Code:
QString text;
while (m_socket-> canReadLine ())
text + = m_socket-> readLine ();
This code is from a slot that is connected to the readyRead signal of the socket. This signal is always sent out when new data arrive in the socket.
Since we know that there will always be a final newline, we only do something when an entire line can be read.
In the general case you will use QSocket :: bytesAvialable to check how much data has arrived and then read it into a buffer.
Server
The server also has a simple GUI.
The central part of the server functionality, i.e. accepting client connections, is done by the ServerSocket class , which is derived from QServerSocket .
The abstract method (pure virtual) newConnection (int) is implemented.
Code:
void ServerSocket :: newConnection (int socketfd)
{
QSocket * socket = new QSocket (parent ());
socket-> setSocket (socketfd);
with newClient (socket);
}
The integer parameter is the file descriptor of the data socket of the new connection.
We create a new QSocket instance and let it work with this descriptor.
So that we can do something with this connection, we have to make it accessible to our server program somehow.
This is done via a self-defined signal newClient (QSocket *) that transports our new socket as a parameter.
The first part of the servermainwindow.cpp file is the client data class
Code:
class Client
{
public:
Client (QSocket * socket, QListViewItem * item)
: m_socket (socket), m_item (item), m_num (++ m_count) {};
~ Client () {
delete m_socket;
delete m_item;
}
inline QSocket * socket () {return m_socket; }
inline QListViewItem * item () {return m_item; }
inline int number () {return m_num; }
protected:
static int m_count;
QSocket * m_socket;
QListViewItem * m_item;
int m_num;
};
The class is more or less just a grouping of data associated with a client: the socket of the data connection to the client, the ListView element in the display and the number of the client.
The number is a consecutive number that is used here to name the clients for the sake of simplicity.
The class is declared in the CPP file because it is not relevant externally. It is only important for the server class.
In the constructor of the ServerMainWindow class we use a feature of the Qt container
Code:
m_clients.setAutoDelete (true);
The AutoDelete tells the container to delete all of its entries when they are removed from it.
We will use this later to delete all Cleint instances with a simple clear () . Our server behaves like a simple chat server. It sends incoming lines of text to all clients. As with the client, this sending happens with a QTextStream, which treats the sockets of the connections as QIODevice .
Code:
void ServerMainWindow :: sendToClients (const QString & text)
{
if (text.isNull ()) return;
// iterate over all clients and send them the text
QPtrDictIterator <Client> iter (m_clients);
for (; iter.current ()! = 0; ++ iter)
{
QSocket * sock = iter.current () -> socket ();
QTextStream stream (sock);
stream << text;
}
}
We use an iterator to address all entries in the dictionary.
The entries in the dictionary are client instances and therefore have the socket () method , which returns the socket of the connection.
To be able to accept connections at all, we have to create a ServerSocket and connect its newClient .
Code:
m_server = new ServerSocket (m_port-> value (), this);
if (! m_server-> ok ())
{
delete m_server;
m_server = 0;
return;
}
QObject :: connect (m_server, SIGNAL (newClient (QSocket *)), this, SLOT (slotNewClient (QSocket *)));
If the bind () that the QSockerSocket has to do internally to reserve the port works, the QServerSocket: k () method returns "true" if it did not work "false".
If it did not work, we delete the unusable SocketServer Instant and end the method.
Otherwise we connect the newClient signal to a slot, which then processes the new connection.
This method looks like this
Code:
QListViewItem * item = new QListViewItem (m_list, socket-> peerAddress (). ToString (),
QString :: number (socket-> peerPort ()));
Client * client = new Client (socket, item);
// notify all others about the newcomer
sendToClients (QString ("Server: Client% 0 connected \ n"). arg (client-> number ()));
m_clients.insert (socket, client);
QObject :: connect (socket, SIGNAL (connectionClosed ()), this, SLOT (slotClientDisconnected ()));
QObject :: connect (socket, SIGNAL (readyRead ()), this, SLOT (slotSocketRead ()));
// great the newcomer
QTextStream stream (socket);
stream << "Server: Hi \ nYou are client" << client-> number () << endl;
First we create the ListView entry, which shows the connection in the GUI. Then we create the client instance that we use internally to manage the connection and enter it in our dictionary after we have reported the arrival of the new participant to all clients entered so far.
As in the client program, we connect QSocket signals to corresponding slots in order to be able to handle incoming data and the disconnection .
If the connection partner terminates the connection on its own, the socket sends the connectionClosed signal at our end .
We connected this to a slot that contains the following code
Code:
QObject * sender = const_cast <QObject *> (QObject :: sender ());
QSocket * socket = static_cast <QSocket *> (sender);
qDebug ("client disconnected");
// disconnect signals
socket-> disconnect ();
// remove from dict
Client * client = m_clients.take (socket);
sendToClients (QString ("Server: Client% 0 disconnected \ n"). arg (client-> number ()));
delete client;
Since we have connected the signal of all client sockets to a single slot, we first have to find out which socket sent the signal.
We do this via QObject :: sender () . Please note that this slot must never be called like a method, because in this case the value provided by this method is not defined !
The take removes the entry from the dictionary without performing the automatic deletion that we have allowed with AutoDelete.
If we take the client would not need Instance more, we could instead take and delete equal to removeto use.
We treat incoming data in a similar way to the client program
Code:
QObject * sender = const_cast <QObject *> (QObject :: sender ());
QSocket * socket = static_cast <QSocket *> (sender);
Client * client = m_clients.find (socket);
while (socket-> canReadLine ())
{
QString line = socket-> readLine (); // read the line of text
// and send it to everone including the sender
sendToClients (QString ("Client% 0:% 1"). arg (client-> number ()). arg (line));
client-> item () -> setText (2, line.left (line.length () - 1));
m_list-> update ();
}
As in the previous method, we first determine which socket has new data. Then we use this pointer to search in our dictionary for the associated client instance.
Then we read all the available lines and send them to all clients, including the current one.
At the end we update the display in the GUI of the server.
If the server program should stop working, we have to get rid of all clients.
Code:
QPtrDictIterator <Client> iter (m_clients);
for (; iter.current ()! = 0; ++ iter)
{
// disconnect the socket's signals from any slots or signals
iter.current () -> socket () -> disconnect ();
}
m_clients.clear (); // delete all clients (m_clients has autodelete ON)
First we iterate over our dictionary and delete the connections of the socket signals so that no connectionClosed bugs us in between while we iterate.
Then we destroy all client instances by simply clearing the dictionary. Since AutoDelete allowed it, it deletes all entries.
The client destructor in turn deletes the socket of the connection, which is closed in the process.
댓글 없음:
댓글 쓰기