1,Linux中Tcp通信流程简介
服务器端:创建socket(监听套接字),绑定bind,服务器端监听listen,接受accept(通信套接字)。
客户端:创建socket(通信套接字),连接connect(客户端主动和服务器链接)
2,Qt中Tcp的通信机制
Qt中服务器端也有两个套接字,其中一个类似QTcpServer(监听套接字),然后Linux中的bind和listen合在了一起为listen()函数,另一个通信套接字是QTcpSocket(通信套接字)。连接的时候有一个connectToHost(),但是通信的时候是一种信号和槽的连接,当主动连接的时候,成功后,服务器端会收到一个newConnection()信号,然后就会触发槽函数,参函数就会取出建立好连接的套接字 QTcpSocket()。 如果成功和对方建立好连接,通信套接字会自动触发connected();如果对方主动断开连接,通信套接字会自动触发disconnected()信号。发送数据的时候,如果发送成功,对方的通信套接字会触发readyRead()信号,需要在对应的槽函数做接收处理。
3,Qt中的Tcp客户端和服务端开发示例
效果图:
服务器代码:
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
tcpServer = NULL;
tcpSocket = NULL;
//监听套接字,指定父对象,让其自动回收空间
tcpServer = new QTcpServer(this);
tcpServer -> listen(QHostAddress::Any, 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);
}
);
}
);
}
Widget::~Widget()
{
delete ui;
}
void Widget::on_buttonSend_clicked()
{
if(tcpSocket == NULL){
return;
}
//获取编辑区内容
QString str = ui -> textEditWrite -> toPlainText();
//利用通信套接字给对方发送数据
tcpSocket -> write(str.toUtf8().data());
}
void Widget::on_buttonClose_clicked()
{
if(tcpSocket == NULL){
return;
}
//主动和客户端断开连接
tcpSocket ->disconnectFromHost();
tcpSocket -> close();
}
客户端代码:
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include<QHostAddress>
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
tcpSocket = NULL;
//分配空间,指定父对象
tcpSocket = new QTcpSocket(this);
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();
}
4,Qt中的Udp通信流程
Udp是无连接的,所以不需要建立连接。Tcp通信相当于现实中的打电话。
Qt中用到的套接字是QUdpSocket,然后使用bind进行绑定,然后使用readDatagram/writeDatagram进行文字的收发。如果对方给我发数据,套接字自动触发readyRead()。
UDP传送数据示例:
服务器端代码:
#include "widget.h"
#include "ui_widget.h"
#include<QHostAddress>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//分配空间,指定父对象
udpSocket = new QUdpSocket(this);
//绑定
udpSocket -> bind(8888);
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);
}
5,定时器QTimer的对象可以设置时间间隔,每隔设置的时间间隔,定时器myTimer就会自动触发timeout()信号。
6,Tcp文件服务器客户端
需求:从服务器端向客户端发送所选择的文件
原理:
客户端和服务器端首先建立连接,服务器端选择文件,在服务器端会获取文件的两个信息,大小和名字。先向客户端发送文件信息,客户端接收到信息之后。在客户端,对字符串进行分析,获取文件的大小和名字,并且在本地创建一个同名文件。在服务器端会读文件,发送数据(规则:读多少发多少)。在客户端,对方发多少就读多少,再把收到的内容写到文件里面。
效果图:
服务器端代码:
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
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();
}
);
}
Widget::~Widget()
{
delete ui;
}
void Widget::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() << "只读方式打开失败 77";
}
//提示打开文件的路径
ui -> textEdit -> append(filePath);
ui -> buttonFile -> setEnabled(false);
ui -> buttonSend -> setEnabled(true);
}else{
qDebug() << "选择文件路径出错 62";
}
}
//发送文件按钮
void Widget::on_buttonSend_clicked()
{
//先发送文件头信息 文件名##文件大小
QString head = QString("%1##%2").arg(fileName).arg(fileSize);
qint64 len = tcpSocket -> write(head.toUtf8());
if(len > 0){//头部信息发送成功
//发送真正的文件信息,防止TCP连包文件,需要通过延时定时器延时20ms
timer.start(20);
}else{
qDebug() << "头部信息发送失败 110";
file.close();
ui -> buttonFile -> setEnabled(true);
ui -> buttonSend -> setEnabled(false);
}
//在发送文件的信息
}
void Widget::sendData(){
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();
}
}
客户端代码:
#include "clientwidget.h"
#include "ui_clientwidget.h"
#include <QDebug>
#include <QMessageBox>
#include <QHostAddress>
ClientWidget::ClientWidget(QWidget *parent) :
QWidget(parent),
ui(new Ui::ClientWidget)
{
ui->setupUi(this);
tcpSocket = new QTcpSocket(this);
ui -> progressBar -> setValue(0);//当前值
isStart = true;
connect(tcpSocket, &QTcpSocket::readyRead,
[=](){
//取出接收的内容
QByteArray buf = tcpSocket -> readAll();
if(true == isStart){//接收头
isStart = false;
//初始化
//解析文件头部信息buf = "hello##1024"
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() << "Write Only error 40";
}
QString str = QString("接收的文件:[%1:%2kb]").arg(fileName).arg(fileSize/1024);
QMessageBox::information(this, "文件信息", str);
//设置进度条
ui -> progressBar -> setMinimum(0);//最小值
ui -> progressBar -> setMaximum(fileSize / 1024);//最大值
ui -> progressBar -> setValue(0);//当前值
}else{//接收文件信息
qint64 len = file.write(buf);
if(len > 0){
recvSize += len;//累计接收大小
qDebug() << len;
}
//更新进度条
ui -> progressBar -> setValue(recvSize/1024);
if(recvSize == fileSize){
//先给服务器发送(接收文件完成的信息)
tcpSocket -> write("file done");
file.close();
QMessageBox::information(this, "完成", "文件接收完成");
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);
}