- 如果QT头文件找不到QTcpSocket、QTcpSocket、QTcpServer、QtNetwork ,那么可能是pro文件中缺少
QT += network
这行代码
客户端QTcpSocket
- void QTcpSocket::connectToHost( QString servip, quint16 port );
- connectToHost 函数会尝试与指定的服务器建立 TCP 连接。如果连接成功,将会触发 connected 信号,如果连接失败,将会触发 errorOccurred 信号。属于QTcpSocket类
- QString servip:这是服务器的 IP 地址或主机名。QString 是 Qt 提供的一个字符串类,用于处理文本字符串。
- quint16 port:这是服务器的端口号。quint16 是 Qt 提供的一个无符号 16 位整数类型,通常用于表示端口号。
代码举例
QTcpSocket *socket = new QTcpSocket;
// 连接到服务器,假设服务器的 IP 地址是 "192.168.1.100" 端口号是 12345
socket->connectToHost("192.168.1.100", 12345);
// 连接成功时,触发的槽函数
connect(socket, SIGNAL(connected()), this, SLOT(onConnected()));
// 连接失败时,触发的槽函数
connect(socket, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(onError(QAbstractSocket::SocketError)));
- toUShort() 是 Qt 中 QString 类的成员函数,用于将字符串转换为 unsigned short(即 quint16)类型的整数。其作用是将一个表示数字的字符串转换为对应的无符号 16 位整数。这适合于从lineEdit中获取端口号。
- QTcpSocket的信号有
- connected(),当套接字成功连接到目标主机时发出的信号。
- disconnected(),当套接字与目标主机断开连接时发出的信号。
- readyRead(),当套接字有新的数据可供读取时发出的信号。
// 假设 ptCliSocket 是一个 QTcpSocket 对象,在构造函数或其他地方已经创建和初始化好了。
// 连接 readyRead 信号到 ptCliSocketReadyReadSlotFun 槽函数,即当 ptCliSocket 有可读数据时调用 ptCliSocketReadyReadSlotFun 函数。
connect(ptCliSocket, SIGNAL(readyRead()), this, SLOT(ptCliSocketReadyReadSlotFun()));
void Widget::ptCliSocketReadyReadSlotFun()
{
char buf[512] = {0}; // 创建一个 512 字节大小的字符数组,用于存储接收到的数据。
int ret = ptCliSocket->read(buf, sizeof(buf)); // 从 ptCliSocket 中读取数据到 buf 中,最多读取 sizeof(buf) 字节的数据。
if (ret < 0) { // 如果读取失败(返回值小于 0),显示接收错误信息到界面上的 labelTips,并返回。
ui->labelTips->setText("recv err");
return;
}
// 将读取到的数据显示在界面上的 textEdit 控件中。
ui->textEdit->setText(buf);
}
-------------------------------------------------------------------------
//上面是read读出,以下是write写入
在 QTcpSocket 类中,调用 write 方法写入数据并没有对应的信号。写入数据是一个操作,它不会触发信号。信号通常用于通知接收到新数据(readyRead 信号)或连接状态变化等事件,而不是在发送数据时触发的。
当你调用 QTcpSocket 的 write 方法写入数据时,它是一个阻塞操作,意味着程序会等待数据完全写入或发生错误才会继续执行后续代码。如果需要了解写入操作的结果,你可以根据返回值来判断是否写入成功,而不需要依赖信号来通知。
QTcpSocket *socket = new QTcpSocket;
// 假设已连接到服务器
char data[] = "Hello, server!"; // 准备要发送的数据,这里使用 C 风格的字符串
quint64 size = strlen(data); // 获取要发送数据的长度
qint64 bytesWritten = socket->write(data, size); // 向服务器发送数据,并记录实际写入的字节数
if (bytesWritten == -1) { // 如果写入操作返回 -1,表示出现错误
qDebug() << "Error writing to socket:" << socket->errorString(); // 输出错误信息
} else { // 写入成功的情况
qDebug() << "Successfully wrote" << bytesWritten << "bytes to socket."; // 输出成功写入的字节数
}
- 关于write和read
- quint64 QIODevice::write(char *data, quint64 size); 向服务器发送消息: 返回值-和普通的write保持一致
- quint64 QIODevice::read (char *data, quint64 size); 返回值和以前一致,0表示没有数据,注意,gui进程如果使用read,一定要确保有数据才去读;所以一般绑定readyread()信号
- close
- 关闭连接:这是 QIODevice 类的成员函数,用于关闭设备(例如文件、套接字等)。在网络编程中,特别是在 QTcpSocket 类中,调用 close() 方法会关闭当前的网络连接。
- :Qt 中的大多数函数都是非阻塞的。这意味着调用 close() 方法不会立即阻塞程序的执行,而是将关闭操作放入事件循环中处理,使得程序可以继续执行后续代码或响应其他事件。具体来说,在网络编程中,当调用 close() 方法时,它会发送关闭请求,并立即返回,而实际的关闭操作会在稍后异步完成。
- 一些小函数交互类
- toStdString()
- toStdString() 是 Qt 中的一个成员函数,用于将 Qt 的字符串类型转换为标准的 C++ 字符串 std::string。在实际开发中,这个函数经常用于将 Qt 的字符串类型(如 QString)转换为标准库 std::string,以便在 Qt 和标准 C++ 代码之间进行数据交换或处理。
- c_str()
- c_str() 是 C++ 标准库中用于获取字符串的 C 风格(以 null 结尾的字符数组)表示的成员函数。在 Qt 编程中,如果你有一个 QString 或 QByteArray,你可以使用 c_str() 方法来获取其对应的 C 风格字符串指针,即 const char * 类型的指针。
代码举例
#include <QString>
#include <QByteArray>
#include <iostream>
int main() {
QString qtString = "Hello, Qt!";
QByteArray byteArray = "Hello, QByteArray!";
// 使用 c_str() 获取 C 风格字符串,并输出
const char *cString1 = qtString.toStdString().c_str();
const char *cString2 = byteArray.toStdString().c_str();
// 输出 C 风格字符串
std::cout << "C string (from QString): " << cString1 << std::endl;
std::cout << "C string (from QByteArray): " << cString2 << std::endl;
// 使用 toStdString() 直接转换为 std::string,然后输出
std::string stdString1 = qtString.toStdString();
std::string stdString2 = byteArray.toStdString();
std::cout << "std::string (from QString): " << stdString1 << std::endl;
std::cout << "std::string (from QByteArray): " << stdString2 << std::endl;
return 0;
}
-------------------------------------------------------------------------------
例子2
void Widget::btnSendClickedSlotFun()
{
QString str = ui->leSendContext->text();
std::string sstr = str.toStdString();
const char *pch = sstr.c_str();
int ret = ptCliSocket->write(pch,sstr.length());
if(ret <0){
ui->labelTips->setText("send err!!!");
return;
}
return;
}
服务器QTcpServer
- QTcpServer 是 Qt 网络模块中的一个类,用于创建一个 TCP 服务器,可以监听指定的地址和端口,接受客户端的连接请求。
1.QTcpServer 的信号
- newConnection(),当有新的连接到达时,会触发这个信号。可以连接到一个槽函数,用于处理新的 QTcpSocket 连接。
connect(server,SIGNAL(newConnection()),this,SLOT(serverNewConnectionSlot() ));
- acceptError(QAbstractSocket::SocketError),这个信号在服务器在接受连接时发生错误时发出。可以通过连接到一个槽函数,来处理这些错误。(一般不用)。
connect(server, SIGNAL(acceptError(QAbstractSocket::SocketError)),
this, SLOT(onAcceptError(QAbstractSocket::SocketError)));
2. nextPendingConnection 下一个挂起连接
- nextPendingConnection 是 QTcpServer 类的一个成员函数,其作用是返回下一个挂起的连接请求,并生成一个与该连接相关的 QTcpSocket 对象。这个函数通常在处理 newConnection 信号的槽函数中调用,用于获取新的客户端连接。
// 接受新的连接
QTcpSocket *socket = server->nextPendingConnection();
- 如果没有挂起的连接请求,则返回 nullptr。
- QTcpSocket::readyRead 信号: 当有数据可读时触发,用于读取客户端发送的数据。
- QTcpSocket::disconnected 信号: 当连接断开时触发,用于清理资源。
- tips:由于QTcpServer 默认支持多个并发连接。可以在同一时间与多个客户端进行通信。你可以通过处理 newConnection 信号来接受多个客户端连接,并使用 QTcpSocket 对象与每个客户端进行独立的通信。
- 在widget.h中定义**QList<QTcpSocket*> sockets;*是在 Qt 中声明一个名为sockets的列表,该列表包含 QTcpSocket 类型的指针。这意味着sockets是一个可以存储多个 QTcpSocket 指针的容器,方便管理和操作多个客户端连接。比如使用sockets.append(newSocket);//将新的Socket指针存入数组中
3.判断是哪个客户端发来了信息
- 对socket进行read操作即可,判断读取是否非0,非0即是该socket发来的信息
void Widget::pCliSockReadyReadSlot()
{
char buf[512] = {0}; // 用于存储从客户端读取的数据的缓冲区,初始清空
// 遍历所有已连接的客户端 socket
for(int i = 0; i < sockets.length(); i++){
QTcpSocket *p = sockets.at(i); // 获取第 i 个客户端 socket
int ret = p->read(buf, sizeof(buf)); // 从客户端 socket 中读取数据到 buf 中,返回值 ret 表示实际读取的字节数。
if(ret == 0){
continue; // 如果未读取到数据,继续下一个客户端 socket 的处理
}
// 显示接收到的消息在界面的 textEdit 中
ui->textEdit->setText(buf);
char sendbuf[] = "hello client, Ur msg be got!";
p->write(sendbuf, strlen(sendbuf)); // 向客户端发送响应消息
}
}
4.判断是哪个客户端关闭了连接
- state()
- QTcpServer 类具有一个 state() 函数,用于获取当前 TCP 服务器的状态。这个函数返回一个枚举值 QAbstractSocket::SocketState,表示服务器当前的连接状态。这个状态对于管理和监控服务器的运行状态非常有用。
有以下几种状态值
状态值 | 含义 |
---|---|
QAbstractSocket::UnconnectedState | 服务器处于未连接状态,即尚未开始监听任何客户端连接。 |
QAbstractSocket::HostLookupState | 服务器正在进行主机名查找,用于解析 IP 地址。 |
QAbstractSocket::ConnectingState | 服务器正在尝试与另一个设备建立连接。 |
QAbstractSocket::ConnectedState | 服务器已成功连接到另一个设备。 |
QAbstractSocket::BoundState | 服务器已绑定到本地地址和端口,等待连接请求。 |
QAbstractSocket::ClosingState | 服务器正在关闭连接,即将结束当前的连接。 |
QAbstractSocket::ListeningState | 服务器正在监听传入的连接请求。 |
一般用第一个判断是否断开连接
void Widget::pCliSockDisconnectionSlot()
{
for(int i = 0; i < sockets.length(); i++){
QTcpSocket *p = sockets.at(i); // 获取第 i 个客户端的 QTcpSocket 对象指针
// 检查客户端的连接状态是否为未连接状态
if(p->state() == QAbstractSocket::UnconnectedState){
p->close(); // 如果客户端已经断开连接,关闭当前的 QTcpSocket 对象
sockets.removeAt(i); // 从列表中移除已经断开连接的客户端 QTcpSocket 对象
ui->textEdit->setText("found cli shutdown!"); // 在界面的 textEdit 中显示找到客户端断开连接的消息
}
}
}
5.如何启动服务器
- listen(),listen() 函数是用于 QTcpServer 类的成员函数,用来开始监听指定地址和端口的连接请求。它的作用是启动服务器,使其能够接受来自客户端的连接。
bool QTcpServer::listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
参数
address:要监听的本地地址。默认为 QHostAddress::Any,表示监听所有可用的网络接口。
port:要监听的端口号。如果为 0,系统将分配一个随机的未使用端口。
返回值
true:如果成功开始监听指定的地址和端口。
false:如果不能开始监听,可能是由于地址或端口被占用或其他原因导致的失败。
- 代码举例
void Widget::btnSetupClickedSlot()
{
// 从界面的 lePort LineEdit 中获取端口号,并转换为 quint16 类型
quint16 port = ui->lePort->text().toUShort();
// 调用 ptTcpServ(QTcpServer 对象)的 listen 函数,开始监听任意本地地址和指定端口
bool r = ptTcpServ->listen(QHostAddress::Any, port);
// 检查 listen 函数返回值,r 为 false 表示服务器启动失败
if(r == false){
ui->textEdit->setText("serv setup failed!"); // 在界面的 textEdit 中显示服务器设置失败的消息
return; // 退出函数
}
ui->textEdit->setText("serv setup success"); // 在界面的 textEdit 中显示服务器设置成功的消息
// 服务器启动成功,等待客户端连接的信号处理
}
代码举例
服务器代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QTcpServer>
#include<QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void serverNewConnectedSlotFun();
void socketReadyReadSlotFun();
void socketDisconnectedSlotFun();
void btnLinkClickedSlotFun();
void socketBoxCurrentIndexChanged(int);
private:
Ui::Widget *ui;
QTcpServer *server;
QList<QTcpSocket*> sockets;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
// Widget类的构造函数,初始化UI和QTcpServer对象,并设置连接信号槽
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this); // 设置UI
server = new QTcpServer; // 创建QTcpServer对象
connect(server, SIGNAL(newConnection()), this, SLOT(serverNewConnectedSlotFun())); // 连接新连接信号到槽函数
ui->btnLink->setText("打开服务器"); // 设置按钮文本
ui->linePort->setText("8888"); // 设置端口文本
connect(ui->btnLink, SIGNAL(clicked()), this, SLOT(btnLinkClickedSlotFun())); // 连接按钮点击信号到槽函数
connect(ui->socketsBox, SIGNAL(currentIndexChanged(int)), this, SLOT(socketBoxCurrentIndexChanged(int))); // 连接下拉框改变信号到槽函数
}
// Widget类的析构函数,释放UI资源
Widget::~Widget()
{
delete ui; // 释放UI资源
}
// 当有新连接时,处理新连接的槽函数
void Widget::serverNewConnectedSlotFun()
{
QTcpSocket *socket = server->nextPendingConnection(); // 获取新连接的socket
connect(socket, SIGNAL(readyRead()), this, SLOT(socketReadyReadSlotFun())); // 连接数据可读信号到槽函数
connect(socket, SIGNAL(disconnected()), this, SLOT(socketDisconnectedSlotFun())); // 连接断开连接信号到槽函数
sockets.append(socket); // 将新连接的socket添加到列表中
QString str = QString("QTcpSocket(%1)").arg(reinterpret_cast<quintptr>(socket), 0, 16); // 获取socket的地址字符串
ui->socketsBox->addItem(str); // 将地址字符串添加到下拉框
ui->textEditRecv->setText("new connect"); // 设置文本编辑框的文本
}
// 当有数据可读时,处理读取数据的槽函数
void Widget::socketReadyReadSlotFun()
{
char buf[512] = {0}; // 创建缓冲区
QTcpSocket *p;
for(int i = 0; i < sockets.length(); i++) { // 遍历所有socket
p = sockets.at(i); // 获取当前socket
int ret = p->read(buf, sizeof(buf)); // 读取数据到缓冲区
if(ret == 0) {
continue; // 如果没有数据读取,继续下一次循环
}
ui->textEditRecv->setText(buf); // 将读取的数据设置到文本编辑框
char sendbuf[] = "hello client"; // 创建发送数据
p->write(sendbuf, strlen(sendbuf)); // 发送数据到客户端
}
}
// 当连接断开时,处理断开连接的槽函数
void Widget::socketDisconnectedSlotFun()
{
for(int i = 0; i < sockets.length(); i++) { // 遍历所有socket
QTcpSocket *p = sockets.at(i); // 获取当前socket
if(p->state() == QAbstractSocket::UnconnectedState) { // 检查socket是否断开连接
p->close(); // 关闭socket
sockets.removeAt(i); // 从列表中移除socket
ui->socketsBox->removeItem(i); // 从下拉框中移除项
ui->textEditRecv->setText("found cli shutdown"); // 设置文本编辑框的文本
}
}
}
// 处理按钮点击事件的槽函数
void Widget::btnLinkClickedSlotFun()
{
quint16 port = ui->linePort->text().toUShort(); // 获取端口号
bool ret = server->listen(QHostAddress::Any, port); // 启动服务器监听
if(ret == false) {
ui->textEditRecv->setText("打开服务器失败"); // 如果监听失败,设置文本编辑框的文本
return;
}
ui->textEditRecv->setText("打开服务器成功"); // 如果监听成功,设置文本编辑框的文本
}
// 处理下拉框当前索引改变事件的槽函数
void Widget::socketBoxCurrentIndexChanged(int i)
{
QString send = ui->textEditSend->toPlainText(); // 获取发送数据
const char *send1 = send.toStdString().c_str(); // 将QString转换为C字符串
QString str = ui->socketsBox->itemText(i); // 获取下拉框当前选中的文本
for(int i = 0; i < sockets.length(); i++) { // 遍历所有socket
QTcpSocket* aimSocket = sockets.at(i); // 获取当前socket
QString straim = QString("QTcpSocket(%1)").arg(reinterpret_cast<quintptr>(aimSocket), 0, 16); // 获取socket的地址字符串
if(str == straim) { // 如果地址字符串匹配
int ret = aimSocket->write(send1, strlen(send1)); // 发送数据到客户端
if(ret < 0) {
ui->label->setText("发送失败"); // 如果发送失败,设置标签的文本
} else {
ui->label->setText("发送成功"); // 如果发送成功,设置标签的文本
}
}
}
}
客户端代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void btnlinkClickedSlotFun();
void socketClientConnectedSlotFun();
void socketClientReadyReadSlotFun();
void socketClientDisconnectedSlotFun();
void btnSendClickedSlotFun();
private:
Ui::Widget *ui;
QTcpSocket *SocketClient;
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include<QDebug>
// Widget类的构造函数,初始化UI和QTcpSocket对象,并设置连接信号槽
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this); // 设置UI
SocketClient = new QTcpSocket; // 创建QTcpSocket对象
ui->btnlink->setText("connect"); // 设置连接按钮的文本
connect(ui->btnlink, SIGNAL(clicked()), this, SLOT(btnlinkClickedSlotFun())); // 连接按钮点击信号到槽函数
connect(SocketClient, SIGNAL(connected()), this, SLOT(socketClientConnectedSlotFun())); // 连接socket连接成功信号到槽函数
connect(SocketClient, SIGNAL(readyRead()), this, SLOT(socketClientReadyReadSlotFun())); // 连接socket有数据可读信号到槽函数
connect(SocketClient, SIGNAL(disconnected()), this, SLOT(socketClientDisconnectedSlotFun())); // 连接socket断开连接信号到槽函数
ui->btnSend->setText("发送"); // 设置发送按钮的文本
connect(ui->btnSend, SIGNAL(clicked()), this, SLOT(btnSendClickedSlotFun())); // 连接发送按钮点击信号到槽函数
ui->lineIp->setText("192.168.124.137"); // 设置默认IP地址
ui->linePort->setText("8888"); // 设置默认端口号
}
// Widget类的析构函数,释放UI资源
Widget::~Widget()
{
delete ui; // 释放UI资源
}
// 处理连接按钮点击事件的槽函数
void Widget::btnlinkClickedSlotFun()
{
if(ui->btnlink->text() == "connect") { // 如果按钮文本为"connect"
QString Ip = ui->lineIp->text(); // 获取IP地址
quint16 Port = ui->linePort->text().toUShort(); // 获取端口号
SocketClient->connectToHost(Ip, Port); // 连接到服务器
}
else if(ui->btnlink->text() == "disconnect") // 如果按钮文本为"disconnect"
SocketClient->close(); // 关闭连接
else
ui->labelTips->setText("connect error"); // 如果文本为其他值,显示错误信息
}
// 处理socket连接成功事件的槽函数
void Widget::socketClientConnectedSlotFun()
{
ui->btnlink->setText("disconnect"); // 设置按钮文本为"disconnect"
ui->labelTips->setText("Tips:connected success"); // 显示连接成功信息
}
// 处理socket有数据可读事件的槽函数
void Widget::socketClientReadyReadSlotFun()
{
char buf[512] = {0}; // 创建缓冲区
int ret = SocketClient->read(buf, sizeof(buf)); // 读取数据到缓冲区
if(ret < 0) {
ui->labelTips->setText("Tips:读取错误"); // 如果读取失败,显示错误信息
return;
}
ui->textRecv->setText(buf); // 将读取的数据设置到文本框
}
// 处理socket断开连接事件的槽函数
void Widget::socketClientDisconnectedSlotFun()
{
ui->btnlink->setText("connect"); // 设置按钮文本为"connect"
ui->labelTips->setText("Tips:连接断开"); // 显示连接断开信息
}
// 处理发送按钮点击事件的槽函数
void Widget::btnSendClickedSlotFun()
{
QString send = ui->lineSend->text(); // 获取发送数据
const char *send1 = send.toStdString().c_str(); // 将QString转换为C字符串
int ret = SocketClient->write(send1, strlen(send1)); // 发送数据到服务器
if(ret < 0) {
ui->labelTips->setText("Tips:发送失败"); // 如果发送失败,显示错误信息
return;
}
return;
}