最近在做网络编程实验,要求用到图形界面,于是又重拾了QT这个非常喜欢的C++框架。在QT官网上找到了一个特别容易上手的小软件,在此和大家一起分享,并加强一下对QT的应用。
这是代码的网址
最后软件的样子,接下来就一段代码一段代码的分析。
首先是工程
</pre>HEADERS = chatdialog.h \ client.h \ connection.h \ peermanager.h \ server.hSOURCES = chatdialog.cpp \ client.cpp \ connection.cpp \ main.cpp \ peermanager.cpp \ server.cppFORMS = chatdialog.uiQT += network widgets# installtarget.path = $$[QT_INSTALL_EXAMPLES]/network/network-chatINSTALLS += target 和普通的qmake文件没什么太大区别,注意要加上 QT += network widgets ,只有加上这个QT 才能有网络库。接下来是整个界面的定义,Chatdialog.h#ifndef CHATDIALOG_H#define CHATDIALOG_H#include "ui_chatdialog.h"#include "client.h"class ChatDialog : public QDialog, private Ui::ChatDialog{ Q_OBJECTpublic: ChatDialog(QWidget *parent = 0);public slots: void appendMessage(const QString &from, const QString &message);private slots: void returnPressed(); void newParticipant(const QString &nick); void participantLeft(const QString &nick); void showInformation();private: Client client; QString myNickName; QTextTableFormat tableFormat;};#endif然后是界面的实现Chatdialog.cpp#include <QtWidgets>#include "chatdialog.h"ChatDialog::ChatDialog(QWidget *parent) : QDialog(parent){ setupUi(this); lineEdit->setFocusPolicy(Qt::StrongFocus); //将光标锁定在lineEdit textEdit->setFocusPolicy(Qt::NoFocus); //光标不会出现在textedit,listwidget textEdit->setReadOnly(true); listWidget->setFocusPolicy(Qt::NoFocus); connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed())); connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed())); connect(&client, SIGNAL(newMessage(QString,QString)), this, SLOT(appendMessage(QString,QString))); connect(&client, SIGNAL(newParticipant(QString)), this, SLOT(newParticipant(QString))); connect(&client, SIGNAL(participantLeft(QString)), this, SLOT(participantLeft(QString))); myNickName = client.nickName(); //返回用户名,nickName()是自己定义的一个函数 newParticipant(myNickName); tableFormat.setBorder(0); //设置界面边沿宽度为0 QTimer::singleShot(10 * 1000, this, SLOT(showInformation()));//程序在10秒后会调用一个槽showInformation() }void ChatDialog::appendMessage(const QString &from, const QString &message){ if (from.isEmpty() || message.isEmpty()) return; QTextCursor cursor(textEdit->textCursor()); cursor.movePosition(QTextCursor::End); //将textEdit里的光标设置在文字的最后,以免输出混乱 QTextTable *table = cursor.insertTable(1, 2, tableFormat); table->cellAt(0, 0).firstCursorPosition().insertText('<' + from + "> ");table->cellAt(0, 1).firstCursorPosition().insertText(message); //设置textEdit的输出格式 QScrollBar *bar = textEdit->verticalScrollBar();//设置textEdit支持垂直滚动 bar->setValue(bar->maximum());}void ChatDialog::returnPressed(){ QString text = lineEdit->text(); if (text.isEmpty()) return; if (text.startsWith(QChar('/'))) { //输入以"/"打头的字符会报错 QColor color = textEdit->textColor(); textEdit->setTextColor(Qt::red); //报错内容为红色 textEdit->append(tr("! Unknown command: %1") .arg(text.left(text.indexOf(' ')))); textEdit->setTextColor(color); //将输出内容重新设为黑色 } else { client.sendMessage(text); //发送给客户机文本信息 appendMessage(myNickName, text); //在本机上显示文本信息 } lineEdit->clear();}void ChatDialog::newParticipant(const QString &nick){ if (nick.isEmpty()) return; QColor color = textEdit->textColor(); textEdit->setTextColor(Qt::gray); //将加入者信息输出设为灰色 textEdit->append(tr("* %1 has joined").arg(nick)); textEdit->setTextColor(color); /重设为黑色 listWidget->addItem(nick); //在listWidget栏内添加新的成员 }void ChatDialog::participantLeft(const QString &nick){ if (nick.isEmpty()) return; QList<QListWidgetItem *> items = listWidget->findItems(nick, Qt::MatchExactly); //在listWidget栏中找到离开者的项 if (items.isEmpty()) return; delete items.at(0); //删除项 QColor color = textEdit->textColor(); textEdit->setTextColor(Qt::gray); textEdit->append(tr("* %1 has left").arg(nick)); //输出相应信息 textEdit->setTextColor(color);}void ChatDialog::showInformation() //十秒后输出的信息 { if (listWidget->count() == 1) { QMessageBox::information(this, tr("Chat"), tr("Launch several instances of this " "program on your local network and " "start chatting!")); }}接下来是客户机的定义:Client.h#ifndef CLIENT_H#define CLIENT_H#include <QAbstractSocket>#include <QHash>#include <QHostAddress>#include "server.h"class PeerManager;class Client : public QObject{ Q_OBJECTpublic: Client(); void sendMessage(const QString &message); QString nickName() const; bool hasConnection(const QHostAddress &senderIp, int senderPort = -1) const;signals: void newMessage(const QString &from, const QString &message); void newParticipant(const QString &nick); void participantLeft(const QString &nick);private slots: void newConnection(Connection *connection); void connectionError(QAbstractSocket::SocketError socketError); void disconnected(); void readyForUse();private: void removeConnection(Connection *connection); PeerManager *peerManager; Server server; QMultiHash<QHostAddress, Connection *> peers;};#endif客户机的实现Client.cpp#include <QtNetwork>#include "client.h"#include "connection.h"#include "peermanager.h"Client::Client(){ peerManager = new PeerManager(this); peerManager->setServerPort(server.serverPort()); //将peerManager的端口设置为服务器的端口 peerManager->startBroadcasting(); QObject::connect(peerManager, SIGNAL(newConnection(Connection*)), this, SLOT(newConnection(Connection*))); QObject::connect(&server, SIGNAL(newConnection(Connection*)), this, SLOT(newConnection(Connection*)));}void Client::sendMessage(const QString &message){ if (message.isEmpty()) return; QList<Connection *> connections = peers.values(); foreach (Connection *connection, connections) //一个宏,对于每个连接都执行sendMessage() connection->sendMessage(message);}QString Client::nickName() const{ return QString(peerManager->userName()) + '@' + QHostInfo::localHostName() + ':' + QString::number(server.serverPort());}bool Client::hasConnection(const QHostAddress &senderIp, int senderPort) const{ if (senderPort == -1) return peers.contains(senderIp); if (!peers.contains(senderIp)) return false; QList<Connection *> connections = peers.values(senderIp); foreach (Connection *connection, connections) { if (connection->peerPort() == senderPort) return true; } return false;}void Client::newConnection(Connection *connection){ connection->setGreetingMessage(peerManager->userName()); connect(connection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(connectionError(QAbstractSocket::SocketError))); connect(connection, SIGNAL(disconnected()), this, SLOT(disconnected())); connect(connection, SIGNAL(readyForUse()), this, SLOT(readyForUse()));}void Client::readyForUse(){ Connection *connection = qobject_cast<Connection *>(sender()); if (!connection || hasConnection(connection->peerAddress(), connection->peerPort())) return; connect(connection, SIGNAL(newMessage(QString,QString)), this, SIGNAL(newMessage(QString,QString))); peers.insert(connection->peerAddress(), connection); QString nick = connection->name(); if (!nick.isEmpty()) emit newParticipant(nick);}void Client::disconnected(){ if (Connection *connection = qobject_cast<Connection *>(sender())) removeConnection(connection);}void Client::connectionError(QAbstractSocket::SocketError /* socketError */){ if (Connection *connection = qobject_cast<Connection *>(sender())) removeConnection(connection);}void Client::removeConnection(Connection *connection){ if (peers.contains(connection->peerAddress())) { peers.remove(connection->peerAddress()); QString nick = connection->name(); if (!nick.isEmpty()) emit participantLeft(nick); } connection->deleteLater();}连接的定义Connect.h#ifndef CONNECTION_H#define CONNECTION_H#include <QHostAddress>#include <QString>#include <QTcpSocket>#include <QTime>#include <QTimer>static const int MaxBufferSize = 1024000;class Connection : public QTcpSocket{ Q_OBJECTpublic: enum ConnectionState { WaitingForGreeting, ReadingGreeting, ReadyForUse }; enum DataType { PlainText, Ping, Pong, Greeting, Undefined }; Connection(QObject *parent = 0); QString name() const; void setGreetingMessage(const QString &message); bool sendMessage(const QString &message);signals: void readyForUse(); void newMessage(const QString &from, const QString &message);protected: void timerEvent(QTimerEvent *timerEvent) Q_DECL_OVERRIDE;private slots: void processReadyRead(); void sendPing(); void sendGreetingMessage();private: int readDataIntoBuffer(int maxSize = MaxBufferSize); int dataLengthForCurrentDataType(); bool readProtocolHeader(); bool hasEnoughData(); void processData(); QString greetingMessage; QString username; QTimer pingTimer; QTime pongTime; QByteArray buffer; ConnectionState state; DataType currentDataType; int numBytesForCurrentDataType; int transferTimerId; bool isGreetingMessageSent;};#endif连接的实现软件的重点:面向连接。 Connect.cpp#include "connection.h"#include <QtNetwork>static const int TransferTimeout = 30 * 1000;static const int PongTimeout = 60 * 1000;static const int PingInterval = 5 * 1000;static const char SeparatorToken = ' ';Connection::Connection(QObject *parent) : QTcpSocket(parent){ greetingMessage = tr("undefined"); username = tr("unknown"); state = WaitingForGreeting; currentDataType = Undefined; numBytesForCurrentDataType = -1; transferTimerId = 0; isGreetingMessageSent = false; pingTimer.setInterval(PingInterval); QObject::connect(this, SIGNAL(readyRead()), this, SLOT(processReadyRead())); QObject::connect(this, SIGNAL(disconnected()), &pingTimer, SLOT(stop())); QObject::connect(&pingTimer, SIGNAL(timeout()), this, SLOT(sendPing())); QObject::connect(this, SIGNAL(connected()), this, SLOT(sendGreetingMessage()));}QString Connection::name() const{ return username;}void Connection::setGreetingMessage(const QString &message){ greetingMessage = message;}bool Connection::sendMessage(const QString &message){ if (message.isEmpty()) return false; QByteArray msg = message.toUtf8(); QByteArray data = "MESSAGE " + QByteArray::number(msg.size()) + ' ' + msg; return write(data) == data.size();}void Connection::timerEvent(QTimerEvent *timerEvent){ if (timerEvent->timerId() == transferTimerId) { abort(); killTimer(transferTimerId); transferTimerId = 0; }}void Connection::processReadyRead(){ if (state == WaitingForGreeting) { if (!readProtocolHeader()) return; if (currentDataType != Greeting) { abort(); return; } state = ReadingGreeting; } if (state == ReadingGreeting) { if (!hasEnoughData()) return; buffer = read(numBytesForCurrentDataType); if (buffer.size() != numBytesForCurrentDataType) { abort(); return; } username = QString(buffer) + '@' + peerAddress().toString() + ':' + QString::number(peerPort()); currentDataType = Undefined; numBytesForCurrentDataType = 0; buffer.clear(); if (!isValid()) { abort(); return; } if (!isGreetingMessageSent) sendGreetingMessage(); pingTimer.start(); pongTime.start(); state = ReadyForUse; emit readyForUse(); } do { if (currentDataType == Undefined) { if (!readProtocolHeader()) return; } if (!hasEnoughData()) return; processData(); } while (bytesAvailable() > 0);}void Connection::sendPing(){ if (pongTime.elapsed() > PongTimeout) { abort(); return; } write("PING 1 p");}void Connection::sendGreetingMessage(){ QByteArray greeting = greetingMessage.toUtf8(); QByteArray data = "GREETING " + QByteArray::number(greeting.size()) + ' ' + greeting; if (write(data) == data.size()) isGreetingMessageSent = true;}int Connection::readDataIntoBuffer(int maxSize){ if (maxSize > MaxBufferSize) return 0; int numBytesBeforeRead = buffer.size(); if (numBytesBeforeRead == MaxBufferSize) { abort(); return 0; } while (bytesAvailable() > 0 && buffer.size() < maxSize) { buffer.append(read(1)); if (buffer.endsWith(SeparatorToken)) break; } return buffer.size() - numBytesBeforeRead;}int Connection::dataLengthForCurrentDataType(){ if (bytesAvailable() <= 0 || readDataIntoBuffer() <= 0 || !buffer.endsWith(SeparatorToken)) return 0; buffer.chop(1); int number = buffer.toInt(); buffer.clear(); return number;}bool Connection::readProtocolHeader(){ if (transferTimerId) { killTimer(transferTimerId); transferTimerId = 0; } if (readDataIntoBuffer() <= 0) { transferTimerId = startTimer(TransferTimeout); return false; } if (buffer == "PING ") { currentDataType = Ping; } else if (buffer == "PONG ") { currentDataType = Pong; } else if (buffer == "MESSAGE ") { currentDataType = PlainText; } else if (buffer == "GREETING ") { currentDataType = Greeting; } else { currentDataType = Undefined; abort(); return false; } buffer.clear(); numBytesForCurrentDataType = dataLengthForCurrentDataType(); return true;}bool Connection::hasEnoughData(){ if (transferTimerId) { QObject::killTimer(transferTimerId); transferTimerId = 0; } if (numBytesForCurrentDataType <= 0) numBytesForCurrentDataType = dataLengthForCurrentDataType(); if (bytesAvailable() < numBytesForCurrentDataType || numBytesForCurrentDataType <= 0) { transferTimerId = startTimer(TransferTimeout); return false; } return true;}void Connection::processData(){ buffer = read(numBytesForCurrentDataType); if (buffer.size() != numBytesForCurrentDataType) { abort(); return; } switch (currentDataType) { case PlainText: emit newMessage(username, QString::fromUtf8(buffer)); break; case Ping: write("PONG 1 p"); break; case Pong: pongTime.restart(); break; default: break; } currentDataType = Undefined; numBytesForCurrentDataType = 0; buffer.clear();}网路频宽管理伺服器定义软件的重点,connection连接好后便会调用peermanager进行传输。真正帧的封装交给了库函数,这儿只是运用了类似于帧的封装的封装信息处理。Peermanager.h#ifndef PEERMANAGER_H#define PEERMANAGER_H#include <QByteArray>#include <QList>#include <QObject>#include <QTimer>#include <QUdpSocket>class Client;class Connection;class PeerManager : public QObject{ Q_OBJECTpublic: PeerManager(Client *client); void setServerPort(int port); QByteArray userName() const; void startBroadcasting(); bool isLocalHostAddress(const QHostAddress &address);signals: void newConnection(Connection *connection);private slots: void sendBroadcastDatagram(); void readBroadcastDatagram();private: void updateAddresses(); Client *client; QList<QHostAddress> broadcastAddresses; QList<QHostAddress> ipAddresses; QUdpSocket broadcastSocket; QTimer broadcastTimer; QByteArray username; int serverPort;};#endifPeermanager.cpp#include <QtNetwork>#include "client.h"#include "connection.h"#include "peermanager.h"static const qint32 BroadcastInterval = 2000;static const unsigned broadcastPort = 45000;PeerManager::PeerManager(Client *client) : QObject(client){ this->client = client; QStringList envVariables; envVariables << "USERNAME.*" << "USER.*" << "USERDOMAIN.*" << "HOSTNAME.*" << "DOMAINNAME.*"; QStringList environment = QProcess::systemEnvironment(); foreach (QString string, envVariables) { int index = environment.indexOf(QRegExp(string)); if (index != -1) { QStringList stringList = environment.at(index).split('='); if (stringList.size() == 2) { username = stringList.at(1).toUtf8(); break; } } } if (username.isEmpty()) username = "unknown"; updateAddresses(); serverPort = 0; broadcastSocket.bind(QHostAddress::Any, broadcastPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint); connect(&broadcastSocket, SIGNAL(readyRead()), this, SLOT(readBroadcastDatagram())); broadcastTimer.setInterval(BroadcastInterval); connect(&broadcastTimer, SIGNAL(timeout()), this, SLOT(sendBroadcastDatagram()));}void PeerManager::setServerPort(int port){ serverPort = port;}QByteArray PeerManager::userName() const{ return username;}void PeerManager::startBroadcasting(){ broadcastTimer.start();}bool PeerManager::isLocalHostAddress(const QHostAddress &address){ foreach (QHostAddress localAddress, ipAddresses) { if (address == localAddress) return true; } return false;}void PeerManager::sendBroadcastDatagram(){ QByteArray datagram(username); datagram.append('@'); datagram.append(QByteArray::number(serverPort)); bool validBroadcastAddresses = true; foreach (QHostAddress address, broadcastAddresses) { if (broadcastSocket.writeDatagram(datagram, address, broadcastPort) == -1) validBroadcastAddresses = false; } if (!validBroadcastAddresses) updateAddresses();}void PeerManager::readBroadcastDatagram(){ while (broadcastSocket.hasPendingDatagrams()) { QHostAddress senderIp; quint16 senderPort; QByteArray datagram; datagram.resize(broadcastSocket.pendingDatagramSize()); if (broadcastSocket.readDatagram(datagram.data(), datagram.size(), &senderIp, &senderPort) == -1) continue; QList<QByteArray> list = datagram.split('@'); if (list.size() != 2) continue; int senderServerPort = list.at(1).toInt(); if (isLocalHostAddress(senderIp) && senderServerPort == serverPort) continue; if (!client->hasConnection(senderIp)) { Connection *connection = new Connection(this); emit newConnection(connection); connection->connectToHost(senderIp, senderServerPort); } }}void PeerManager::updateAddresses(){ broadcastAddresses.clear(); ipAddresses.clear(); foreach (QNetworkInterface interface, QNetworkInterface::allInterfaces()) { foreach (QNetworkAddressEntry entry, interface.addressEntries()) { QHostAddress broadcastAddress = entry.broadcast(); if (broadcastAddress != QHostAddress::Null && entry.ip() != QHostAddress::LocalHost) { broadcastAddresses << broadcastAddress; ipAddresses << entry.ip(); } } }}服务器定义Server.h#ifndef SERVER_H#define SERVER_H#include <QTcpServer>class Connection;class Server : public QTcpServer{ Q_OBJECTpublic: Server(QObject *parent = 0);signals: void newConnection(Connection *connection);protected: void incomingConnection(qintptr socketDescriptor) Q_DECL_OVERRIDE;};#endifServer.cpp#include <QtNetwork>#include "connection.h"#include "server.h"Server::Server(QObject *parent) : QTcpServer(parent){ listen(QHostAddress::Any);}void Server::incomingConnection(qintptr socketDescriptor){ Connection *connection = new Connection(this); connection->setSocketDescriptor(socketDescriptor); emit newConnection(connection);}<span style="white-space:pre"></span>首先是工程</p><p><span style="white-space:pre"></span>network-chat.pro</p><p></p><pre name="code" class="cpp">HEADERS = chatdialog.h \
client.h \
connection.h \
peermanager.h \
server.h
SOURCES = chatdialog.cpp \
client.cpp \
connection.cpp \
main.cpp \
peermanager.cpp \
server.cpp
FORMS = chatdialog.ui
QT += network widgets
# install
target.path = $$[QT_INSTALL_EXAMPLES]/network/network-chat
INSTALLS += target
和普通的qmake文件没什么太大区别,注意要加上 QT += network widgets ,只有加上这个QT 才能有网络库。
接下来是整个界面的定义,
chatdialog.h
#ifndef CHATDIALOG_H
#define CHATDIALOG_H
#include "ui_chatdialog.h"
#include "client.h"
class ChatDialog : public QDialog, private Ui::ChatDialog
{
Q_OBJECT
public:
ChatDialog(QWidget *parent = 0);
public slots:
void appendMessage(const QString &from, const QString &message);
private slots:
void returnPressed();
void newParticipant(const QString &nick);
void participantLeft(const QString &nick);
void showInformation();
private:
Client client;
QString myNickName;
QTextTableFormat tableFormat;
};
#endif
chatdialog.cpp
#include <QtWidgets>
#include "chatdialog.h"
ChatDialog::ChatDialog(QWidget *parent)
: QDialog(parent)
{
setupUi(this);
lineEdit->setFocusPolicy(Qt::StrongFocus);<span style="white-space:pre"> </span>//将光标锁定在lineEdit
textEdit->setFocusPolicy(Qt::NoFocus);<span style="white-space:pre"> </span>//光标不会出现在textedit,listwidget
textEdit->setReadOnly(true);<span style="white-space:pre"> </span>
listWidget->setFocusPolicy(Qt::NoFocus);
connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnPressed()));
connect(&client, SIGNAL(newMessage(QString,QString)),
this, SLOT(appendMessage(QString,QString)));
connect(&client, SIGNAL(newParticipant(QString)),
this, SLOT(newParticipant(QString)));
connect(&client, SIGNAL(participantLeft(QString)),
this, SLOT(participantLeft(QString)));
myNickName = client.nickName();<span style="white-space:pre"> </span>//返回用户名,nickName()是自己定义的一个函数
newParticipant(myNickName);<span style="white-space:pre"> </span>
tableFormat.setBorder(0);<span style="white-space:pre"> </span>//设置界面边沿宽度为0
QTimer::singleShot(10 * 1000, this, SLOT(showInformation()));<span style="white-space:pre"> </span>//程序在10秒后会调用一个槽showInformation()
}
void ChatDialog::appendMessage(const QString &from, const QString &message)
{
if (from.isEmpty() || message.isEmpty())
return;
QTextCursor cursor(textEdit->textCursor());
cursor.movePosition(QTextCursor::End);<span style="white-space:pre"> </span>//将textEdit里的光标设置在文字的最后,以免输出混乱
QTextTable *table = cursor.insertTable(1, 2, tableFormat);<span style="white-space:pre"> </span>
table->cellAt(0, 0).firstCursorPosition().insertText('<' + from + "> ");
table->cellAt(0, 1).firstCursorPosition().insertText(message);<span style="white-space:pre"> </span><span style="font-family: Arial, Helvetica, sans-serif;">//设置textEdit的输出格式</span>
QScrollBar *bar = textEdit->verticalScrollBar();<span style="white-space:pre"> </span>//设置textEdit支持垂直滚动
bar->setValue(bar->maximum());
}
void ChatDialog::returnPressed() { QString text = lineEdit->text(); if (text.isEmpty()) return; if (text.startsWith(QChar('/'))) {<span style="white-space:pre"> </span>//输入以"/"打头的字符会报错 QColor color = textEdit->textColor(); textEdit->setTextColor(Qt::red);<span style="white-space:pre"> </span>//报错内容为红色 textEdit->append(tr("! Unknown command: %1") .arg(text.left(text.indexOf(' ')))); textEdit->setTextColor(color);<span style="white-space:pre"> </span>//将输出内容重新设为黑色 } else { client.sendMessage(text);<span style="white-space:pre"> </span>//发送给客户机文本信息 appendMessage(myNickName, text);<span style="white-space:pre"> </span>//在本机上显示文本信息 } lineEdit->clear(); }
void ChatDialog::newParticipant(const QString &nick)
{
if (nick.isEmpty())
return;
QColor color = textEdit->textColor();
textEdit->setTextColor(Qt::gray);<span style="white-space:pre"> </span>//将加入者信息输出设为灰色
textEdit->append(tr("* %1 has joined").arg(nick));
textEdit->setTextColor(color);<span style="white-space:pre"> </span>//重设为黑色
listWidget->addItem(nick);<span style="white-space:pre"> </span>//在listWidget栏内添加新的成员
}
void ChatDialog::participantLeft(const QString &nick)
{
if (nick.isEmpty())
return;
QList<QListWidgetItem *> items = listWidget->findItems(nick,
Qt::MatchExactly);<span style="white-space:pre"> </span>//在listWidget栏中找到离开者的项
if (items.isEmpty())<span style="white-space:pre"> </span>
return;
delete items.at(0);<span style="white-space:pre"> </span>//删除项
QColor color = textEdit->textColor();
textEdit->setTextColor(Qt::gray);<span style="white-space:pre"> </span>//输出相应信息
textEdit->append(tr("* %1 has left").arg(nick));
textEdit->setTextColor(color);
}
void ChatDialog::showInformation()
{
if (listWidget->count() == 1) {<span style="white-space:pre"> </span>//十秒后输出的信息
QMessageBox::information(this, tr("Chat"),
tr("Launch several instances of this "
"program on your local network and "
"start chatting!"));
}
}
这篇博客介绍了使用QT框架进行网络编程实验,创建一个局域网聊天软件的过程。作者分享了从QT官网获取的易于上手的示例代码,并逐步解析了工程配置、界面设计和网络库的使用,帮助读者加深对QT应用的理解。
1293

被折叠的 条评论
为什么被折叠?



