转载自:http://liuyuananfang.blog.163.com/blog/static/843244212011118953799/
Qt编写TCP通讯程序 客户端与服务器端
说明:这是本人的练手之作,学习这个程序花费了大量时间,学习过程中借鉴了网友的成果。现在百忙之中将我的学习成果制作成学习教程供大家学习。同时我也希望大家都将自己的学习成果发布到网上,让大家共享。本教程文档和源代码我发布在CSDN网站http://download.csdn.net 大家可以搜索下载。CSDN网站下载链接http://download.csdn.net/detail/liuguangzhou123/3917541
由于我是初学者,该程序肯定会有BUG和许多需要改进的地方。有需要讨论的请加我的QQ:489478088,加我时请注明:QT学习讨论。或者加入我的QQ群84998716。
下载源代码后,由于不同版本的QT Creator创建的环境不同,可能无法正确运行,所以打开后,QT Creator可能会提示QT Creator发现其他环境的配置文件,问是否要载入,选择No。提示如下:
这时要选择NO。如果还无法运行,那你就只好新建一个工程,把我的源文件代码全部复制到你的新建工程里,再运行。
下面,开始讲解如何编写TCP测试软件:
第一步,创建工程,选中QtNetwork支持,基类选择Widget。如果创建工程中没有QtNetwork选项,在工程建好后,在工程文件*.pro文件里QT += core gui语句后面加入
QT += network
否则编译将出现“QNetworkInterface: No such file o”等错误
第二步,新建->文件或工程->选择QT项目->选择qt设计师界面类->选择Dialog without buttons,类名为client,完成。
编辑clientui界面,右击空白部分,将对象名称改为“client”,在界面添加标签按钮等,添加完成如下:
第三步,服务器地址行编辑器(Line Edit)的对象名改为clientIPlineEdit;
数据发送区行编辑器(Line Edit)的对象名改为clientMessagelineEdit;
端口行编辑器(Line Edit)的对象名改为clientPortlineEdit;
数据显示区行编辑器(Line Edit)的对象名改为messagetextBrowser;
发送按钮的对象名改为clientSendpushButton;
清空按钮的对象名改为cCleanpushButton;
连接按钮的对象名改为connectpushButton;
断开按钮的对象名改为disconnectpushButton;
Ready标签的对象名改为cStatuslabel。
第四步,编辑client.h文件
1. 添加:
#include <QNetworkInterface>
#include <QtNetwork>
2. 在函数class client : public QDialog添加
private:
QList<QHostAddress> IPlist;
QTcpServer *tcpServer;
QTcpSocket *tcpClient;
QString clientPort;
QString serverIP;
QString clientMessage; //客户端发出的信息
QString message;
quint16 blockSize; //存放文件的大小信息
QTcpSocket *clientConnection;
private slots:
void clientSendMessage();
void updateStatus();
void readMessage(); //接收数据
void displayError(QAbstractSocket::SocketError); //显示客户端错误
void on_connectpushButton_clicked();
void createServerSocket();
void on_cCleanpushButton_clicked();
void updateClientStatusConnect();
void updateClientStatusDisconnect();
void on_disconnectpushButton_clicked();
第五步,编辑client.cpp文件
1. 添加#include <QMessageBox>
2. 在构造函数Widget::Widget(QWidget *parent)添加:
connect (ui->clientSendpushButton, SIGNAL(clicked()), this, SLOT(clientSendMessage()));
ui->clientSendpushButton->setEnabled (false);
ui->disconnectpushButton->setEnabled (false);
3. 打开client.ui界面,单击界面的空白处,将界面的Windows tittle的属性改为“客户端”。
分别右击“清空”、“连接”、“断开”按钮,选择“go to slot” ,信号选择clicked().各函数修改如下:
void client::on_cCleanpushButton_clicked()
{
ui->messagetextBrowser->setText ("");
}
//客户端连接按钮槽函数
void client::on_connectpushButton_clicked()
{
tcpClient = new QTcpSocket(this);
connect (tcpClient, SIGNAL(readyRead()), this, SLOT(readMessage()));
connect (tcpClient, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayError(QAbstractSocket::SocketError)));
connect (tcpClient, SIGNAL(connected()), this, SLOT(updateClientStatusConnect())); //更新状态
connect (tcpClient, SIGNAL(disconnected()), this, SLOT(updateClientStatusDisconnect()));
blockSize = 0;//初始化
tcpClient->abort ();
serverIP = ui->clientIPlineEdit->text ();
clientPort = ui->clientPortlineEdit->text ();
if(serverIP.isEmpty () || clientPort.isEmpty ())
{
QMessageBox::warning (this, tr("Warnning"), tr("服务器IP或端口号不能为空"));
return;
}
tcpClient->connectToHost (serverIP, clientPort.toInt ()); //连接到主机
}
//客户端断开按钮槽函数
void client::on_disconnectpushButton_clicked()
{
ui->clientMessagelineEdit->setText (tr("clientStop"));
clientSendMessage ();
ui->clientMessagelineEdit->setText (tr(""));
tcpClient->close ();
}
4.实现各种函数
//客户端发送信息
void client::clientSendMessage ()
{
clientMessage = ui->clientMessagelineEdit->text ();
if(clientMessage.isEmpty ())
{
QMessageBox::warning (this, tr("Warnning"), tr("请输入发送数据"));
return;
}
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion (QDataStream::Qt_4_7);
out << (quint16) 0;
out << clientMessage;
out.device ()->seek (0);
out << (quint16)(block.size () - sizeof(quint16));
tcpClient->write (block);
if(clientMessage.contains ("clientStop"))
return;
ui->messagetextBrowser->insertPlainText (tr("send message: %1 \n").arg (clientMessage));
}
//客户端错误提示
void client::displayError (QAbstractSocket::SocketError)
{
QMessageBox::warning (this, tr("Warnning"), tcpClient->errorString ());
tcpClient->close ();
ui->connectpushButton->setEnabled (true);
ui->disconnectpushButton->setEnabled (false);
ui->clientSendpushButton->setEnabled (false);
}
//客户端更新连接状态
void client::updateClientStatusConnect ()
{
ui->cStatuslabel->setText (tr("已连接"));
ui->connectpushButton->setEnabled (false);
ui->disconnectpushButton->setEnabled (true);
ui->clientSendpushButton->setEnabled (true);
}
//客户端更新断开状态
void client::updateClientStatusDisconnect ()
{
ui->cStatuslabel->setText (tr("已断开"));
tcpClient->close ();
ui->connectpushButton->setEnabled (true);
ui->disconnectpushButton->setEnabled (false);
ui->clientSendpushButton->setEnabled (false);
}
//需要自己添加的函数
void client::updateStatus()
{
//暂时为空
}
//客户端读取信息
void client::readMessage ()
{
QDataStream in(tcpClient);
in.setVersion (QDataStream::Qt_4_7);
if(blockSize == 0)
{
// 判断接收的数据是否有两字节,也就是文件的大小信息
// 如果有则保存到 blockSize 变量中,没有则返回,继续接收数据
if(tcpClient->bytesAvailable () < (int)sizeof(quint16))
return ;
in >> blockSize;
}
if(tcpClient->bytesAvailable () < blockSize)// 如果没有得到全部的数据,则返回,继续接收数据
return;
in >> message;
if(message.contains ("serverStop")) //如果收到是服务器停止监听的信息
{
tcpClient->close ();
ui->cStatuslabel->setText (tr("服务器断开连接"));
blockSize = 0;
return;
}
ui->messagetextBrowser->insertPlainText (tr("reveived message: %1 \n").arg (message));
blockSize = 0;
}
//客户端创建套接字
void client::createServerSocket ()
{
clientConnection = tcpServer->nextPendingConnection ();
connect (clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater()));
}
这样,客户端就编写好了。
第六步,编辑main.h文件,该文件修改后如下:
#include <QtGui/QApplication>
#include "widget.h"
#include "client.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
//w.show();
client w1;
w1.show();
return a.exec();
}
可以看到效果如下,注意标题栏改成“客户端”后可能出现乱码,原因是没有添加中文支持,如何添加中文支持将在后面讲解。
第七步,编辑widget.ui界面文件,添加标签和按钮,如下
服务器地址行编辑器(Combo box)的对象名改为serverIPcomboBox;
数据发送区行编辑器(Line Edit)的对象名改为serverMessagelineEdit;
端口行编辑器(Line Edit)的对象名改为serverPortlineEdit;
数据显示区行编辑器(Line Edit)的对象名改为servertextBrowser;
发送按钮的对象名改为serverSendpushButton;
清空按钮的对象名改为sCleanpushButton;
侦听按钮的对象名改为listenpushButton;
停止按钮的对象名改为stoppushButton;
Ready标签的对象名改为statuslabel。
第八步,编辑widget.h文件
1. 添加
#include <QNetworkInterface>
#include <QtNetwork>
2. 在类class Widget : public QWidget里面添加
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void getLocalIP();//获取本机IP地址
private:
Ui::Widget *ui;
QList<QHostAddress> IPlist;
QTcpServer *tcpServer;
QString serverPort;
QString serverIP;
QString serverMessage; //服务端发出的信息
QString message;
quint16 blockSize; //存放文件的大小信息
QTcpSocket *clientConnection;
private slots:
void serverSendMessage();
void on_listenpushButton_clicked();
void updateStatus();
void displayErrorS(QAbstractSocket::SocketError); //显示服务端错误
void screateServerSocket();
void on_sCleanpushButton_clicked();
void on_stoppushButton_clicked();
void serverReadMessage();
第九步,编辑widget.cpp文件
1.添加#include <QMessageBox>
2.构造函数修改如下:
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
getLocalIP ();
clientConnection = NULL;
connect (ui->serverSendpushButton, SIGNAL(clicked()), this, SLOT(serverSendMessage()));
//ui->serverSendpushButton->setEnabled (false);
ui->serverSendpushButton->setEnabled (true);
ui->stoppushButton->setEnabled (false);
}
3. 打开widget.ui界面,单击界面的空白处,将界面的Windows tittle的属性改为“服务器端”。
在widget.ui界面分别右击 “清空”、“侦听”、“停止”按钮,选择“go to slot” ,信号选择clicked().各函数修改如下:
//侦听按钮槽函数
void Widget::on_listenpushButton_clicked()
{
serverPort = ui->serverPortlineEdit->text ();
serverIP = ui->serverIPcomboBox->currentText ();
if(serverPort.isEmpty ())
{
QMessageBox::warning (this, tr("Warnning"), tr("端口号不能为空"));
return;
}
tcpServer = new QTcpServer(this);
if(!tcpServer->listen (QHostAddress::Any, serverPort.toInt ()))
{
QMessageBox::warning (this, tr("Warnning"), tcpServer->errorString ());
close ();
}
connect (tcpServer, SIGNAL(newConnection()), this, SLOT(updateStatus()));
connect (tcpServer, SIGNAL(newConnection()), this, SLOT(screateServerSocket())); //有新的连接到来,则开始创建套接字
ui->statuslabel->setText (tr("开始监听"));
ui->listenpushButton->setEnabled (false);
ui->stoppushButton->setEnabled (true);
}
//清空按钮槽函数
void Widget::on_sCleanpushButton_clicked()
{
ui->servertextBrowser->setText ("");
}
//停止按钮槽函数
void Widget::on_stoppushButton_clicked()
{
ui->serverMessagelineEdit->setText (tr("serverStop")); //发送服务端停止监听信息
serverSendMessage ();
ui->serverMessagelineEdit->setText (tr(""));
tcpServer->close ();
ui->statuslabel->setText (tr("停止监听"));
ui->listenpushButton->setEnabled (true);
ui->stoppushButton->setEnabled (false);
ui->serverSendpushButton->setEnabled (false);
}
4. 将其余函数补充完整:
//服务器端读取信息
void Widget::serverReadMessage ()
{
QDataStream in(clientConnection);
in.setVersion (QDataStream::Qt_4_7);
if(blockSize == 0)
{
// 判断接收的数据是否有两字节,也就是文件的大小信息
// 如果有则保存到 blockSize 变量中,没有则返回,继续接收数据
if(clientConnection->bytesAvailable () < (int)sizeof(quint16))
return ;
in >> blockSize;
}
if(clientConnection->bytesAvailable () < blockSize)// 如果没有得到全部的数据,则返回,继续接收数据
return;
in >> message;
if(message.contains ("clientStop")) //如果收到是客户端断开连接的信息
{
clientConnection->close ();
ui->serverSendpushButton->setEnabled (false);
ui->statuslabel->setText (tr("客户端断开连接"));
blockSize = 0;
return;
}
ui->servertextBrowser->insertPlainText (tr("reveived message: %1 \n").arg (message));
blockSize = 0;
}
//服务器端发送信息
void Widget::serverSendMessage ()
{
if(!clientConnection) //判断有没有实例化
if(!(clientConnection = tcpServer->nextPendingConnection ())) //没有客户端连接
{
return;
}
serverMessage = ui->serverMessagelineEdit->text ();
if(serverMessage.isEmpty ())
{
QMessageBox::warning (this, tr("Warnning"), tr("请输入发送数据"));
return;
}
QByteArray block;
QDataStream out(&block, QIODevice::WriteOnly);
out.setVersion (QDataStream::Qt_4_7);
out << (quint16) 0;
out << serverMessage;
out.device ()->seek (0);
out << (quint16) (block.size () - sizeof(quint16));
connect(clientConnection, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(displayErrorS(QAbstractSocket::SocketError)));
clientConnection->write (block);
if(serverMessage.contains ("serverStop"))
return;
ui->servertextBrowser->insertPlainText (tr("send message: %1 \n").arg (serverMessage));
}
void Widget::getLocalIP ()
{
IPlist = QNetworkInterface::allAddresses ();
foreach(QHostAddress IP, IPlist)
{
ui->serverIPcomboBox->addItem (IP.toString ());
}
}
//需要自己添加的函数
void Widget::updateStatus()
{
//暂时为空
}
//服务器端创建套接字
void Widget::screateServerSocket ()
{
ui->statuslabel->setText(tr("客户端已连接"));
ui->serverSendpushButton->setEnabled (true);
clientConnection = tcpServer->nextPendingConnection ();
blockSize = 0;//初始化
connect (clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater()));
connect (clientConnection, SIGNAL(readyRead()), this, SLOT(serverReadMessage()));
}
//服务器端错误提示
void Widget::displayErrorS (QAbstractSocket::SocketError)
{
// QMessageBox::warning (this, tr("Warnning"), clientConnection->errorString ());
ui->servertextBrowser->insertPlainText (clientConnection->errorString ());
connect (clientConnection, SIGNAL(disconnected()), clientConnection, SLOT(deleteLater()));
clientConnection->disconnectFromHost ();
ui->statuslabel->setText (tr("断开连接"));
}
第十步,编辑main.cpp文件,修改好后如下:
#include <QtGui/QApplication>
#include <QTextCodec> //添加中文支持2-1
#include "widget.h"
#include "client.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QTextCodec::setCodecForTr(QTextCodec::codecForLocale());//添加中文支持2-2
Widget w;
w.show();
client w1;
w1.show();
return a.exec();
}
其中,两条语句便可完成中文支持。运行程序,如下: