在Qt框架中,用于网络通信的套接字(Socket)类主要包括QTcpSocket和QUdpSocket。QTcpSocket用于基于TCP协议的客户端和服务端通信;QUdpSocket用于基于UDP协议的通信。
在Qt中,不论使用TCP还是UDP,都需要在项目文件中加入:
qt+= network
TCP
我对TCP三次握手的理解:
三次握手犹如两个人打电话,手机上有麦克风和扬声器。麦克风是采集声音的,扬声器是播放声音的。而两个人正常通话的前提就是双方的麦克风和扬声器都是能够正常工作的。那他们该怎么确定双方的麦克风和扬声器都是能够正常工作的呢?
我们假设打电话的两个人分别为A和B,下面我列出步骤:
- A拨通了B的手机号,说:喂?(此时没有能够确定的信息。)
- B听到了A的话,说:嗯。喂?(此时B能够确定A的麦克风是没问题的,B自己的扬声器是没问题的。A此时还没有得到任何信息。)
- A听到了B的话,说:嗯。(此时,A能够确定的是,自己的麦克风和扬声器也是没问题的。因为如果有问题的话,B就不可能回话。A也能确定,B的扬声器和麦克风也是没问题的。)
- B听到了A的话(即3中的“嗯”)。(此时,B得知了,A的扬声器也没有问题,因为如果扬声器有问题,A就不会听到自己的话,还做出回应。还得知了B自己的麦克风也没有问题。)
至此为止:A得知了双方的扬声器和麦克风没问题,B也同样得知了这一点。此时连接建立,可以正常交流。
我对TCP四次挥手的理解:
假设现在A和B已经通话了一段时间,此时A将要讲得话已经向B讲完了,想要挂断通话。
- A,说:哥们,我话说完了,你还有事儿没,没事挂了啊。
- B,说:哥们,等会,我还有一事儿没告诉你,咱们村头一只母牛生了只小牛。
- A听了没吭声。
- B接着说:行了,我话也将完了,挂了吧。
- A,说:我当啥要紧事儿,成天扯犊子,挂了。
- B听到A的话,挂断了电话。
至此为止:A和B结束了通话,也就是断开了TCP连接状态。
注意:TCP的挥手过程实际上比这个类比更复杂一些,涉及到序列号的交换、等待确认等步骤。
Qt中的TCP使用
Qt提供了对TCP协议的封装和支持,Qt中用于处理TCP的主要类是QTcpSocket和QTcpSever。
QTcpSocket类用于创建TCP客户端,它允许连接到远程主机,并通过套接字进行数据传输。
QTcpServer类用于创建TCP服务端,它可以监听指定的端口号,也可以在监听时不指定端口号,并处理客户端的连接请求。
Qt对TCP协议的封装简化了网络编程的复杂性。
具体使用流程如图所示:
每有一个客户端socket连接,就会在sever端触发newConnection信号,sever的nextPendingConnection方法会返回一个socket,使用该socket即可与客户端socket进行数据通信。
通信时,无论是服务端的socket发送数据,还是客户端的socket发送数据,都会触发对方的readyRead信号,可以在自定义的该信号的槽函数中处理数据。
效果展示
TCP服务端代码示例
服务端头文件:
#ifndef TCPSEVER_H #define TCPSEVER_H #include <QWidget> #include <QTcpServer> #include <QTcpSocket> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QHBoxLayout> #include <QVBoxLayout> #include <QMouseEvent> #include <QPainter> #include <QListView> #include <QAbstractListModel> #include <QPlainTextEdit> class SwitchButton:public QWidget{ Q_OBJECT public: SwitchButton(QWidget* parent); ~SwitchButton(); inline void setSwitch(bool isChecked){_isChecked=isChecked;update();}; protected: void mouseReleaseEvent(QMouseEvent* event)override; void paintEvent(QPaintEvent* event)override; signals: void switchChanged(bool); private: bool _isChecked=false; }; class MessageModel:public QAbstractListModel{ Q_OBJECT public: struct Message{ bool Self=false; QByteArray Data; }; public: MessageModel(QObject* parent); ~MessageModel(); inline void insertMessage(const Message& message){_insertMessage=message;insertRows(_messages.count(),1);}; protected: int rowCount(const QModelIndex& parent)const override; QVariant data(const QModelIndex& index,int role=Qt::DisplayRole)const override; bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())override; private: Message _insertMessage; QList<Message> _messages; }; class TCPServer : public QWidget{ Q_OBJECT public: TCPServer(QWidget *parent = nullptr); ~TCPServer(); private slots: void listenButtonSlot(bool); void newConnectionSlot(); void readyReadSlot(); void sendButtonSlot(); private: QTcpServer* _server; QList<QTcpSocket*> _sockets; private://界面元素 QLabel* _addressLabel; QLineEdit* _addressLine; QLabel* _portLabel; QLineEdit* _portLine; QLabel* _listenLabel; SwitchButton* _listenButton; QHBoxLayout* _listenLayout; QListView* _messageView; MessageModel* _messageModel; QPlainTextEdit* _textEdit; QPushButton* _sendButton; QVBoxLayout* _layout; }; #endif // TCPSEVER_H
服务端源文件:
#include "tcpsever.h" SwitchButton::SwitchButton(QWidget* parent):QWidget(parent){ setFixedSize(35,20); } SwitchButton::~SwitchButton(){} void SwitchButton::mouseReleaseEvent(QMouseEvent* event){ if(rect().contains(event->pos())){ _isChecked=!_isChecked; emit switchChanged(_isChecked); update(); } } void SwitchButton::paintEvent(QPaintEvent* event){ Q_UNUSED(event); QColor backColor=_isChecked?QColor("#34b92e"):QColor("#8f8f8f"); QColor frontColor("#FFFFFF"); QRect buttonRect=_isChecked?QRect(width()-height()+2,2,height()-4,height()-4):QRect(2,2,height()-4,height()-4); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true); painter.setPen(Qt::NoPen); painter.setBrush(backColor); painter.drawRoundedRect(rect(),height()/2,height()/2); painter.setBrush(frontColor); painter.drawEllipse(buttonRect); } MessageModel::MessageModel(QObject* parent):QAbstractListModel(parent){} MessageModel::~MessageModel(){} int MessageModel::rowCount(const QModelIndex& parent)const{ if (parent.isValid())return 0; return _messages.count(); } QVariant MessageModel::data(const QModelIndex& index,int role)const{ if (!index.isValid())return QVariant(); if(role==Qt::DisplayRole){ return _messages.at(index.row()).Data; }else if(role==Qt::TextAlignmentRole){ return _messages.at(index.row()).Self?QVariant(Qt::AlignRight|Qt::AlignVCenter):QVariant(Qt::AlignLeft|Qt::AlignVCenter); } return QVariant(); } bool MessageModel::insertRows(int row, int count, const QModelIndex& parent){ if (row < 0 || row > rowCount(parent) || count <= 0)return false; beginInsertRows(parent, row, row); _messages.insert(row,_insertMessage); endInsertRows(); return true; } TCPServer::TCPServer(QWidget *parent): QWidget(parent){ _server=new QTcpServer(this); _addressLabel=new QLabel("地址:",this); _addressLine=new QLineEdit(this); _portLabel=new QLabel("端口:",this); _portLine=new QLineEdit(this); _listenLabel=new QLabel("监听",this); _listenButton=new SwitchButton(this); _listenLayout=new QHBoxLayout(); _listenLayout->addWidget(_addressLabel); _listenLayout->addWidget(_addressLine); _listenLayout->addWidget(_portLabel); _listenLayout->addWidget(_portLine); _listenLayout->addWidget(_listenLabel); _listenLayout->addWidget(_listenButton); _messageView=new QListView(this); _messageModel=new MessageModel(this); _messageView->setModel(_messageModel); _textEdit=new QPlainTextEdit(this); _textEdit->setFixedHeight(100); QFont font=QFont("Xindayungu",10); _textEdit->setFont(font); _sendButton=new QPushButton("发送",this); _layout=new QVBoxLayout(this); _layout->addLayout(_listenLayout); _layout->addWidget(_messageView); _layout->addWidget(_textEdit); _layout->addWidget(_sendButton); connect(_listenButton,&SwitchButton::switchChanged,this,&TCPServer::listenButtonSlot); connect(_server,&QTcpServer::newConnection,this,&TCPServer::newConnectionSlot); connect(_sendButton,&QPushButton::clicked,this,&TCPServer::sendButtonSlot); } TCPServer::~TCPServer(){ _listenLayout->deleteLater(); } void TCPServer::listenButtonSlot(bool isListen){ if(isListen){ QString address=_addressLine->text(); QString port=_portLine->text(); quint16 uintport=port.toInt(0); if(!_server->listen(QHostAddress(address),uintport)){ _listenButton->setSwitch(false); qDebug()<<"监听失败"; return; } qDebug()<<"监听成功"; qDebug()<<"ServerAddress:"<<_server->serverAddress(); qDebug()<<"ServerPort:"<<_server->serverPort(); }else{ _server->close(); } } void TCPServer::newConnectionSlot(){ QTcpSocket* socket=_server->nextPendingConnection(); socket->setParent(_server); _sockets.append(socket); connect(socket,&QTcpSocket::readyRead,this,&TCPServer::readyReadSlot); qDebug()<<"监听到:"<<socket; } void TCPServer::readyReadSlot(){ QByteArray data=_sockets.at(0)->readAll(); MessageModel::Message message; message.Self=false; message.Data=data; _messageModel->insertMessage(message); } void TCPServer::sendButtonSlot(){ if(_sockets.count()==0){ _textEdit->clear(); return; } QTcpSocket* socket=_sockets.at(0); QString text=_textEdit->toPlainText(); socket->write(text.toUtf8()); _textEdit->clear(); if(!socket->flush())return; MessageModel::Message message; message.Self=true; message.Data=text.toUtf8(); _messageModel->insertMessage(message); }
TCP客户端代码示例
客户端头文件:
#ifndef TCPCLIENT_H #define TCPCLIENT_H #include <QWidget> #include <QTcpSocket> #include <QLabel> #include <QLineEdit> #include <QPushButton> #include <QHBoxLayout> #include <QVBoxLayout> #include <QMouseEvent> #include <QPainter> #include <QListView> #include <QAbstractListModel> #include <QPlainTextEdit> class SwitchButton:public QWidget{ Q_OBJECT public: SwitchButton(QWidget* parent); ~SwitchButton(); inline void setSwitch(bool isChecked){_isChecked=isChecked;update();}; protected: void mouseReleaseEvent(QMouseEvent* event)override; void paintEvent(QPaintEvent* event)override; signals: void switchChanged(bool); private: bool _isChecked=false; }; class MessageModel:public QAbstractListModel{ Q_OBJECT public: struct Message{ bool Self=false; QByteArray Data; }; public: MessageModel(QObject* parent); ~MessageModel(); inline void insertMessage(const Message& message){_insertMessage=message;insertRows(_messages.count(),1);}; protected: int rowCount(const QModelIndex& parent)const override; QVariant data(const QModelIndex& index,int role=Qt::DisplayRole)const override; bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex())override; private: Message _insertMessage; QList<Message> _messages; }; class TCPClient : public QWidget{ Q_OBJECT public: TCPClient(QWidget *parent = nullptr); ~TCPClient(); private slots: void connectButtonSlot(bool isConnect); void readyReadSlot(); void sendButtonSlot(); private: QTcpSocket* _socket; private://界面元素 QLabel* _addressLabel; QLineEdit* _addressLine; QLabel* _portLabel; QLineEdit* _portLine; QLabel* _connectLabel; SwitchButton* _connectButton; QHBoxLayout* _connectLayout; QListView* _messageView; MessageModel* _messageModel; QPlainTextEdit* _textEdit; QPushButton* _sendButton; QVBoxLayout* _layout; }; #endif // TCPCLIENT_H
客户端源文件:
#include "tcpclient.h" SwitchButton::SwitchButton(QWidget* parent):QWidget(parent){ setFixedSize(35,20); } SwitchButton::~SwitchButton(){} void SwitchButton::mouseReleaseEvent(QMouseEvent* event){ if(rect().contains(event->pos())){ _isChecked=!_isChecked; emit switchChanged(_isChecked); update(); } } void SwitchButton::paintEvent(QPaintEvent* event){ Q_UNUSED(event); QColor backColor=_isChecked?QColor("#34b92e"):QColor("#8f8f8f"); QColor frontColor("#FFFFFF"); QRect buttonRect=_isChecked?QRect(width()-height()+2,2,height()-4,height()-4):QRect(2,2,height()-4,height()-4); QPainter painter(this); painter.setRenderHint(QPainter::Antialiasing,true); painter.setPen(Qt::NoPen); painter.setBrush(backColor); painter.drawRoundedRect(rect(),height()/2,height()/2); painter.setBrush(frontColor); painter.drawEllipse(buttonRect); } MessageModel::MessageModel(QObject* parent):QAbstractListModel(parent){} MessageModel::~MessageModel(){} int MessageModel::rowCount(const QModelIndex& parent)const{ if (parent.isValid())return 0; return _messages.count(); } QVariant MessageModel::data(const QModelIndex& index,int role)const{ if (!index.isValid())return QVariant(); if(role==Qt::DisplayRole){ return _messages.at(index.row()).Data; }else if(role==Qt::TextAlignmentRole){ return _messages.at(index.row()).Self?QVariant(Qt::AlignRight|Qt::AlignVCenter):QVariant(Qt::AlignLeft|Qt::AlignVCenter); } return QVariant(); } bool MessageModel::insertRows(int row, int count, const QModelIndex& parent){ if (row < 0 || row > rowCount(parent) || count <= 0)return false; beginInsertRows(parent, row, row); _messages.insert(row,_insertMessage); endInsertRows(); return true; } TCPClient::TCPClient(QWidget *parent): QWidget(parent){ _socket=new QTcpSocket(this); _addressLabel=new QLabel("地址:",this); _addressLine=new QLineEdit(this); _portLabel=new QLabel("端口",this); _portLine=new QLineEdit(this); _connectLabel=new QLabel("连接",this); _connectButton=new SwitchButton(this); _connectLayout=new QHBoxLayout(); _connectLayout->addWidget(_addressLabel); _connectLayout->addWidget(_addressLine); _connectLayout->addWidget(_portLabel); _connectLayout->addWidget(_portLine); _connectLayout->addWidget(_connectLabel); _connectLayout->addWidget(_connectButton); _messageView=new QListView(this); _messageModel=new MessageModel(this); _messageView->setModel(_messageModel); _textEdit=new QPlainTextEdit(this); _textEdit->setFixedHeight(100); QFont font=QFont("Xindayungu",10); _textEdit->setFont(font); _sendButton=new QPushButton("发送",this); _layout=new QVBoxLayout(this); _layout->addLayout(_connectLayout); _layout->addWidget(_messageView); _layout->addWidget(_textEdit); _layout->addWidget(_sendButton); connect(_connectButton,&SwitchButton::switchChanged,this,&TCPClient::connectButtonSlot); connect(_socket,&QTcpSocket::readyRead,this,&TCPClient::readyReadSlot); connect(_sendButton,&QPushButton::clicked,this,&TCPClient::sendButtonSlot); } TCPClient::~TCPClient(){ _connectLayout->deleteLater(); } void TCPClient::connectButtonSlot(bool isConnect){ if(isConnect){ QString address=_addressLine->text(); QString port=_portLine->text(); quint16 uintport=port.toULong(0); _socket->connectToHost(address,uintport); if(!_socket->waitForConnected(3000)){ qDebug()<<"连接超时"; _connectButton->setSwitch(false); return; } qDebug()<<_socket; qDebug()<<"连接成功"; }else{ _socket->abort(); _socket->close(); qDebug()<<"断开成功"; } } void TCPClient::readyReadSlot(){ QByteArray data=_socket->readAll(); MessageModel::Message message; message.Self=false; message.Data=data; _messageModel->insertMessage(message); } void TCPClient::sendButtonSlot(){ QString text=_textEdit->toPlainText(); _socket->write(text.toUtf8()); _textEdit->clear(); if(!_socket->flush())return; MessageModel::Message message; message.Self=true; message.Data=text.toUtf8(); _messageModel->insertMessage(message); }
简单的示例就是这样了。