TCP 协议(Transmission Control Protocol)全称是传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议
简单来说就是用来网络通信的。
TCP 通信必须先建立 TCP 连接,通信端分为客户端和服务端。


服务端通过监听某个端口来监听是否有客户端连接到来,如果有连接到来,则建立新的 socket 连接;客户端通过 ip 和port 连接服务端,当成功建立连接之后,就可进行数据的收发了。
新建一个WIdget项目,我们先添加network模块:



我们先进行槽函数的连接(先创建槽函数):


我们创建Socket对象,先把类添加进来:

按F4:(在 Qt 中, Qt 把 socket 当成输入输出流来对待的,数据的收发是通过 read()和 write()来进行的,需要与我们常见的 send()与 recv()进行区分。
)

然后要接收消息或者是发送消息的话要有一些信号的。





OK,接收消息的函数已经写好了。
我们看一下状态,因为TCP服务端有时候跟客户端要断开连接,或者不在线的时候要做出一些处理的,这比较重要。既然有消息发出的信号,就肯定有连接状态的信号。



接下来就是是否要监听客户端的到来?监听哪个端口?
我们先来设计一个界面:

Ctrl+b先构建一下。
然后我们在新的连接里面打印一下客户端的消息:
首先打印下客户端连接的状态,就是状态改变的参数先完善一下,先链接才会获取到IP地址等。


下面是详细代码(服务器端程序完成):
//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();
private:
Ui::Widget *ui;
QTcpServer *tcpServer;
private slots:
void mNewConnection();
void receiveMessages();
void mStateChanged(QAbstractSocket::SocketState);
void on_pushButton_3_clicked();
void on_pushButton_clicked();
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = new QTcpServer(this);
//新的连接
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(mNewConnection()));//连接槽函数
}
Widget::~Widget()
{
delete ui;
}
void Widget::mNewConnection()
{
//与客户端连接(他不是主动去连接的,而是客户端主动连接服务端)
QTcpSocket *tmpTcpSocket = tcpServer->nextPendingConnection();
//客户端的ip地址
QString ipaddr = tmpTcpSocket->peerAddress().toString();
quint16 port = tmpTcpSocket->peerPort();
//打印客户端连接的IP信息
ui->textBrowser->append("客户端的ip地址:" + ipaddr);
//打印客户端连接的端口信息
ui->textBrowser->append("客户端的端口:" + QString::number(port));
connect(tmpTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessages()));//连接槽函数
connect(tmpTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(mStateChanged(QAbstractSocket::SocketState)));//连接槽函数
}
void Widget::receiveMessages()
{
QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
//接收消息
ui->textBrowser->append(tmpTcpSocket->readAll());
}
void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
switch(state) {
//已经断开
case QAbstractSocket::UnconnectedState:
ui->textBrowser->append("客户端断开连接");
delete tmpTcpSocket;
break;
//已经连接
case QAbstractSocket::ConnectedState:
ui->textBrowser->append("客户端已连接");
break;
default:
break;
}
delete tmpTcpSocket;
}
//发送消息按钮clicked
void Widget::on_pushButton_3_clicked()
{
QList <QTcpSocket *> socketList = tcpServer->findChildren<QTcpSocket *>();
foreach (QTcpSocket *tmpTcpSocket, socketList) {
//服务端向每个客户服务端发送消息
tmpTcpSocket->write(ui->lineEdit->text().toUtf8());
}
}
//开始监听按钮clicked
void Widget::on_pushButton_clicked()
{
//绑定ip地址和端口
tcpServer->listen(QHostAddress("192.168.18.130"), 9999);
}
接下来我们将用Qt编写Tcp客户端应用程序:
在TCP服务端的应用程序中我们使用了两个类,即QTcpServer和QTcpSocket,在客户端里面只需要使用QTcpSocket类。
TCP客户端的程序编写·流程如下:

新建一个项目:




信号槽添加进来:

我们设计它的界面:

ctrl+b构建一下。
主要代码展示:
//widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QTcpSocket>
#include <QHostAddress>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private:
Ui::Widget *ui;
QTcpSocket *tcpSocket;
private slots:
void receiveMessage();
void mStateChanged(QAbstractSocket::SocketState);
void on_pushButton_3_clicked();
void on_pushButton_clicked();
void on_pushButton_2_clicked();
};
#endif // WIDGET_H
//widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::receiveMessage()
{
//接收消息
ui->textBrowser->append("服务器端:" + tcpSocket->readAll());
}
void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
switch(state) {
case QAbstractSocket::UnconnectedState:
ui->textBrowser->append("与服务端断开连接");
break;
case QAbstractSocket::ConnectedState:
ui->textBrowser->append("已连接服务端");
break;
default:
break;
}
}
//发送消息槽函数clicked
void Widget::on_pushButton_3_clicked()
{
//发送消息
if (tcpSocket->state() == QAbstractSocket::ConnectedState)
tcpSocket->write(ui->lineEdit->text().toUtf8());
else
ui->textBrowser->append("请先于服务端连接");
}
//连接服务器槽函数clicked
void Widget::on_pushButton_clicked()
{
//连接服务端,指定服务端的IP地址或端口
tcpSocket->connectToHost(QHostAddress("192.168.18.130"), 10000);
}
//断开连接槽函数clicked
void Widget::on_pushButton_2_clicked()
{
//断开连接
tcpSocket->disconnectFromHost();
}
接下来我们将进行TCP客户端与TCP服务器端的测试:
首先我们设置下服务器项目和客户端项目的标题,如下:


我们将他们运行,服务器端点击监听,客户端点击连接服务器,发现连接失败。
我们找下原因:先检查下程序,我们发现ip ok,端口不对,我们将客户端的端口也改为和服务器端一样9999,再次执行上一段的动作:

我们点击停止监听,会断开,这里执行无效。我们转到停止监听的槽函数,发现没写:


在按钮组里他们才会互斥


灰色的话就说明不可选的。
客户端也是:




其实还有一个问题我们没有考虑,只有连上之后才可以用才对。所以互斥还要改下:


我们用程序来写:


我们再运行下服务端:


服务端出现服务异常,这是为什么?
一般程序异常的话肯定是指针的问题。
此时我们进行Debug,发现我们删除了指针,我们这么修改:

此时我们再次执行:发现运行正常。
接下来我们验证下退出的时候socket是不是被删除了。



这与该段代码的设置有莫大的关系!

这样可以避免浪费内存。
下面我们补充下客户端和服务端打印信息的内容:

我们再次修改下:

将服务端的默认发送消息也编辑下:

然后我们验证下:


接下来我们将修改下显示内容,修改的位置见下面详细的代码:
cpp
//服务器端widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = new QTcpServer(this);
this->setWindowTitle("服务端");
//新的连接
connect(tcpServer, SIGNAL(newConnection()), this, SLOT(mNewConnection()));//连接槽函数
}
Widget::~Widget()
{
delete ui;
}
void Widget::mNewConnection()
{
//与客户端连接(他不是主动去连接的,而是客户端主动连接服务端)
QTcpSocket *tmpTcpSocket = tcpServer->nextPendingConnection();
//客户端的ip地址
QString ipaddr = tmpTcpSocket->peerAddress().toString();
quint16 port = tmpTcpSocket->peerPort();
//打印客户端连接的IP信息
ui->textBrowser->append("服务端:客户端的ip地址:" + ipaddr);
//打印客户端连接的端口信息
ui->textBrowser->append("服务端:客户端的端口:" + QString::number(port));
connect(tmpTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessages()));//连接槽函数
connect(tmpTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this, SLOT(mStateChanged(QAbstractSocket::SocketState)));//连接槽函数
}
void Widget::receiveMessages()
{
QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
//接收消息
ui->textBrowser->append("客户端:" + tmpTcpSocket->readAll());
}
void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
QTcpSocket *tmpTcpSocket = (QTcpSocket *)sender();
switch(state) {
//已经断开
case QAbstractSocket::UnconnectedState:
ui->textBrowser->append("服务端:客户端断开连接");
tmpTcpSocket->deleteLater();
//delete tmpTcpSocket;
break;
//已经连接
case QAbstractSocket::ConnectedState:
ui->textBrowser->append("服务端:客户端已连接");
break;
default:
break;
}
//delete tmpTcpSocket;
}
//发送消息按钮clicked
void Widget::on_pushButton_3_clicked()
{
QList <QTcpSocket *> socketList = tcpServer->findChildren<QTcpSocket *>();
qDebug() << "QTcpSocket 数量:" << socketList.count() << endl;
if(socketList.count() == 0) {
ui->textBrowser->append("当前没有客户端连接,请先与客户端连接");
return;
}
foreach (QTcpSocket *tmpTcpSocket, socketList) {
//服务端向每个客户服务端发送消息
tmpTcpSocket->write(ui->lineEdit->text().toUtf8());
}
ui->textBrowser->append("服务端:" + ui->lineEdit->text());
}
//开始监听按钮clicked
void Widget::on_pushButton_clicked()
{
//绑定ip地址和端口
tcpServer->listen(QHostAddress("192.168.18.130"), 9999);
ui->textBrowser->append("服务端:监听的IP地址和端口:192.168.18.130,9999");
}
void Widget::on_pushButton_2_clicked()
{
//停止监听
tcpServer->close();
}
cpp
//客户端widget.cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
this->setWindowTitle("客户端");
ui->pushButton_2->setEnabled(false);
tcpSocket = new QTcpSocket(this);
connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
}
Widget::~Widget()
{
delete ui;
}
void Widget::receiveMessage()
{
//接收消息
ui->textBrowser->append("服务器端:" + tcpSocket->readAll());
}
void Widget::mStateChanged(QAbstractSocket::SocketState state)
{
switch(state) {
case QAbstractSocket::UnconnectedState:
ui->textBrowser->append("客户端:与服务端断开连接");
ui->pushButton->setEnabled(true);
ui->pushButton_2->setEnabled(false);
break;
case QAbstractSocket::ConnectedState:
ui->textBrowser->append("客户端:已连接服务端");
ui->pushButton->setEnabled(false);
ui->pushButton_2->setEnabled(true);
break;
default:
break;
}
}
//发送消息槽函数clicked
void Widget::on_pushButton_3_clicked()
{
//发送消息
if (tcpSocket->state() == QAbstractSocket::ConnectedState) {
tcpSocket->write(ui->lineEdit->text().toUtf8());
ui->textBrowser->append("客户端" + ui->lineEdit->text());
}
else
ui->textBrowser->append("请先于服务端连接");
}
//连接服务器槽函数clicked
void Widget::on_pushButton_clicked()
{
ui->textBrowser->append("客户端:监听的IP地址和端口:192.168.18.130,9999");
//连接服务端,指定服务端的IP地址或端口
tcpSocket->connectToHost(QHostAddress("192.168.18.130"), 9999);
}
//断开连接槽函数clicked
void Widget::on_pushButton_2_clicked()
{
//断开连接
tcpSocket->disconnectFromHost();
}
执行效果如下:

客户端的端口是随机的,假设多个客户端连接主机的话,它的端口肯定是不一样的。
我们还需要修改一些逻辑:
首先:


首先我们停止监听初始化的时候是不可用的。


我们再次执行测试PASS!!!

我们再跑一个客户端出来:



这里实现了服务器和客户端 一对多的通信!