前言
之前项目上用到了TCP通信,作为TCP的服务端上位机与下位机进行控制信号传输。
这篇博客就对QT中使用TCP通信理一个简单的demo,做一个简单的TCP服务端和客户端的通信。
因为时间有限,这里就阐述一下基本原理和代码实现,具体的demo参考的是《QT5.9C++开发指南》
具体效果图如下
1. TCP通信原理和流程阐述
1.1 TCP 通信原理简述
TCP通信主要是三次握手和四次挥手,前者是建立连接,后者是断开连接。
上图是三次握手的一个基本流程图:
首先客户机向服务器申请同步,即向服务器申请连接。
服务器接收到请求,返回一个确认帧,告诉客户机已经收到你的请求,同意连接。
客户机收到同意请求信息之后,还要告诉服务器它已经收到服务器的确认请求,接下来可以传输数据。
上面是一个简单的TCP建立连接的阐述,那么回归到服务器端。在QT中,TCP服务器端使用QTcpServer用于端口监听和建立服务器,服务器和客户端之间在建立连接后,通信使用QTcpSocket,套接字Socket进行。
1.2 TCP服务端建立与通信流程
在QT中,使用QTcpServer::listen()函数开始服务器端监听,这里可以指定监听的IP地址和端口。这个表示服务器的IP和端口,监听向这个IP和端口发起请求的客户端。
当有新的客户端接入时,QTcpServer内部的incomingConnection()函数会创建一个与客户端连接的QTcpSocket对象,接着发射信号newConnection()。在newConnection()信号的槽函数中,可以用nextPendingConnection()接收客户端的连接,最后使用QTcpSocket与客户端通信。
1.3 TCP客户端通信流程
TCP客户端使用QTcpSocket与TCP服务器建立连接并通信。
客户端的QTcpSocket 实例首先通过 connectToHost()尝试连接到服务器,需要指定服务器的IP 地址和端口。
connectToHost()是异步方式连接服务器,不会阻塞程序运行,连接后发射 connected()信号。
如果需要使用阻塞方式连接服务器,则使用 waitForConnected()函数阻塞程序运行,直到连接成功或失败。
例如:
1
2
3
|
socket->connectToHost( "192.168.1.100" ,1340); if (socket->waitForConnected(1000) qDebug( "Connected!" ); |
客户端与服务器建立socket连接后,就可以向缓冲区写数据或从接收缓冲区读取数据,实现数据的通信。当缓冲区有新数据进入时,会发射readyRead()信号,一般在此信号的槽函数里面读取缓冲区数据。
2. 关键源码阐述
2.1 服务端代码
初始化TCP服务端,绑定信号与槽函数
1
2
3
4
5
|
QTcpServer *tcpServer; //TCP服务器 QTcpSocket *tcpSocket; //TCP通讯的Socket tcpServer= new QTcpServer( this ); connect(tcpServer,SIGNAL(newConnection()), this ,SLOT(onNewConnection())); |
定义了一个TcpServer的对象tcpServer,将获取的newConnection()信号绑定到自定义的槽函数onNewConnection()上。
开始监听,选择IP和端口号
1
2
3
4
5
6
7
8
9
10
|
void MainWindow::on_actStart_triggered() { //开始监听 QString IP=ui->comboIP->currentText(); //IP地址 quint16 port=ui->spinPort->value(); //端口 QHostAddress addr(IP); tcpServer->listen(addr,port); //指定ip和端口,这里的IP就是默认IP端口也是控件的默认值端口 // tcpServer->listen(QHostAddress::LocalHost,port);// Equivalent to QHostAddress("127.0.0.1"). ... ... } |
下面这个函数绑定了一些tcp连接的状态信号和对应的处理函数。
对于接收到的tcpServer连接,使用nextPending函数创建socket进行通信。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
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())); } |
服务端发送数据函数:
1
2
3
4
5
6
7
8
9
10
11
|
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); } |
服务器读取数据
1
2
3
4
5
6
7
|
void MainWindow::onSocketReadyRead() { //读取缓冲区行文本 // QStringList lines; while (tcpSocket->canReadLine()) ui->plainTextEdit->appendPlainText( "[in] " +tcpSocket->readLine()); // lines.append(clientConnection->readLine()); } |
2.2 客户端代码
对于客户端来说主要是socket通信
初始化客户端并绑定信号
1
2
3
4
5
6
7
8
9
|
QTcpSocket *tcpClient; //socket tcpClient= new QTcpSocket( this ); //创建socket变量 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())); |
连接到服务器
1
2
3
|
QString addr=ui->comboServer->currentText(); quint16 port=ui->spinPort->value(); tcpClient->connectToHost(addr,port); |
发送数据
1
2
3
4
5
6
7
8
9
10
11
|
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); } |
读取数据
1
2
3
4
5
|
void MainWindow::onSocketReadyRead() { //readyRead()信号槽函数 while (tcpClient->canReadLine()) ui->plainTextEdit->appendPlainText( "[in] " +tcpClient->readLine()); } |