QT-3.4 socket编程

本文介绍了一个基于 Qt 的 Socket 通信示例,通过事件循环处理非阻塞 I/O,无需使用额外线程。该教程提供了客户端和服务端的实现细节,包括如何建立连接、发送接收数据及管理客户端。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Introduction

You can find the original German version of this tutorial written by forum member anda_skoa here.

This tutorial gives an example of the use of eventloop based Qt socket classes.
Eventloop based means that one can deal with non-blocking input/output without threads.

It is important to pay attention to the fact that when you work with eventloop, you can not block any function called from eventloop. In the case of QSocket it means that slots connected to QSocket signals must be used only for short tasks.

Besides, it means that the methods of QSocket class terminate usually immediately and the task is delayed, therefore the result of the task can be known only on the ground of signals.

Production

The demo project is supplied with qmake project data. In order to compile the server and the client it is nessesary to set QMAKESPEC and QTDIR propely. Then the production goes easely:

# > make
The main makefile produces two respective makefiles and then makes the both the server and the client.

Client

The client has simple GUI made with Designer. The first interesting code snippet is located in the constructor of ClientMainWindow class:

1:
2:
3:
4:
5:
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()));
Here the instance of QSocket is created and its signals are connected with the slots of the window class.

As stated above, the result of work of a QSocket method can be got through a signal. For example, if QSocket::connectToHost establishes connection successfully, then the socket emits connected() signal. If the attempt of connection failed, error(int) is sent so that the integer parameter will give us the hint for finding the reason of the failure (see ClientMainWindow::slotError method connected for that purpose).

It is very easy to set up a server connection:

1:
2:
3:
4:
QString host = m_host->text();
int port = m_port->value();

m_socket->connectToHost(host, port);
The QSocket::connectToHost method establishes a connection and returns after that immediately.
The first parameter is the hosts IP address or hostname transformed into IP address by means of QDns.

Please note that after the call the eventloop is not blocked, thus we have in that call thereafter e.g. an infinite loop.

QSocket is inherited from QIODevice and therefore can be used for example as the destination of a stream:

1:
2:
3:
4:
5:
6:
7:
8:
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 yes, a text line is sent through the socket.
In this case the endline is important because the protocol of this simple example in based on the assumption that a transmission is always ended with newline, in other words, consists of plain lines.

In general, if arbitrary data is transferred, it is always better to transfer only little packets and to store the rest in a buffer.
Using bytesWritten signals one can track when all the data of a whole current packet were transferred and when it is time to send the next one.

To read from the socket it is possible either again using QIODevice functionality or as in the example with the method of the QSocket class:

1:
2:
3:
QString text;
while (m_socket->canReadLine())
text += m_socket->readLine();
This code reads from the slot connected with readyRead socket signal. This signal is emitted every time when new data arrive to the socket.
Since we know that the trailing newline comes, we begin to do something if the a whole line can be read.

In general, it is checked with QSocket::bytesAvialable the amount of the data received and then this amount is read in a buffer.

Server

The server has also simple GUI.
The ServerSocket class derived from QServerSocket performs the main part of the server functionality - the establishment of client connections.
The abstract method (pure virtual) newConnection(int) is implemented for that purpose:

1:
2:
3:
4:
5:
6:
7:
void ServerSocket::newConnection(int socketfd)
{
QSocket* socket = new QSocket(parent());
socket->setSocket(socketfd);

emit newClient(socket);
}
The integer parameter is the file descriptor of data socket of new connection.
We create a new QSocket instance and let it work with this file descriptor.
In order to use this new connection we have to let our server know that a new connection was already established and it is available now. This is done with a self-explanatory newClient(QSocket*) signal. The new socket as the parameter is passed to newClient().

The first part of servermainwindow.cpp file is the client class:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
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 grouped data associated with a client: the socket for data transfer to client, the ListView item in the declaration of the class and the client number.
The number is a sequential number used for the sake of simplicity here to name a client.

The class is defined in CPP file since it has importance only for the server class.

In the constructor of the ServerMainWindow class we use a feature of the Qt container:

1:
m_clients.setAutoDelete(true);
setAutoDelete(true) instructs the container to delete all its entries when they are removed from it.
We will use this later in order to delete all the client instances with simple clear() call.

Our server behaves like a simple chatserver. It sends the lines of text to all clients continually.
The sending process take place in the same manner as in case of client with a QTextStream, which treats connection sockets as QIODevice.

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
void ServerMainWindow::sendToClients(const QString& text)
{
if (text.isNull()) return;

// iterate over all clients and send them the text
QPtrDictIterator iter(m_clients);
for (; iter.current() != 0; ++iter)
{
QSocket* sock = iter.current()->socket();
QTextStream stream(sock);
stream << text;
}
}
We use iterator so that access all entries of the dictionary.
The entries in the dictionary are therefore client instances and therefore they have the socket() method returning the connection socket.

In order to be able to accept all connections we must create a ServerSocket and connect it to newClient:

1:
2:
3:
4:
5:
6:
7:
8:
9:
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 bind() succeeded to reserve the port, the QServerSocket::ok() method returns "true" otherwise - "false".

If it failed, we delete useless SocketServer instant and terminate the method. Otherwise we connect newClient signal with a slot which processes the new connection.

This method looks like the following:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
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 of all we create a ListView item indicating the connection in our GUI. Then we create the client instance which we use internally for administration of the connection. Then we add this client it in the dictionary after telling all previously registered clients about this latest registered client.

We connect QSocket signals with corresponding slots (as done in the client code) in order to manage incoming data and disconnection.

If a client terminates the connection, the socket emits connectionClosed signal. The slot is called and the following code is run:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
QObject* sender = const_cast(QObject::sender());
QSocket* socket = static_cast(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 connected the signal of each client socket with the same slot, first of all we have to find out which client's socket has emitted the signal.
We implement that with QObject::sender(). At this point it is necessary to pay attention that this slot may not be called as a method because in this case the value returned by the method is undefined!

The take() removes the entry from the dictionary without performing the automatic deletion which we have permitted with AutoDelete. If we need not the client instance any longer, we can use remove instead of take and delete.

We process incoming data similarly as in client code:

1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
QObject* sender = const_cast(QObject::sender());
QSocket* socket = static_cast(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 determine first which the socket has new data. Then we use this pointer in order to look up the associated client instance in our dictionary.
Then we read all available lines and send them to all the clients including the current client.
At the end we update still the view of the server GUI.

If the server is to stop its service, we must turn off all the clients:

1:
2:
3:
4:
5:
6:
7:
8:
QPtrDictIterator 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)
We iterate trough the dictionary and disconnect the signals and slots. Then we destroy all the instances of the clients by emptying the dictionary. Since AutoDelete was enabled, it destroys all the entries. Then the client destructor deletes the socket of the connection which thereby is closed.

Translated from German by Alexander Atamas

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值