目录
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_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
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,通过赋值转成 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();
});
}
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_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
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("");
}