TCP(Transmission Control Protocol)是一种被大多数 Internet 网络协议( 如 HTTP 和 FTP 〕用于数据传输的低级网络协议,它是可靠的 、面向流、面向连接的传输协议,特别适合用于连续数据传输 。
网络编程必须要创建服务端,客户端,这里为了方便,在本机通过网络实现这个功能模块。
先让我们看下效果图吧:
就是一个简单的通信模式:
QTcpServer 类的主要接口函数:
QAbstractSocket类的主要接口函数:
建立项目一定要记得在你的.pro 文件里加上 QT += network 不然找不到对应的文件
TCP 服务器端程序设计:
首先是界面布局,这里就不多讲了,如下图:
对应的头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpServer>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QLabel *LabListen;//状态栏标签
QLabel *LabSocketState;//状态栏标签
QTcpServer *tcpServer; //TCP服务器
QTcpSocket *tcpSocket;//TCP通讯的Socket
QString getLocalIP();//获取本机IP地址
protected:
void closeEvent(QCloseEvent *event);
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
//自定义槽函数
void onNewConnection();//QTcpServer的newConnection()信号
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onClientConnected(); //Client Socket connected
void onClientDisconnected();//Client Socket disconnected
void onSocketReadyRead();//读取socket传入的数据
//UI生成的
void on_actStart_triggered();
void on_actStop_triggered();
void on_actClear_triggered();
void on_btnSend_clicked();
void on_actHostInfo_triggered();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
cpp 文件部分慢慢给你分析: 在cpp文件中加上头文件
#include <QtNetwork>
在构造函数中添加一下程序:
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
LabListen=new QLabel("监听状态:");
LabListen->setMinimumWidth(150);
ui->statusBar->addWidget(LabListen);
LabSocketState=new QLabel("Socket状态:");//
LabSocketState->setMinimumWidth(200);
ui->statusBar->addWidget(LabSocketState);
QString localIP=getLocalIP();//本机IP
this->setWindowTitle(this->windowTitle()+"----本机IP:"+localIP);
ui->comboIP->addItem(localIP);
tcpServer=new QTcpServer(this);
connect(tcpServer,SIGNAL(newConnection()),this,SLOT(onNewConnection()));
}
QString MainWindow::getLocalIP()
{//获取本机IPv4地址
QString hostName=QHostInfo::localHostName();//本地主机名
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QString localIP="";
QList<QHostAddress> addList=hostInfo.addresses();//
if (!addList.isEmpty())
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost=addList.at(i);
if (QAbstractSocket::IPv4Protocol==aHost.protocol())
{
localIP=aHost.toString();
break;
}
}
return localIP;
}
MainWindow 的构造函数创建状态栏上的标签用于信息显示 ,调用 自定义函数 getLocallP()获取本机 IP 地址,并显示到标题栏上。创建 QTcpServer 实例 tcpServer , 并将其 newConnection()信号与 onNewConnection()槽函数关联。
有关获取主机信息的部分可以看上一节的编程,传送门:
void MainWindow::onNewConnection()
{
// ui->plainTextEdit->appendPlainText("有新连接");
tcpSocket = tcpServer->nextPendingConnection(); //创建socket
connect(tcpSocket, SIGNAL(connected()),
this, SLOT(onClientConnected()));
onClientConnected();//
connect(tcpSocket, SIGNAL(disconnected()),
this, SLOT(onClientDisconnected()));
connect(tcpSocket,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
onSocketStateChange(tcpSocket->state());
connect(tcpSocket,SIGNAL(readyRead()),
this,SLOT(onSocketReadyRead()));
}
tcpServer 所关联的函数数据,启动后会对应执行。
网络监听与 socket 连接的建立:
void MainWindow::on_actStart_triggered()
{//开始监听
QString IP=ui->comboIP->currentText();//IP地址
quint16 port=ui->spinPort->value();//端口
QHostAddress addr(IP);
tcpServer->listen(addr,port);//
// tcpServer->listen(QHostAddress::LocalHost,port);// Equivalent to QHostAddress("127.0.0.1").
ui->plainTextEdit->appendPlainText("**开始监听...");
ui->plainTextEdit->appendPlainText("**服务器地址:"
+tcpServer->serverAddress().toString());
ui->plainTextEdit->appendPlainText("**服务器端口:"
+QString::number(tcpServer->serverPort()));
ui->actStart->setEnabled(false);
ui->actStop->setEnabled(true);
LabListen->setText("监听状态:正在监听");
}
程序读取窗口上设置的监昕地址和监昕端口 , 然后调用 QTcpServer 的 listen()函数开始监昕 。TCP 服务器在本机上监昕, 所 以 IP 地址可以是表示本机的“ 127 . 0 .0 . 1 ”,或是本机的实际 IP ,亦或是常量 QHostAddress::LocalHost,
所以在本机上写这句话也是一样的:
tcpServer->lis ten(QHostAddress : LocalHost,port);
tcpServer 开始监听后, TCPClient 就可以通过 IP 地址和端口连接到此服务器 。当有客户端接入时, tcpServer 会发射 newConnection()信号. 这个时候才会调用。
- connected()信号,客户端 socket 连接建立时发射此信号 ;
- disconnected()信号,客户端 socket 连接断开时发射此信号 ;
- stateChanged(), 本程序 的 socket 状态变化时发射此信号 ;
- readyRead(), 本程序的 socket 的读取缓冲区有新数据时发射此信号。
下面是对应的程序函数:
void MainWindow::onClientConnected()
{//客户端接入时
ui->plainTextEdit->appendPlainText("**client socket connected");
ui->plainTextEdit->appendPlainText("**peer address:"+
tcpSocket->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:"+
QString::number(tcpSocket->peerPort()));
}
void MainWindow::onClientDisconnected()
{//客户端断开连接时
ui->plainTextEdit->appendPlainText("**client socket disconnected");
tcpSocket->deleteLater();
// deleteLater();//QObject::deleteLater();
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{//socket状态变化时
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
LabSocketState->setText("scoket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
LabSocketState->setText("scoket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
LabSocketState->setText("scoket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
LabSocketState->setText("scoket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
LabSocketState->setText("scoket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
LabSocketState->setText("scoket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
LabSocketState->setText("scoket状态:ListeningState");
}
}
TCP 服务器停止监听,只 需调用 QTcpServer 的 close() 函数即可:
void MainWindow::on_actStop_triggered()
{//停止监听
if (tcpServer->isListening()) //tcpServer正在监听
{
tcpServer->close();//停止监听
ui->actStart->setEnabled(true);
ui->actStop->setEnabled(false);
LabListen->setText("监听状态:已停止监听");
}
}
与 TCPClient 的 数据通信:
单击窗口上的“发送消息飞将文本框里的字符串发送给客户端,其实现代码如下:
void MainWindow::on_btnSend_clicked()
{//发送一行字符串,以换行符结束
QString msg=ui->editMsg->text();
ui->plainTextEdit->appendPlainText("[out] "+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
QByteArray str=msg.toUtf8();
str.append('\n');//添加一个换行符
tcpSocket->write(str);
}
void MainWindow::onSocketReadyRead()
{//读取缓冲区行文本
// QStringList lines;
while(tcpSocket->canReadLine())
ui->plainTextEdit->appendPlainText("[in] "+tcpSocket->readLine());
// lines.append(clientConnection->readLine());
}
这个读取的是文本读取,要是2进制采用其他方式。
剩下的代码块:
void MainWindow::closeEvent(QCloseEvent *event)
{//关闭窗口时停止监听
if (tcpServer->isListening())
tcpServer->close();;//停止网络监听
event->accept();
}
void MainWindow::on_actClear_triggered()
{
ui->plainTextEdit->clear();
}
void MainWindow::on_actHostInfo_triggered()
{//获取本机地址
QString hostName=QHostInfo::localHostName();//本地主机名
ui->plainTextEdit->appendPlainText("本机主机名:"+hostName+"\n");
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QList<QHostAddress> addList=hostInfo.addresses();//
if (!addList.isEmpty())
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost=addList.at(i);
if (QAbstractSocket::IPv4Protocol==aHost.protocol())
{
QString IP=aHost.toString();
ui->plainTextEdit->appendPlainText("本机IP地址:"+aHost.toString());
if (ui->comboIP->findText(IP)<0)
ui->comboIP->addItem(IP);
}
}
}
已上是服务端部分,客户端部分请看下面:
TCP 客户端程序设计
对应的头文件:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QTcpSocket>
#include <QLabel>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QTcpSocket *tcpClient; //socket
QLabel *LabSocketState; //状态栏显示标签
QString getLocalIP();//获取本机IP地址
protected:
void closeEvent(QCloseEvent *event);
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private slots:
//自定义槽函数
void onConnected();
void onDisconnected();
void onSocketStateChange(QAbstractSocket::SocketState socketState);
void onSocketReadyRead();//读取socket传入的数据
//
void on_actConnect_triggered();
void on_actDisconnect_triggered();
void on_actClear_triggered();
void on_btnSend_clicked();
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
对应的cpp:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QHostAddress>
#include <QHostInfo>
QString MainWindow::getLocalIP()
{
QString hostName=QHostInfo::localHostName();//本地主机名
QHostInfo hostInfo=QHostInfo::fromName(hostName);
QString localIP="";
QList<QHostAddress> addList=hostInfo.addresses();//
if (!addList.isEmpty())
for (int i=0;i<addList.count();i++)
{
QHostAddress aHost=addList.at(i);
if (QAbstractSocket::IPv4Protocol==aHost.protocol())
{
localIP=aHost.toString();
break;
}
}
return localIP;
}
void MainWindow::closeEvent(QCloseEvent *event)
{
if (tcpClient->state()==QAbstractSocket::ConnectedState)
tcpClient->disconnectFromHost();
event->accept();
}
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);
QString localIP=getLocalIP();//本机IP
this->setWindowTitle(this->windowTitle()+"----本机IP:"+localIP);
ui->comboServer->addItem(localIP);
connect(tcpClient,SIGNAL(connected()),this,SLOT(onConnected()));
connect(tcpClient,SIGNAL(disconnected()),this,SLOT(onDisconnected()));
connect(tcpClient,SIGNAL(stateChanged(QAbstractSocket::SocketState)),
this,SLOT(onSocketStateChange(QAbstractSocket::SocketState)));
connect(tcpClient,SIGNAL(readyRead()),
this,SLOT(onSocketReadyRead()));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onConnected()
{ //connected()信号槽函数
ui->plainTextEdit->appendPlainText("**已连接到服务器");
ui->plainTextEdit->appendPlainText("**peer address:"+
tcpClient->peerAddress().toString());
ui->plainTextEdit->appendPlainText("**peer port:"+
QString::number(tcpClient->peerPort()));
ui->actConnect->setEnabled(false);
ui->actDisconnect->setEnabled(true);
}
void MainWindow::onDisconnected()
{//disConnected()信号槽函数
ui->plainTextEdit->appendPlainText("**已断开与服务器的连接");
ui->actConnect->setEnabled(true);
ui->actDisconnect->setEnabled(false);
}
void MainWindow::onSocketReadyRead()
{//readyRead()信号槽函数
while(tcpClient->canReadLine())
ui->plainTextEdit->appendPlainText("[in] "+tcpClient->readLine());
}
void MainWindow::onSocketStateChange(QAbstractSocket::SocketState socketState)
{//stateChange()信号槽函数
switch(socketState)
{
case QAbstractSocket::UnconnectedState:
LabSocketState->setText("scoket状态:UnconnectedState");
break;
case QAbstractSocket::HostLookupState:
LabSocketState->setText("scoket状态:HostLookupState");
break;
case QAbstractSocket::ConnectingState:
LabSocketState->setText("scoket状态:ConnectingState");
break;
case QAbstractSocket::ConnectedState:
LabSocketState->setText("scoket状态:ConnectedState");
break;
case QAbstractSocket::BoundState:
LabSocketState->setText("scoket状态:BoundState");
break;
case QAbstractSocket::ClosingState:
LabSocketState->setText("scoket状态:ClosingState");
break;
case QAbstractSocket::ListeningState:
LabSocketState->setText("scoket状态:ListeningState");
}
}
void MainWindow::on_actConnect_triggered()
{//连接到服务器
QString addr=ui->comboServer->currentText();
quint16 port=ui->spinPort->value();
tcpClient->connectToHost(addr,port);
// tcpClient->connectToHost(QHostAddress::LocalHost,port);
}
void MainWindow::on_actDisconnect_triggered()
{//断开与服务器的连接
if (tcpClient->state()==QAbstractSocket::ConnectedState)
tcpClient->disconnectFromHost();
}
void MainWindow::on_actClear_triggered()
{
ui->plainTextEdit->clear();
}
void MainWindow::on_btnSend_clicked()
{//发送数据
QString msg=ui->editMsg->text();
ui->plainTextEdit->appendPlainText("[out] "+msg);
ui->editMsg->clear();
ui->editMsg->setFocus();
QByteArray str=msg.toUtf8();
str.append('\n');
tcpClient->write(str);
}
在窗口上设置服务器 IP 地址和端口后,调用 QTcpSocket 的函数 connectToHost()连接到服务器,也可以使用 disconnectFromHost()函数断开与服务器的连接。
TCPClient 与 TCP Server 之间采用基于行的数据通信协议 。单击 “发送消息”按钮将发送一行字符串。在 readyRead()信号的槽函数里读取行字符串。
实例 TCPServer 和 TCPClient 只是简单演示了 TCP 通信的基本原理, TCPServer 只允许一个TCPClient 客户端接入 。 而一般的 TCP 服务器程序允许多个客户端接入,为了使每个 socket 连接独立通信互不影响 , 一般采用多线程,即为一个 socket 连接创建一个线程 。
实例 TCPServer 和 TCPClient 之间的数据通信采用基于行的通信协议,只能传输字符串数据 。QTcpSocket 间接继承于 QIODevice , 可以使用数据流的方式传输二进制数据流,例如传输图片、任意格式文件等 , 但是这涉及到服务器端和客户端之间通信协议的定义,本书不具体介绍了 。