赞
踩
功能:
TCP(Transmission Control Protocol传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议,由IETF的RFC793定义。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,用户数据报协议(UDP)是同一层内另一个重要的传输协议。在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
连接建立:
TCP的三次握手
TCP的三次握手
TCP是因特网中的传输层协议,使用三次握手协议建立连接。当主动方发出SYN连接请求后,等待对方回答SYN+ACK,并最终对对方的 SYN 执行 ACK 确认。这种建立连接的方法可以防止产生错误的连接,TCP使用的流量控制协议是可变大小的滑动窗口协议。
TCP三次握手的过程如下:
客户端发送SYN(SEQ=x)报文给服务器端,进入SYN_SEND状态。
服务器端收到SYN报文,回应一个SYN (SEQ=y)ACK(ACK=x+1)报文,进入SYN_RECV状态。
客户端收到服务器端的SYN报文,回应一个ACK(ACK=y+1)报文,进入Established状态。
三次握手完成,TCP客户端和服务器端成功地建立连接,可以开始传输数据了。
可靠性:
TCP提供一种面向连接的、可靠的字节流服务。面向连接意味着两个使用TCP的应用(通常是一个客户和一个服务器)在彼此交换数据包之前必须先建立一个TCP连接。这一过程与打电话很相似,先拨号振铃,等待对方摘机说“喂”,然后才说明是谁。在一个TCP连接中,仅有两方进行彼此通信。广播和多播不能用于TCP。
QT += core gui network greaterThan(QT_MAJOR_VERSION, 4): QT += widgets TARGET = TCPFile TEMPLATE = app SOURCES += main.cpp\ serverwidget.cpp \ clientwidget.cpp HEADERS += serverwidget.h \ clientwidget.h FORMS += serverwidget.ui \ clientwidget.ui CONFIG += C++11
#ifndef CLIENTWIDGET_H #define CLIENTWIDGET_H #include <QWidget> #include <QtNetwork> #include <QFile> namespace Ui { class ClientWidget; } class ClientWidget : public QWidget { Q_OBJECT public: explicit ClientWidget(QWidget *parent = 0); ~ClientWidget(); private slots: /** * @brief ifConnected 连接成功槽函数 */ void slot_Connected(); /** * @brief slotReadyRead 数据接收槽函数 */ void slot_ReadyRead(); private slots: void on_buttonConnect_clicked();//客户端链接按钮 private: Ui::ClientWidget *ui; QTcpSocket *tcpSocket; //通信套接字 QFile file; //文件对象 QString fileName; //文件名字 qint64 fileSize; //文件大小 qint64 recvSize; //已接收文件的大小 bool isFile; //接收文件数据标志 }; #endif // CLIENTWIDGET_H
#include "clientwidget.h" #include "ui_clientwidget.h" #include <QMessageBox> #include <QDebug> ClientWidget::ClientWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ClientWidget) { ui->setupUi(this); //创建TCP套接字 tcpSocket = new QTcpSocket(this); //进度条初始化 ui->progressBar->setValue(0); //设置标题 setWindowTitle("客户端"); //连接成功 connect(tcpSocket,&QTcpSocket::connected,this,&ClientWidget::slot_Connected); //数据接收 connect(tcpSocket,&QTcpSocket::readyRead,this,&ClientWidget::slot_ReadyRead); } ClientWidget::~ClientWidget() { delete ui; } void ClientWidget::slot_Connected() { //初始化数据 fileName = ""; fileSize = 0; recvSize = 0; isFile = false; ui->buttonConnect->setEnabled(false); ui->textEdit->clear(); ui->textEdit->append("和服务器连接成功"); } void ClientWidget::slot_ReadyRead() { QByteArray buf = tcpSocket->readAll(); //buf.resize(tcpSocket->bytesAvailable()); if(false == isFile)//先接收头部信息 { isFile = true; //文件名字 fileName = QString(buf).section("#", 1, 1); //文件大小 fileSize = QString(buf).section("#", 2, 2).toInt(); qDebug() << fileName << fileSize; recvSize = 0; file.setFileName(fileName); if(false == file.open(QIODevice::WriteOnly)){ //初始化数据 fileName = ""; fileSize = 0; recvSize = 0; isFile = false; QMessageBox::warning(this, "警告", "创建文件失败"); return; } ui->progressBar->setMinimum(0); ui->progressBar->setMaximum(fileSize/1024); ui->textEdit->append(QString("正在接收文件:\n%1").arg(fileName)); } else//文件数据 { //写入数据 qint64 len = file.write(buf); recvSize += len; qDebug() << len; ui->progressBar->setValue(recvSize/1024); } if(recvSize == fileSize)//如果接收数据长度和发送数据长度相等做接收后处理 { file.close(); ui->buttonConnect->setEnabled(true); tcpSocket->disconnectFromHost(); QMessageBox::information(this, "ok", "文件接收完毕"); ui->textEdit->append("文件接收完毕"); } } //连接按钮 void ClientWidget::on_buttonConnect_clicked() { ui->progressBar->setValue(0); QString ip = ui->lineEditIP->text(); qint16 port = ui->lineEditPort->text().toInt(); if(ip.isEmpty() == true || port == 0) { QMessageBox::warning(this, "警告", "ip或端口不能为空"); return; } tcpSocket->abort(); //取消已有的连接 //连接服务器 tcpSocket->connectToHost(QHostAddress(ip), port); }
#ifndef SERVERWIDGET_H #define SERVERWIDGET_H #include <QWidget> #include <QtNetwork> //网络相关头文件 #include <QFile> #include <QTimer> namespace Ui { class ServerWidget; } class ServerWidget : public QWidget { Q_OBJECT public: explicit ServerWidget(QWidget *parent = 0); ~ServerWidget(); /** * @brief sendFileData 发送文件数据函数 */ void sendFileData(); private slots: /** * @brief slot_Timerstart 定时器启动*毫秒调用此槽函数 */ void slot_TimerStart(); /** * @brief slot_NewConnection 当有新数据到来时调用此槽函数 */ void slot_NewConnection(); private slots: /** * @brief on_buttonChoose_clicked 选择文件按钮 */ void on_buttonChoose_clicked(); /** * @brief on_buttonSend_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 <QMessageBox> #include <QFileDialog> #include <QFileInfo> #include <QTimer> #include <QThread> ServerWidget::ServerWidget(QWidget *parent) : QWidget(parent), ui(new Ui::ServerWidget) { ui->setupUi(this); //创建套接字 tcpServer = new QTcpServer(this); //监听,端口:8888 bool isOk = tcpServer->listen(QHostAddress::Any, 8888); if(false == isOk)//监听失败 { QMessageBox::warning(this, "监听", "监听失败"); return; } //设置标题 setWindowTitle("服务器:8888"); //设置按钮(变灰) ui->buttonChoose->setEnabled(false); ui->buttonSend->setEnabled(false); //当有客户端链接时,触发信号:newConnection connect(tcpServer,&QTcpServer::newConnection,this,&ServerWidget::slot_NewConnection); //定时器处理函数 connect(&timer,&QTimer::timeout,this,&ServerWidget::slot_TimerStart); } ServerWidget::~ServerWidget() { delete ui; } //选择文件按钮 void ServerWidget::on_buttonChoose_clicked() { QString path = QFileDialog::getOpenFileName(this, "请选择所要发送的文件", "../"); if(path.isEmpty() == false)//路径有效 { //file为成员变量 file.setFileName(path); //设置文件路径 //只读方式打开文件 bool isOk = file.open(QIODevice::ReadOnly); if(false == isOk)//打开文件失败 { QMessageBox::warning(this, "警告", "打开文件失败"); return; } else { ui->textEdit->append("发送的文件:"); ui->textEdit->append(path); //初始化数据 fileName = ""; fileSize = 0; sendSize = 0; //获取发送文件的信息 QFileInfo info(path); fileName = info.fileName(); //文件名 fileSize = info.size(); //文件大小 ui->buttonSend->setEnabled(true); //恢复发送文件按钮 ui->buttonChoose->setEnabled(false); //选择文件按钮变灰 } } } //发送文件按钮 void ServerWidget::on_buttonSend_clicked() { // 发送文件按钮变灰 ui->buttonSend->setEnabled(false); //先发送文件头,自定义的数据,不是文件数据 //先发送头,自定义组包, 文件名#文件大小 QString buf = QString("head#%1#%2").arg(fileName).arg(fileSize); //先发头 qint64 len = tcpSocket->write( buf.toUtf8().data()); tcpSocket->waitForBytesWritten(); //等待数据发送完毕 ui->textEdit->append("已经在发送文件!!!"); if(len > 0) //如果头部信息发送成功,开始发送文件数据 { //10毫秒后再发送文件数据 //启动定时器,定时器内容发送文件数据 //防止TCP黏包问题 this->timer.start(1); } else { file.close(); //关闭文件 } } void ServerWidget::sendFileData() { //循环读取数据发送 qint64 len = 0; do{ // 每次发送 2kb 大小的数据,如果剩余的数据不足 2kb,就发送剩余数据的大小 char buf[2*1024] = {0}; len = 0; len = file.read( buf, sizeof(buf) ); //读数据 len = tcpSocket->write(buf, len); //发数据 sendSize +=len; //已发送的文件数据大小 }while(len > 0); //文件数据发送完毕 if(sendSize == fileSize) { //QMessageBox::information(this, "ok", "文件发送完毕"); ui->textEdit->append("文件发送完毕"); //关闭文件 file.close(); //关闭客户端 tcpSocket->disconnectFromHost(); tcpSocket->close(); } } void ServerWidget::slot_TimerStart() { this->timer.stop(); //关闭定时器 sendFileData(); //发送文件数据 } void ServerWidget::slot_NewConnection() { //取出链接套接字 tcpSocket = tcpServer->nextPendingConnection(); //客户端IP和端口 QString ip = tcpSocket->peerAddress().toString(); qint16 port = tcpSocket->peerPort(); QString str = QString("[%1:%2]和服务器连接成功").arg(ip).arg(port); ui->textEdit->setText(str); //设置内容 //恢复选择按钮状态 ui->buttonChoose->setEnabled(true); QMessageBox::information(this, "允许", "连接成功,可以选择文件发送"); }
欢迎大家关注作者在文末评论、点赞、转发以及批评指正!
如果大家有更好的方法或有问题可以在文末评论一起讨论!
共同学习!
共同进步!
如今最好,别说来日方长,时光难留,只有一去不返。。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。