一、简介
TCP通信必须先建立TCP链接,通信端分为客户端和服务器端。QT提供了QTcpServer类和QTcpSocket类用于建立TCP通信应用程序。QTcpServer用于端口监听,建立服务器;QTcpSocket用于建立连接后使用套接字(Socket)进行通信。
服务器端程序首先要使用QTcpServer::listen()开始服务器端监听,可以指定监听的IP地址和端口,一般一个服务程序只监听某个端口的网络连接。
本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(Qt实战项目,C++语言基础,C++设计模式,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓
当有新的客户端接入时,QTcpServer内部的incomingConnection()函数会创建一个与客户端连接的QTcpSocket对象,然后发射信号newConnection()。在newConnection()信号的槽函数中,可以使用nextPendingConnection()接受客户端的链接,然后使用QTcpSocket与客户端进行通信。
所以在客户端与服务器端建立TCP链接以后,具体的数据通信是通过QTcpSocket完成的。
客户端的QTcpSocket实列首先通过connectToHost()尝试链接到服务器,需要指定服务器的IP地址和端口。connectToHost()是异步方式链接服务器,不会阻塞程序运行,链接后发射connected()信号。
与服务器端建立socket链接后,就可以向缓冲区写入数据或从接收缓冲区读取数据,实现数据的通信。当缓冲区有新的数据进入时,会发射readyRead()信号,一般在此信号的槽函数里读取缓冲区数据。
QTcpSocket是从QIODevice间接继承的,所以可以使用流数据读写功能,一个QTcpSocket实列既可以接收数据也可以发送数据,且接收和发送是异步工作的,有各自的缓冲区。
二、TCPServer(TCP服务端的设计)
TCPServer程序具有如下功能:
根据指定IP地址(本机地址)和端口打开网络监听,有客户端连接时创建socket链接
采用基于行的数据通信协议,可以接收客户端发来的消息,也可以向客户端发送消息
在状态栏显示服务器监听状态和socket的状态
ui界面如下:
程序设计
TCPServer是一个窗口基于QMainWindow的应用程序,程序名称为QTCPServer。其中mainwindow.h如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QTcpServer>
#include <QHostInfo>
#include <QTcpSocket>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QLabel *LabListen;
QLabel *LabSocketState;
QTcpServer *tcpServer;
QTcpSocket *tcpSocket;
QStringList getLocalIP();
protected:
void closeEvent(QCloseEvent *event);
private slots:
void onNewConnection(); //QTcpServer的newConnection()信号
void onSocketStateChange(QAbstractSocket::SocketState);
void onClientConnected(); //Client Socket connected
void onClientDisconnected(); //Client Socket disconnected
void onSocketReadRead(); //读取socket传入的数据
void on_act_start_triggered();
void on_act_stop_triggered();
void on_pb_sendmessage_clicked();
};
#endif // MAINWINDOW_H
mainwindow.cpp如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
LabListen = new QLabel("监听状态:");
LabListen->setMinimumWidth(200);
ui->statusBar->addWidget(LabListen);
LabSocketState = new QLabel("Socket 状态:");
LabSocketState->setMinimumWidth(250);
ui->statusBar->addWidget(LabSocketState);
QStringList localeIP = getLocalIP(); //本机IP
ui->cb_address->addItems(localeIP);
tcpServer = new QTcpServer(this);
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
}
MainWindow::~MainWindow()
{
delete ui;
}
QStringList MainWindow::getLocalIP()
{
//获取本机IPV4地址
QString hostName = QHostInfo::localHostName(); //本地主机名
QHostInfo hostInfo = QHostInfo::fromName(hostName); //根据主机名获取ip
QStringList localIp;
QList<QHostAddress> addList = hostInfo.addresses();
if(!addList.isEmpty())
{
for(int i = 0;i < addList.count();i++)
{
QHostAddress aAddress = addList.at(i);
if(QAbstractSocket::IPv4Protocol == aAddress.protocol())
{
localIp.append(aAddress.toString());
}
}
}
return localIp;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
}
void MainWindow::onNewConnection()
{
tcpSocket = tcpServer->nextPendingConnection(); //获取socket
connect(tcpSocket,SIGNAL(connected()),this,SLOT(onClientConnected())); //connected()信号,客户端socket连接时发射此信号
onClientConnected();
connect(tcpSocket,SIGNAL(disconnected()),this,SLOT(onClientDisconnected())); //disconnected()信号,客户端socket断开时发射此信号
connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
connect(tcpSocket,SIGNAL(readyRead()),this,SLOT(onSocketReadRead()));
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketstate)
{
//socket状态变化
switch (socketstate)
{
case QAbstractSocket::UnconnectedState:
LabSocketState->setText("socket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
LabSocketState->setText("socket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
LabSocketState->setText("socket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
LabSocketState->setText("socket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
LabSocketState->setText("socket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
LabSocketState->setText("socket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
LabSocketState->setText("socket状态:ListeningState");
break;
}
}
void MainWindow::onClientConnected()
{
//客户端接入时
ui->plainTextEdit->appendPlainText("**client socket connected");
ui->plainTextEdit->appendPlainText("**peer address:"+tcpSocket->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:"+tcpSocket->peerPort());
}
void MainWindow::onClientDisconnected()
{ //客户端断开的时候
ui->plainTextEdit->appendPlainText("**client socket disconnected");
tcpSocket->deleteLater(); //deleteLater 会在当前对象的所有事件处理完成后再删除对象
}
void MainWindow::onSocketReadRead()
{
//读取缓冲区行文本
while (tcpSocket->canReadLine()) {
ui->plainTextEdit->appendPlainText("[In] "+tcpSocket->readLine());
}
}
void MainWindow::on_act_start_triggered()
{
//开始监听
QString IP = ui->cb_address->currentText(); //获取IP
quint16 port = ui->sb_port->value(); //获取端口
// QHostAddress addr(IP); //
tcpServer->listen(QHostAddress::LocalHost,port); //开始监听
ui->plainTextEdit->appendPlainText("**开始监听...");
ui->plainTextEdit->appendPlainText("**服务器地址:"+tcpServer->serverAddress().toString());
ui->plainTextEdit->appendPlainText("**服务器端口:"+QString::number(tcpServer->serverPort()));
ui->act_start->setEnabled(false);
ui->act_stop->setEnabled(true);
LabListen->setText("监听状态:正在监听");
}
void MainWindow::on_act_stop_triggered()
{
//停止监听
if(tcpServer->isListening()) //tcpServer正在监听
{
tcpServer->close();
ui->act_start->setEnabled(true);
ui->act_stop->setEnabled(false);
}
LabListen->setText("监听状态:已经停止监听");
}
void MainWindow::on_pb_sendmessage_clicked()
{
//发送一行字符串,以换行符结束
QString msg = ui->le_message->text();
ui->plainTextEdit->appendPlainText("[Out] "+msg);
ui->le_message->clear();
ui->le_message->setFocus();
QByteArray str = msg.toUtf8();
str.append("\n"); //添加一个换行符
tcpSocket->write(str);
}
作为TCP服务器,QTcpServer类需要调用listen()在本机某个IP地址和端口上开始TCP监听,以等待TCP客户端的接入。单击主窗口上“开始监听”按钮可以开始网络监听。
tcpServer开始监听后,TCPClient就可以通过IP地址和端口连接到此服务器。当客户端接入时,tcpServer会发射newConnection()信号。
程序首先通过nextPendingConnection()函数获取与连接进行通信的QTcpSocket对象实例tcpSocket,然后将tcpSocket的几个信号与相应的槽函数链接起来,QTcpSocket的这几个信号的作用是:
connected()信号,客户端socket连接建立时发射此信号
disconnected()信号,客户端socket连接断开时发射此信号
stateChanged(),本程序的socket状态发生变化时发射此信号
readyRead(),本程序的socket的读取缓冲区有新数据时发射此信号
TCP服务器停止监听,只需要调用QTcpServer的close()函数即可。
三、TCPClient(TCP客户端的设计)
TCPClient程序具有如下功能:
- 通过IP地址和端口号连接到服务器
- 采用基于行的数据通信协议,与服务器端收发消息
- 处理QTcpSocket的StateChange()信号,在状态栏显示socket的状态
ui界面如下:
程序设计
TCPClient也是一个窗口基于QMainWindow的应用程序,其中主窗口定义mainwindow.h如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QLabel>
#include <QTcpServer>
#include <QTcpSocket>
#include <QHostInfo>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QLabel *LabSocketState;
QTcpSocket *tcpClient; //socket
QStringList getLocalIP();
protected:
void closeEvent(QCloseEvent *event);
private slots:
void onCennected();
void onDiscennected();
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead(); //读取socket传入的数据
void on_act_connect_triggered();
void on_act_disconnect_triggered();
void on_pb_send_clicked();
};
#endif // MAINWINDOW_H
mainwindow.cpp如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
tcpClient = new QTcpSocket(this); //创建socket变量
LabSocketState = new QLabel("socket状态:"); //状态栏标签
LabSocketState->setMinimumWidth(250);
ui->statusBar->addWidget(LabSocketState);
QStringList localIp = getLocalIP();
ui->cb_address->addItems(localIp);
connect(tcpClient,SIGNAL(connected()),this,SLOT(onCennected()));
connect(tcpClient,SIGNAL(disconnected()),this,SLOT(onDiscennected()));
connect(tcpClient,SIGNAL(stateChanged(QAbstractSocket::SocketState)),this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
connect(tcpClient,SIGNAL(readyRead()),SLOT(onSocketReadyRead()));
}
MainWindow::~MainWindow()
{
delete ui;
}
QStringList MainWindow::getLocalIP()
{
QString localeName = QHostInfo::localHostName();
QHostInfo ipInfo = QHostInfo::fromName(localeName);
QStringList ipList;
QList<QHostAddress> addList = ipInfo.addresses();
if(!addList.isEmpty())
{
for(int i = 0; i < addList.count(); i++)
{
QHostAddress aHost = addList.at(i);
if(QAbstractSocket::IPv4Protocol == aHost.protocol())
{
ipList.append(aHost.toString());
}
}
}
return ipList;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
Q_UNUSED(event);
}
void MainWindow::onCennected()
{
//connected()信号槽函数
ui->plainTextEdit->appendPlainText("**已连接到服务器");
ui->plainTextEdit->appendPlainText("**peer address:"+tcpClient->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:"+QString::number(tcpClient->peerPort()));
ui->act_connect->setEnabled(false);
ui->act_disconnect->setEnabled(true);
}
void MainWindow::onDiscennected()
{
//disconnected()信号槽函数
ui->plainTextEdit->appendPlainText("**已断开与服务器的连接");
ui->act_connect->setEnabled(true);
ui->act_disconnect->setEnabled(false);
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{
//socket状态变化
switch (socketState)
{
case QAbstractSocket::UnconnectedState:
LabSocketState->setText("socket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
LabSocketState->setText("socket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
LabSocketState->setText("socket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
LabSocketState->setText("socket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
LabSocketState->setText("socket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
LabSocketState->setText("socket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
LabSocketState->setText("socket状态:ListeningState");
break;
}
}
void MainWindow::onSocketReadyRead()
{
//readyRead()信号槽函数
while(tcpClient->canReadLine())
{
ui->plainTextEdit->appendPlainText("[in] "+tcpClient->readLine());
}
}
void MainWindow::on_act_connect_triggered()
{
//"连接到服务器"按钮
QString addr = ui->cb_address->currentText();
qint16 port = ui->sb_port->value();
tcpClient->connectToHost(addr,port);
}
void MainWindow::on_act_disconnect_triggered()
{
if(tcpClient->state() == QAbstractSocket::ConnectedState)
{
tcpClient->disconnectFromHost();
}
}
void MainWindow::on_pb_send_clicked()
{
QString msg = ui->le_message->text();
ui->plainTextEdit->appendPlainText("[out] "+msg);
ui->le_message->clear();
ui->le_message->setFocus();
QByteArray str = msg.toUtf8();
str.append("\n");
tcpClient->write(str);
}
在窗口上设置服务器IP地址后,调用QTcpSocket的函数connectToHost()链接到服务器,也可以使用disconnectFromHost()函数断开与服务器的链接。
其中,槽函数onSocketStateChange()的功能和代码与TcpServer完全一样。
TCPClient与TCPServer之间采用基于行的数据通信协议。单击“发送消息”按钮将发送一行字符串。在readyRead()信号的槽函数里进行字符串的读取。
实例TCPServer和TCPClient只是简单的演示了TCP通信的原理,TCPServer只允许一个TCPClient客户接入。而一般的TCP服务器程序允许多个客户接入,为了使每个socket链接独立通信且互不影响,一般采用多线程,即为一个socket链接创建一个新的线程。
本文福利,莬费领取Qt开发学习资料包、技术视频,内容包括(Qt实战项目,C++语言基础,C++设计模式,Qt编程入门,QT信号与槽机制,QT界面开发-图像绘制,QT网络,QT数据库编程,QT项目实战,QSS,OpenCV,Quick模块,面试题等等)↓↓↓↓↓↓见下面↓↓文章底部点击莬费领取↓↓