Qt封装了各个系统的网络API,以供我们进行网络编程。要想使用网络API,先要引入网络模块(其实就是在.pro文件中设置编译选项以链接库罢了):

不过值得一提的是,Qt利用了信号槽机制实现了事件驱动(即不会盲目阻塞,而是收到数据准备好了的通知后再去读取数据或者获取新链接)。
QUdpSocket
相关接口,方法,信号

使用QUdpsocket编写服务器和客户端
服务器:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
// 创建出这个对象.
socket = new QUdpSocket(this);
// 设置窗口标题
this->setWindowTitle("服务器");
// 连接信号槽.
connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
// 绑定端口号.
bool ret = socket->bind(QHostAddress::Any, 9090);
if (!ret) {
// 绑定失败!
//errorString相当于perror()
QMessageBox::critical(this, "服务器启动出错", socket->errorString());
return;
}
}
Widget::~Widget()
{
delete ui;
}
// 这个函数完成的逻辑, 就是服务器的最核心逻辑了.
void Widget::processRequest()
{
// 1. 读取请求并解析.
const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
QString request = requestDatagram.data();
// 2. 根据请求计算响应. (由于是回显服务器. 响应不需要计算, 就是请求本身).
const QString& response = process(request);
// 3. 把响应写回给客户端.
QNetworkDatagram responseDatagram(response.toUtf8(), requestDatagram.senderAddress(), requestDatagram.senderPort());
socket->writeDatagram(responseDatagram);
// 把这次交互的信息, 显示到界面上.
QString log = "[" + requestDatagram.senderAddress().toString() + ":" + QString::number(requestDatagram.senderPort())
+ "] req: " + request + ", resp: " + response;
ui->listWidget->addItem(log);
}
QString Widget::process(const QString &request)
{
// 由于当前是回显服务器, 响应就是和请求完全一样.
// 对于一个成熟的商业服务器, 这里请求->响应的计算过程可能是非常复杂的. (业务逻辑).
return request;
}
客户端:
cpp
#include "widget.h"
#include "ui_widget.h"
#include <QNetworkDatagram>
// 定义两个常量, 描述服务器的 地址 和 端口.
const QString& SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 9090;
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
socket = new QUdpSocket(this);
// 修改窗口标题, 方便咱们区分这是一个客户端程序.
this->setWindowTitle("客户端");
// 通过信号槽, 来处理服务器返回的数据.
connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_pushButton_clicked()
{
// 1. 获取到输入框的内容
const QString& text = ui->lineEdit->text();
// 2. 构造 UDP 的请求数据.
QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
// 3. 发送请求数据.
socket->writeDatagram(requestDatagram);
// 4. 把发送的请求也添加到列表框中.
ui->listWidget->addItem("客户端说: " + text);
// 5. 把输入框的内容也清空一下.
ui->lineEdit->setText("");
}
void Widget::processResponse()
{
// 通过这个函数来处理收到的响应.
// 1. 读取到响应数据
const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
QString response = responseDatagram.data();
// 2. 把响应数据显示到界面上.
ui->listWidget->addItem("服务器说: " + response);
}

QTcpSocket和QTcpServer
相关接口,方法,信号

使用QTcpsocket和QTcpServer编写服务器和客户端
服务器:
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("服务器");
//创建监听套接字
listen_tcp = new QTcpServer();
//给链接建立信号关联槽
connect(listen_tcp,&QTcpServer::newConnection,this,&MainWindow::accept_link);
//绑定地址端口号,开始接收链接
bool ret = listen_tcp->listen(QHostAddress::Any,8080);
if(!ret)
{
//errorString相当于perror()
QMessageBox::critical(this,"服务器启动失败",listen_tcp->errorString());
return;
}
}
void MainWindow::accept_link()
{
//获取新链接
QTcpSocket* newlink = listen_tcp->nextPendingConnection();
//在服务器界面显示链接成功的信息
const QHostAddress& addr = newlink->peerAddress();
ui->listWidget->addItem("["+addr.toString()+":"+QString::number(newlink->peerPort())+"]"+"有客户端链接成功了");
//给新链接的readReady信号关联消息处理的槽
connect(newlink,&QTcpSocket::readyRead,this,[=]()->void
{
//获取客户端发来的信息
QString mesg = newlink->readAll();
ui->listWidget->addItem("[客户端说]"+mesg);
//给客户端回复消息
QString result = mesg;
newlink->write(result.toUtf8());
});
//给新链接的disconnected信号关联槽
connect(newlink,&QTcpSocket::disconnected,this,[=]()->void
{
//删除链接对象
newlink->deleteLater();
ui->listWidget->addItem("["+addr.toString()+"]"+"客户端断开链接");
});
}
客户端:
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("客户端");
//创建套接字对象
socket = new QTcpSocket();
//请求连接
socket->connectToHost("127.0.0.1",8080);
//链接的readReady信号关联消息处理的槽
connect(socket,&QTcpSocket::readyRead,this,[=]()->void
{
QString x = "[服务器回复说]"+socket->readAll();
ui->listWidget->addItem(x);
});
bool ret = socket->waitForConnected();
if (!ret) {
//errorString相当于perror()
QMessageBox::critical(this, "连接服务器出错", socket->errorString());
return;
}
}
void MainWindow::on_pushButton_clicked()
{
//获取输入框的数据
QString x = ui->lineEdit->text();
//将数据发送给服务器
socket->write(x.toUtf8());
//将数据回显到客户端界面
ui->listWidget->addItem("你发送了"+x);
//清除输入框的数据
ui->lineEdit->setText("");
}

QTcpSocket的销毁
对于服务器来说,链接会有很多,所以QTcpSocket不能依靠对象树释放,要在断开连接的时候就及时释放,防止内存,fd不够用。QTcpSever和QUdpSocket则没有这样的问题,因为他们只有一份。
销毁有两种方式,第一种就是直接delete。第二种是Qt提供的方法:deleteLater,调用了这个方法的QTcpSocket会在下一个事件循环中****销毁。
客户端的链接
linux中的原生接口connect是阻塞式的,而Qt中的connectToHost是非阻塞式的,效率会好一点,使用这个函数还要****配合 waitForConnected函数,他能判断链接是否成功**。**
QNetworkAccessManager
QNetworkAccessManager是基于Http协议的通信接口,专用于Http通信。
相关接口,方法,信号

使用QNetworkAccessManager编写一个访问网页的客户端
cpp
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
//设置窗口标题
this->setWindowTitle("客户端");
//创建QNetworkAccessManager对象
httper = new QNetworkAccessManager();
}
void MainWindow::on_pushButton_clicked()
{
//从输入框获取用户输入的url并创建url对象
QString myurl = ui->lineEdit->text();
QUrl url(myurl);
//构建一个http请求
QNetworkRequest request(url);
//发送一个get类型的http请求,非阻塞式
QNetworkReply* response = httper->get(request);
//response收到完整的应答后会触发信号finished,这里给它关联一个处理槽函数
connect(response,&QNetworkReply::finished,this,[=]()->void
{
//首先判断获取应答成功没有
if(response->error() == QNetworkReply::NoError)
{
//读取响应报文的body并展示在文本框
QString str = response->readAll();
ui->plainTextEdit->setPlainText(str);
}
else
{
//把出错原因显示在文本框
ui->plainTextEdit->setPlainText(response->errorString());
}
//这里的意思是下个事件循环再删除response对象
response->deleteLater();
});
//清空输入框
ui->lineEdit->setText("");
}

非阻塞获取应答
同connectToHost一样,QNetworkAccessManager也是非阻塞的,即并不一定在获取一个完整响应报文后才返回一个QNetworkReply对象,而是立即返回。等到QNetworkReply对象真的有了完整响应报文或者发生错误就会发送finished信号通知上层可以取用。