Qt中提供的所有的Socket类都是非阻塞的。
Qt中常用的用于socket通信的套接字类:
用于TCP/IP通信, 作为服务器端套接字使用
用于TCP/IP通信,作为客户端套接字使用。
用于UDP通信,服务器,客户端均使用此套接字。
在Qt中实现TCP/IP服务器端通信的流程:
可以通过QTcpServer提供的void newConnection()信号来检测是否有连接请求,如果有可以在对应的槽函数中调用nextPendingConnection函数获取到客户端的Socket信息(返回值为QTcpSocket*类型指针),通过此套接字与客户端之间进行通信。
客户端通信流程:
可以使用QTcpSocket类的connectToHost()函数来连接服务器。
下面例子为简单的TCP/IP通信的实现:
通过Qt提供的QTcpServer类实现服务器端的socket通信:
//---------- tcpserver.h ------------
class TCPServer : public QMainWindow
{
Q_OBJECT
public:
explicit TCPServer(QWidget *parent = 0);
~TCPServer();
public slots:
void slotNewConnection();
void slotReadyRead();
private:
Ui::TCPServer *ui;
// 负责监听的套接字
QTcpServer* m_server;
// 负责通信的套接字
QTcpSocket* m_client;
};
//---------- tcpserver.cpp ------------
TCPServer::TCPServer(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::TCPServer),
m_server(NULL),
m_client(NULL)
{
ui->setupUi(this);
//创建套接字对象
m_server = new QTcpServer(this);
//将套接字设置为监听模式
m_server->listen(QHostAddress::Any, 9999);
//通过信号接收客户端请求
connect(m_server, &QTcpServer::newConnection,
this, &TCPServer::slotNewConnection);
}
TCPServer::~TCPServer()
{
delete ui;
}
void TCPServer::slotNewConnection()
{
if(m_client == NULL)
{
//处理客户端的连接请求
m_client = m_server->nextPendingConnection();
//发送数据
m_client->write("服务器连接成功!!!");
//连接信号, 接收客户端数据
connect(m_client, &QTcpSocket::readyRead,
this, &TCPServer::slotReadyRead);
}
}
void TCPServer::slotReadyRead()
{
//接收数据
QByteArray array = m_client->readAll();
QMessageBox::information(this, "Client Message", array);
}
客户端通过使用Qt提供的QTcpSocket类可以方便的实现与服务器端的通信。
//------------- tcpclient.h ------------
class TCPClient : public QMainWindow
{
Q_OBJECT
public:
explicit TCPClient(QWidget *parent = 0);
~TCPClient();
public slots:
void slotReadyRead();
void slotSendMsg();
private:
Ui::TCPClient *ui;
QTcpSocket* m_client;
};
//------------- tcpclient.cpp --------------
TCPClient::TCPClient(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::TCPClient)
{
ui->setupUi(this);
//创建套接字
m_client = new QTcpSocket(this);
//连接服务器
m_client->connectToHost(QHostAddress("127.0.0.1"), 9999);
//通过信号接收服务器数据
connect(m_client, &QTcpSocket::readyRead,
this, &TCPClient::slotReadyRead);
//发送按钮
connect(ui->btnSend, &QPushButton::clicked,
this, &TCPClient::slotSendMsg);
}
TCPClient::~TCPClient()
{
delete ui;
}
void TCPClient::slotReadyRead()
{
//接收数据
QByteArray array = m_client->readAll();
QMessageBox::information(this, "Server Message", array);
}
void TCPClient::slotSendMsg()
{
QString text = ui->textEdit->toPlainText();
//发送数据
m_client->write(text.toUtf8());
ui->textEdit->clear();
}
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include
#include //通信套接字
namespace Ui {
class ClientWidget;
}
class ClientWidget : public QWidget
{
Q_OBJECT
public:
explicit ClientWidget(QWidget *parent = 0);
~ClientWidget();
private slots:
void on_buttonConnect_clicked();
void on_buttonSend_clicked();
void on_buttonClose_clicked();
private:
Ui::ClientWidget *ui;
QTcpSocket *tcpSocket; //通信套接字
};
#endif // CLIENTWIDGET_H
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
tcpSocket = NULL;
//分配空间,指定父对象
tcpSocket = new QTcpSocket(this);
setWindowTitle("客户端");
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
ui->textEditRead->setText("成功和服务器建立好连接");
}
);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//获取对方发送的内容
QByteArray array = tcpSocket->readAll();
//追加到编辑区中
ui->textEditRead->append(array);
}
);
}
ClientWidget::~ClientWidget()
{
delete ui;
}
void ClientWidget::on_buttonConnect_clicked()
{
//获取服务器ip和端口
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditPort->text().toInt();
//主动和服务器建立连接
tcpSocket->connectToHost(QHostAddress(ip), port);
}
void ClientWidget::on_buttonSend_clicked()
{
//获取编辑框内容
QString str = ui->textEditWrite->toPlainText();
//发送数据
tcpSocket->write( str.toUtf8().data() );
}
void ClientWidget::on_buttonClose_clicked()
{
//主动和对方断开连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include
#include //监听套接字
#include //通信套接字
namespace Ui {
class ServerWidget;
}
class ServerWidget : public QWidget
{
Q_OBJECT
public:
explicit ServerWidget(QWidget *parent = 0);
~ServerWidget();
private slots:
void on_buttonSend_clicked();
void on_buttonClose_clicked();
private:
Ui::ServerWidget *ui;
QTcpServer *tcpServer; //监听套接字
QTcpSocket *tcpSocket; //通信套接字
};
#endif // SERVERWIDGET_H
#include "serverwidget.h"
#include "ui_serverwidget.h"
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
tcpServer = NULL;
tcpSocket = NULL;
//监听套接字,指定父对象,让其自动回收空间
tcpServer = new QTcpServer(this);
tcpServer->listen(QHostAddress::Any, 8888);
setWindowTitle("服务器: 8888");
connect(tcpServer, &QTcpServer::newConnection,
[=]()
{
//取出建立好连接的套接字
tcpSocket = tcpServer->nextPendingConnection();
//获取对方的IP和端口
QString ip = tcpSocket->peerAddress().toString();
qint16 port = tcpSocket->peerPort();
QString temp = QString("[%1:%2]:成功连接").arg(ip).arg(port);
ui->textEditRead->setText(temp);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//从通信套接字中取出内容
QByteArray array = tcpSocket->readAll();
ui->textEditRead->append(array);
}
);
}
);
}
ServerWidget::~ServerWidget()
{
delete ui;
}
void ServerWidget::on_buttonSend_clicked()
{
if(NULL == tcpSocket)
{
return;
}
//获取编辑区内容
QString str = ui->textEditWrite->toPlainText();
//给对方发送数据, 使用套接字是tcpSocket
tcpSocket->write( str.toUtf8().data() );
}
void ServerWidget::on_buttonClose_clicked()
{
if(NULL == tcpSocket)
{
return;
}
//主动和客户端端口连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
tcpSocket = NULL;
}
使用Qt提供的QUdpSocketQUdpSocket进行UDP通信。在UDP方式下,客户端并不与服务器建立连接,它只负责调用发送函数向服务器发送数据。类似的服务器也不从客户端接收连接,只负责调用接收函数,等待来自客户端的数据的到达。
传统UDP通信过程:
在UDP通信中,服务器端和客户端的概念已经显得有些淡化,两部分做的工作都大致相同:
在UDP中如果需要接收数据则需要对套接字进行绑定,只发送数据则不需要对套接字进行绑定。
通过调用bind()函数将套接字绑定到指定端口上。
qint64 readDatagram(char * data, qint64 maxSize,
QHostAddress * address = 0, quint16 * port = 0)
参数:
QT中的UDP通信过程
使用pendingDatagramSize()可以获取到将要接收的数据的大小,根据该函数返回值来准备对应大小的内存空间存放将要接收的数据。
qint64 writeDatagram(const QByteArray & datagram,
const QHostAddress & host, quint16 port)
参数:
#ifndef WIDGET_H
#define WIDGET_H
#include
#include //UDP套接字
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
void dealMsg(); //槽函数,处理对方发过来的数据
private slots:
void on_buttonSend_clicked();
private:
Ui::Widget *ui;
QUdpSocket *udpSocket; //UDP套接字
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//分配空间,指定父对象
udpSocket = new QUdpSocket(this);
//绑定
//udpSocket->bind(8888);
udpSocket->bind(QHostAddress::AnyIPv4, 8888);
//加入某个组播
//组播地址是D类地址
udpSocket->joinMulticastGroup( QHostAddress("224.0.0.2") );
//udpSocket->leaveMulticastGroup(); //退出组播
setWindowTitle("服务器端口为:8888");
//当对方成功发送数据过来
//自动触发 readyRead()
connect(udpSocket, &QUdpSocket::readyRead, this, &Widget::dealMsg);
}
void Widget::dealMsg()
{
//读取对方发送的内容
char buf[1024] = {0};
QHostAddress cliAddr; //对方地址
quint16 port; //对方端口
qint64 len = udpSocket->readDatagram(buf, sizeof(buf), &cliAddr, &port);
if(len > 0)
{
//格式化 [192.68.2.2:8888]aaaa
QString str = QString("[%1:%2] %3")
.arg(cliAddr.toString())
.arg(port)
.arg(buf);
//给编辑区设置内容
ui->textEdit->setText(str);
}
}
Widget::~Widget()
{
delete ui;
}
//发送按钮
void Widget::on_buttonSend_clicked()
{
//先获取对方的IP和端口
QString ip = ui->lineEditIP->text();
qint16 port = ui->lineEditPort->text().toInt();
//获取编辑区内容
QString str = ui->textEdit->toPlainText();
//给指定的IP发送数据
udpSocket->writeDatagram(str.toUtf8(), QHostAddress(ip), port);
}
#ifndef WIDGET_H
#define WIDGET_H
#include
#include //定时器对象
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
private slots:
void on_buttonStart_clicked();
void on_buttonStop_clicked();
private:
Ui::Widget *ui;
QTimer *myTimer; //定时器对象
int i;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
myTimer = new QTimer(this);
i = 0;
connect(myTimer, &QTimer::timeout,
[=]()
{
i++;
ui->lcdNumber->display(i);
}
);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_buttonStart_clicked()
{
//启动定时器
//时间间隔为100ms
//每隔100ms,定时器myTimer自动触发timeout()
//如果定时器没有激活,才启动
if(myTimer->isActive() == false)
{
myTimer->start(100);
}
}
void Widget::on_buttonStop_clicked()
{
if(true == myTimer->isActive())
{
myTimer->stop();
i = 0;
}
}
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为广播地址:QHostAddress::Broadcast此设置相当于QHostAddress("255.255.255.255")
使用UDP广播的的特点:
我们再使用广播发送消息的时候会发送给所有用户,但是有些用户是不想接受消息的,这时候我们就应该使用组播,接收方只有先注册到组播地址中才能收到组播消息,否则则接受不到消息。另外组播是可以在Internet中使用的。
在使用QUdpSocket类的writeDatagram()函数发送数据的时候,其中第二个参数host应该指定为组播地址,关于组播地址的分类:
注册加入到组播地址需要使用QUdpSocket类的成员函数:
bool joinMulticastGroup(const QHostAddress & groupAddress)
TCP/IP |
UDP |
|
是否连接 |
面向连接 |
无连接 |
传输方式 |
基于流 |
基于数据报 |
传输可靠性 |
可靠 |
不可靠 |
传输效率 |
效率低 |
效率高 |
能否广播 |
不能 |
能 |
TCP传送文件
用一个应用程序进行测试(发送接收都是同一个程序):
主体框架:
#include "serverwidget.h"
#include
#include "clientwidget.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
ServerWidget w;
w.show();
ClientWidget w2;
w2.show();
return a.exec();
}
客户端
头文件
#ifndef CLIENTWIDGET_H
#define CLIENTWIDGET_H
#include
#include
#include
namespace Ui {
class ClientWidget;
}
class ClientWidget : public QWidget
{
Q_OBJECT
public:
explicit ClientWidget(QWidget *parent = 0);
~ClientWidget();
private slots:
void on_buttonConnect_clicked();
private:
Ui::ClientWidget *ui;
QTcpSocket *tcpSocket;
QFile file; //文件对象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 recvSize; //已经接收文件的大小
bool isStart; //标志位,是否为头部信息
};
#endif // CLIENTWIDGET_H
实现文件
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include
#include
#include
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
isStart = true;
ui->progressBar->setValue(0); //当前值
setWindowTitle("客户端");
connect(tcpSocket, &QTcpSocket::connected,
[=]()
{
//提示连接成功
ui->textEdit->clear();
ui->textEdit->append("和服务器连接成功,等待服务器传送文件……");
}
);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//取出接收的内容
QByteArray buf = tcpSocket->readAll();
if(true == isStart)
{//接收头
isStart = false;
//解析头部信息 QString buf = "hello##1024"
// QString str = "hello##1024#mike";
// str.section("##", 0, 0)
//初始化
//文件名
fileName = QString(buf).section("##", 0, 0);
//文件大小
fileSize = QString(buf).section("##", 1, 1).toInt();
recvSize = 0; //已经接收文件大小
//打开文件
//关联文件名字
file.setFileName(fileName);
//只写方式方式,打开文件
bool isOk = file.open(QIODevice::WriteOnly);
if(false == isOk)
{
qDebug() << "WriteOnly error 49";
tcpSocket->disconnectFromHost(); //断开连接
tcpSocket->close(); //关闭套接字
return; //如果打开文件失败,中断函数
}
//弹出对话框,显示接收文件的信息
QString str = QString("接收的文件: [%1: %2kb]").arg(fileName).arg(fileSize/1024);
//QMessageBox::information(this, "文件信息", str);
ui->textEdit->append(str);
ui->textEdit->append("正在接收文件……");
//设置进度条
ui->progressBar->setMinimum(0); //最小值
ui->progressBar->setMaximum(fileSize/1024); //最大值
ui->progressBar->setValue(0); //当前值
}
else //文件信息
{
qint64 len = file.write(buf);
if(len >0) //接收数据大于0
{
recvSize += len; //累计接收大小
qDebug() << len;
}
//更新进度条
ui->progressBar->setValue(recvSize/1024);
if(recvSize == fileSize) //文件接收完毕
{
//先给服务发送(接收文件完成的信息)
tcpSocket->write("file done");
ui->textEdit->append("文件接收完成");
QMessageBox::information(this, "完成", "文件接收完成");
file.close(); //关闭文件
//断开连接
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
}
);
}
ClientWidget::~ClientWidget()
{
delete ui;
}
void ClientWidget::on_buttonConnect_clicked()
{
//获取服务器的ip和端口
QString ip = ui->lineEditIP->text();
quint16 port = ui->lineEditPort->text().toInt();
//主动和服务器连接
tcpSocket->connectToHost(QHostAddress(ip), port);
isStart = true;
//设置进度条
ui->progressBar->setValue(0);
}
服务器
头文件
#ifndef SERVERWIDGET_H
#define SERVERWIDGET_H
#include
#include //监听套接字
#include //通信套接字
#include
#include
namespace Ui {
class ServerWidget;
}
class ServerWidget : public QWidget
{
Q_OBJECT
public:
explicit ServerWidget(QWidget *parent = 0);
~ServerWidget();
void sendData(); //发送文件数据
private slots:
void on_buttonFile_clicked();
void on_buttonSend_clicked();
private:
Ui::ServerWidget *ui;
QTcpServer *tcpServer; //监听套接字
QTcpSocket *tcpSocket; //通信套接字
QFile file; //文件对象
QString fileName; //文件名字
qint64 fileSize; //文件大小
qint64 sendSize; //已经发送文件的大小
QTimer timer; //定时器
};
#endif // SERVERWIDGET_H
实现文件
#include "serverwidget.h"
#include "ui_serverwidget.h"
#include
#include
#include
ServerWidget::ServerWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ServerWidget)
{
ui->setupUi(this);
//监听套接字
tcpServer = new QTcpServer(this);
//监听
tcpServer->listen(QHostAddress::Any, 8888);
setWindowTitle("服务器端口为:8888");
//两个按钮都不能按
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(false);
//如果客户端成功和服务器连接
//tcpServer会自动触发 newConnection()
connect(tcpServer, &QTcpServer::newConnection,
[=]()
{
//取出建立好连接的套接字
tcpSocket = tcpServer->nextPendingConnection();
//获取对方的ip和端口
QString ip = tcpSocket->peerAddress().toString();
quint16 port = tcpSocket->peerPort();
QString str = QString("[%1:%2] 成功连接").arg(ip).arg(port);
ui->textEdit->setText(str); //显示到编辑区
//成功连接后,才能按选择文件
ui->buttonFile->setEnabled(true);
connect(tcpSocket, &QTcpSocket::readyRead,
[=]()
{
//取客户端的信息
QByteArray buf = tcpSocket->readAll();
if(QString(buf) == "file done")
{//文件接收完毕
ui->textEdit->append("文件发送完毕");
file.close();
//断开客户端端口
tcpSocket->disconnectFromHost();
tcpSocket->close();
}
}
);
}
);
connect(&timer, &QTimer::timeout,
[=]()
{
//关闭定时器
timer.stop();
//发送文件
sendData();
}
);
}
ServerWidget::~ServerWidget()
{
delete ui;
}
//选择文件的按钮
void ServerWidget::on_buttonFile_clicked()
{
QString filePath = QFileDialog::getOpenFileName(this, "open", "../");
if(false == filePath.isEmpty()) //如果选择文件路径有效
{
fileName.clear();
fileSize = 0;
//获取文件信息
QFileInfo info(filePath);
fileName = info.fileName(); //获取文件名字
fileSize = info.size(); //获取文件大小
sendSize = 0; //发送文件的大小
//只读方式打开文件
//指定文件的名字
file.setFileName(filePath);
//打开文件
bool isOk = file.open(QIODevice::ReadOnly);
if(false == isOk)
{
qDebug() << "只读方式打开文件失败 106";
}
//提示打开文件的路径
ui->textEdit->append(filePath);
ui->buttonFile->setEnabled(false);
ui->buttonSend->setEnabled(true);
}
else
{
qDebug() << "选择文件路径出错 118";
}
}
//发送文件按钮
void ServerWidget::on_buttonSend_clicked()
{
ui->buttonSend->setEnabled(false);
//先发送文件头信息 文件名##文件大小
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
//发送头部信息
qint64 len = tcpSocket->write( head.toUtf8() );
if(len > 0)//头部信息发送成功
{
//发送真正的文件信息
//防止TCP黏包
//需要通过定时器延时 20 ms
timer.start(20);
}
else
{
qDebug() << "头部信息发送失败 142";
file.close();
ui->buttonFile->setEnabled(true);
ui->buttonSend->setEnabled(false);
}
}
void ServerWidget::sendData()
{
ui->textEdit->append("正在发送文件……");
qint64 len = 0;
do
{
//每次发送数据的大小
char buf[4*1024] = {0};
len = 0;
//往文件中读数据
len = file.read(buf, sizeof(buf));
//发送数据,读多少,发多少
len = tcpSocket->write(buf, len);
//发送的数据需要累积
sendSize += len;
}while(len > 0);
// //是否发送文件完毕
// if(sendSize == fileSize)
// {
// ui->textEdit->append("文件发送完毕");
// file.close();
// //把客户端端口
// tcpSocket->disconnectFromHost();
// tcpSocket->close();
// }
}