目录
核心API
示例:服务器和客户端信息互发
服务器代码实现
第一步:创建QTcpServer对象的实例
第二步:绑定信号槽,处理新的连接
第三步:绑定并监听端口号
客户端代码实现
第一步:创建socket对象的实例
第二步:和服务器建立连接
第三步:连接信号槽,处理响应
第四步:等待连接结果,确认是否连接成功
最终效果
UDP 无连接,不可靠传输,面向数据包,全双工
TCP 有连接,可靠传输,面向字节流,半双工
核心API
核心类是两个: QTcpServer 和 QTcpSocket
QTcpServer ⽤于监听端⼝, 和获取客户端连接
名称 | 类型 | 说明 | 对标原⽣ API |
---|---|---|---|
listen(const QHostAddress&, quint16 port) | ⽅法 | 绑定指定的地址和端⼝号, 并开始监听 | bind 和 listen |
nextPendingConnection() | ⽅法 | 从系统中获取到⼀个已经建⽴好的 tcp 连接. 返回⼀个 QTcpSocket , 表⽰这个 客⼾端的连接. 通过这个 socket 对象完成和客⼾端 之间的通信 | accept |
newConnection | 信号 | 有新的客⼾端建⽴连接好之后触发. | ⽆ (但是类似于 IO 多路复⽤ 中的通知机制) |
名称 | 类型 | 说明 | 对标原⽣ API |
---|---|---|---|
readAll() | ⽅法 | 读取当前接收缓冲区中的所有数据. 返回 QByteArray 对象. | read |
write(const QByteArray& ) | ⽅法 | 把数据写⼊ socket 中. | write |
deleteLater | ⽅法 | 暂时把 socket 对象标记为⽆效. Qt 会在下个事件循环中析构释放该对 象. | ⽆ (但是类似于 "半⾃动化的 垃圾回收") |
readyRead | 信号 | 有数据到达并准备就绪时触发 | ⽆ (但是类似于 IO 多路复⽤ 中的通知机制) |
disconnected | 信号 | 连接断开时触发 | ⽆ (但是类似于 IO 多路复⽤ 中的通知机制) |
示例:服务器和客户端信息互发
服务器代码实现
首先区分QTcpServer 和 QTcpSocket,我们需要先使用QTcpServer来监听端⼝, 和获取客户端连接。
第一步:创建QTcpServer对象的实例
tcpServer = new QTcpServer(this代码);
第二步:绑定信号槽,处理新的连接
connect(tcpServer,&QTcpServer::newConnection,this,&Widget::processConnection);
processConnection方法
void Widget::processConnection()
{
//1.通过tcpserver拿到一个socket对象,通过这个对象来和客户端进行通信
QTcpSocket* clientSocket = tcpServer->nextPendingConnection();
QString log = "[" + clientSocket->peerAddress().toString() + ":" +QString::number(clientSocket->peerPort()) + " 客户端上线了!";
ui->listWidget->addItem(log);
//2.通过信号槽,来处理客户端发来请求的情况
connect(clientSocket,&QTcpSocket::readyRead,this,[=](){
// a) 读取请求数据,此处readAll返回的是QByteArray,通过赋值转成 QString
QString request = clientSocket->readAll();
// b) 根据请求处理响应
const QString& response = process(request);
// c) 把响应写回到客户端
clientSocket->write(response.toUtf8());
// d) 把上述信息记录到日志中
QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "]" + "req:" + request + ",resp:" + response;
});
ui->listWidget->addItem(log);
//3. 通过信号槽,来处理客户端断开连接的情况
connect(clientSocket,&QTcpSocket::disconnected,this,[=](){
//a) 把断开连接的信息通过日志显示出来
QString log = "[" + clientSocket->peerAddress().toString() + ":" +QString::number(clientSocket->peerPort()) + " 客户端下线了!";
ui->listWidget->addItem(log);
//b) 手动释放clientSocket
clientSocket->deleteLater();
});
}
注意:
- 在Linux网络编程中,需要搞一个循环,循环的读取请求,循环的处理,在Qt中基于信号槽就不必循环了。每次客户端发来请求,都能触发readyRead信号。
- 因为QTcpSocket是每个客户端都有一个这样的对象,存在n个,所以必须在客户端断开连接后手动释放,而QTcpServer和QUdpSocket只有一份
- clientSocket->deleteLater();这个操作,不是立即销毁clientSocket,而是告诉Qt,下一轮事件循环时,再进行销毁操作。
第三步:绑定并监听端口号
bool ret = tcpServer->listen(QHostAddress::Any,9090);
if(!ret)
{
QMessageBox::critical(this,"服务器启动失败!",tcpServer->errorString());
exit(1);
}
完整代码:
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpServer>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void processConnection();QString process(const QString request);
private:Ui::Widget *ui;QTcpServer* tcpServer;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//修改窗口标题this->setWindowTitle("服务器");//2.创建QTcpServer的实例tcpServer = new QTcpServer(this);//3.通过信号槽,指定如何处理连接connect(tcpServer,&QTcpServer::newConnection,this,&Widget::processConnection);//4.绑定并监听端口号bool ret = tcpServer->listen(QHostAddress::Any,9090);if(!ret){QMessageBox::critical(this,"服务器启动失败!",tcpServer->errorString());exit(1);}
}Widget::~Widget()
{delete ui;
}void Widget::processConnection()
{//1.通过tcpserver拿到一个socket对象,通过这个对象来和客户端进行通信QTcpSocket* clientSocket = tcpServer->nextPendingConnection();QString log = "[" + clientSocket->peerAddress().toString() + ":" +QString::number(clientSocket->peerPort()) + " 客户端上线了!";ui->listWidget->addItem(log);//2.通过信号槽,来处理客户端发来请求的情况connect(clientSocket,&QTcpSocket::readyRead,this,[=](){// a) 读取请求数据,此处readAll返回的是QByteArray,通过赋值转成 QStringQString request = clientSocket->readAll();// b) 根据请求处理响应const QString& response = process(request);// c) 把响应写回到客户端clientSocket->write(response.toUtf8());// d) 把上述信息记录到日志中QString log = "[" + clientSocket->peerAddress().toString() + ":" + QString::number(clientSocket->peerPort()) + "]" + "req:" + request + ",resp:" + response;ui->listWidget->addItem(log);});//3. 通过信号槽,来处理客户端断开连接的情况connect(clientSocket,&QTcpSocket::disconnected,this,[=](){//a) 把断开连接的信息通过日志显示出来QString log = "[" + clientSocket->peerAddress().toString() + ":" +QString::number(clientSocket->peerPort()) + " 客户端下线了!";ui->listWidget->addItem(log);//b) 手动释放clientSocketclientSocket->deleteLater();});
}QString Widget::process(const QString request)
{return request;
}
客户端代码实现
客户端不需要跟服务器一样监听端口,因此只需要创建socket即可
第一步:创建socket对象的实例
tcpSocket = new QTcpSocket(this);
第二步:和服务器建立连接
这个函数不会阻塞等待三次握手完成的(非阻塞函数)
tcpSocket->connectToHost("127.0.0.1",9090);
第三步:连接信号槽,处理响应
connect(tcpSocket,&QTcpSocket::readyRead,this,[=](){
QString response = tcpSocket->readAll();
ui->listWidget->addItem("服务器说:"+response);
});
第四步:等待连接结果,确认是否连接成功
bool ret = tcpSocket->waitForConnected();
if(!ret){
QMessageBox::critical(this,"连接服务器",tcpSocket->errorString());
}
完整代码:
widget.h
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include <QTcpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();private slots:void on_pushButton_clicked();private:Ui::Widget *ui;QTcpSocket* tcpSocket;
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);//1.设置窗口标题this->setWindowTitle("客户端");//2.创建socket对象的实例tcpSocket = new QTcpSocket(this);//3.和服务器建立连接 这个函数不会阻塞等待三次握手完成的(非阻塞函数)tcpSocket->connectToHost("127.0.0.1",9090);//4. 连接信号槽,处理响应connect(tcpSocket,&QTcpSocket::readyRead,this,[=](){QString response = tcpSocket->readAll();ui->listWidget->addItem("服务器说:"+response);});//5.等待连接建立的结果,确认是否连接成功bool ret = tcpSocket->waitForConnected();if(!ret){QMessageBox::critical(this,"连接服务器",tcpSocket->errorString());}
}Widget::~Widget()
{delete ui;
}void Widget::on_pushButton_clicked()
{//1.获取输入的内容const QString& text = ui->lineEdit->text();//2.发送数据给服务器tcpSocket->write(text.toUtf8());//3.把发送的信息显示到界面上ui->listWidget->addItem("客户端说:" + text);//4.清空输入框的内容ui->lineEdit->setText("");}