易妖游戏网
您的当前位置:首页关于TCP通信的学习和应用案例

关于TCP通信的学习和应用案例

来源:易妖游戏网

1.关于TCP理论知识

参考:

  socket(套接字):四元组:客户端ip+端口号port+服务端ip+端口号port,保证这是绝对唯一的连接(这是一个整体)。端口号最多有65535个。

1.1 TCP如何保证可靠性

  • (1)序列号、确认应答、超时重传
      数据到达接收方,接收方需要发出一个确认应答,表示已经收到该数据段,并且确认序号会说明了它下一次需要接收的数据序列号。如果发送发迟迟未收到确认应答,那么可能是发送的数据丢失,也可能是确认应答丢失,这时发送方在等待一定时间后会进行重传。这个时间一般是2*RTT(报文段往返时间)+一个偏差值
  • (2)窗口控制与高速重发控制/快速重传(重复确认应答)
      TCP会利用窗口控制来提高传输速度,意思是在一个窗口大小内,不用一定要等到应答才能发送下一段数据,窗口大小就是无需等待确认而可以继续发送数据的最大值。如果不使用窗口控制,每一个没收到确认应答的数据都要重发。
      使用窗口控制,如果数据段1001-2000丢失,后面数据每次传输,确认应答都会不停地发送序号为1001的应答,表示我要接收1001开始的数据,发送端如果收到3次相同应答,就会立刻进行重发;但还有种情况有可能是数据都收到了,但是有的应答丢失了,这种情况不会进行重发,因为发送端知道,如果是数据段丢失,接收端不会放过它的,会疯狂向它提醒…
  • (3)拥塞控制
      如果把窗口定的很大,发送端连续发送大量的数据,可能会造成网络的拥堵(大家都在用网,你在这狂发,吞吐量就那么大,当然会堵),甚至造成网络的瘫痪。所以TCP在为了防止这种情况而进行了拥塞控制
      慢启动:定义拥塞窗口,一开始将该窗口大小设为1,之后每次收到确认应答(经过一个rtt),将拥塞窗口大小*2。
      拥塞避免:设置慢启动阈值,一般开始都设为65536。拥塞避免是指当拥塞窗口大小达到这个阈值,拥塞窗口的值不再指数上升,而是加法增加(每次确认应答/每个rtt,拥塞窗口大小+1),以此来避免拥塞。
    将报文段的超时重传看做拥塞,则一旦发生超时重传,我们需要先将阈值设为当前窗口大小的一半,并且将窗口大小设为初值1,然后重新进入慢启动过程。
      快速重传:在遇到3次重复确认应答(高速重发控制)时,代表收到了3个报文段,但是这之前的1个段丢失了,便对它进行立即重传。
    然后,先将阈值设为当前窗口大小的一半,然后将拥塞窗口大小设为慢启动阈值+3的大小。
      这样可以达到:在TCP通信时,网络吞吐量呈现逐渐的上升,并且随着拥堵来降低吞吐量,再进入慢慢上升的过程,网络不会轻易的发生瘫痪。

1.2 简述下TCP建立连接和断开连接的过程

客户端包括:
①应用层
②传输控制层:TCP、UDP。TCP是面向连接的、可靠的传输。TCP包括:三次握手、数据传输、四次分手
③网络层
④链路层
⑤物理层

  • TCP建立连接和断开连接的过程
  • 三次握手
       三次握手是建立连接,开启socket。
      1.Client将标志位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT状态,等待Server确认。
      2.Server收到数据包后由标志位SYN=1知道Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认连接请求,Server进入SYN_RCVD状态。
      3.Client收到确认后,检查ack是否为J+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=K+1,并将该数据包发送给Server,Server检查ack是否为K+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client与Server之间可以开始传输数据了。
  • 四次挥手
      四次分手是断开连接,进行资源的释放。
      由于TCP连接时全双工的,因此,每个方向都必须要单独进行关闭,这一原则是当一方完成数据发送任务后,发送一个FIN来终止这一方向的连接,收到一个FIN只是意味着这一方向上没有数据流动了,即不会再收到数据了,但是在这个TCP连接上仍然能够发送数据,直到这一方向也发送了FIN。首先进行关闭的一方将执行主动关闭,而另一方则执行被动关闭。

1.数据传输结束后,客户端的应用进程发出连接释放报文段,并停止发送数据,客户端进入FIN_WAIT_1状态,此时客户端依然可以接收服务器发送来的数据。
2.服务器接收到FIN后,发送一个ACK给客户端,确认序号为收到的序号+1,服务器进入CLOSE_WAIT状态。客户端收到后进入FIN_WAIT_2状态。
3.当服务器没有数据要发送时,服务器发送一个FIN报文,此时服务器进入LAST_ACK状态,等待客户端的确认
4.客户端收到服务器的FIN报文后,给服务器发送一个ACK报文,确认序列号为收到的序号+1。此时客户端进入TIME_WAIT状态,等待2MSL(MSL:报文段最大生存时间),然后关闭连接。

1.3 TCP的模型

四层TCP/IP模型如下:

1.4 HTTP和HTTPS的区别,以及HTTPS有什么优缺点

  • HTTP协议和HTTPS协议区别如下
      1.HTTP协议是以明文的方式在网络中传输数据,而HTTPS协议传输的数据则是经过TLS加密后的,HTTPS具有更高的安全性
      2.HTTPS在TCP三次握手阶段之后,还需要进行SSL 的handshake,协商加密使用的对称加密密钥
      3.HTTPS协议需要服务端申请证书,浏览器端安装对应的根证书
      4.HTTP协议端口是80,HTTPS协议端口是443

  • HTTPS优点
      1.HTTPS传输数据过程中使用密钥进行加密,所以安全性更高
      2.HTTPS协议可以认证用户和服务器,确保数据发送到正确的用户和服务器

  • HTTPS缺点
      1.HTTPS握手阶段延时较高:由于在进行HTTP会话之前还要进行SSL握手,因此HTTPS协议握手阶段延时增加
      2.HTTPS部署成本高:一方面HTTPS协议需要使用证书来验证自身的安全性,所以需要购买CA证书;另一方面由于采用HTTPS协议需要进行加解密的计算,占用CPU资源较多,需要的服务器配置或数目高。



2.Qt中TCP通信

2.1 QTcpServer

TCP客户端:首先创建套接字socket(),然后连接服务器connect(),连接之后和服务器进行通信,发送数据send()和接收数据rev(),最后关闭套接字close()

2.2 QTcpSocket

2.3 使用多线程进行网络通信

示例场景:TCP客户端和服务端,通过子线程处理将客户端的文件发送给服务器

2.4 源码:TCP服务端和TCP客户端

环境:Ubuntu16.04 + QT5.12.9 + CMake3.21

TCP客户端

CMakeLists.txt

cmake_minimum_required(VERSION 3.5)

project(tcp_client LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt5 COMPONENTS Widgets REQUIRED Network)

if(ANDROID)
  add_library(tcp_client SHARED
    main.cpp
    tcpclient.cpp
    tcpclient.h
    tcpclient.ui
    resources.qrc
  )
else()
  add_executable(tcp_client
    main.cpp
    tcpclient.cpp
    tcpclient.h
    tcpclient.ui
    resources.qrc
  )
endif()

target_link_libraries(tcp_client PRIVATE Qt5::Widgets Qt5::Network)

tcpclient.ui

#include "tcpclient.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TcpClient w;
    w.show();
    w.setWindowTitle("TCP - 客户端");
    return a.exec();
}

tcpclient.h

#ifndef TCPCLIENT_H
#define TCPCLIENT_H

#include <QMainWindow>
#include <QTcpSocket>
#include <QHostAddress>
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui {
class TcpClient;
}
QT_END_NAMESPACE

class TcpClient : public QMainWindow
{
    Q_OBJECT

public:
    TcpClient(QWidget *parent = nullptr);
    ~TcpClient();

private slots:
    void on_pbn_connect_clicked();

    void on_pbn_disconnect_clicked();

    void on_pbn_send_message_clicked();

private:
    Ui::TcpClient *ui;

    QTcpSocket *tcp_{ nullptr };
    QLabel *status_{ nullptr };
};
#endif // TCPCLIENT_H

tcpclient.cpp

#include "tcpclient.h"
#include "./ui_tcpclient.h"

TcpClient::TcpClient(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::TcpClient)
{
    ui->setupUi(this);

    ui->le_port->setText("8000");
    ui->le_ip->setText("127.0.0.1");
    ui->pbn_disconnect->setEnabled(false);

    // 创建通信的套接字对象
    tcp_ = new QTcpSocket(this);

    // 检测服务器是否回复了数据
    connect(tcp_, &QTcpSocket::readyRead, [=] {
        // 接收服务器发送的数据
        QByteArray recv_msg = tcp_->readAll();
        ui->txe_message->append("服务器Say: " + recv_msg);
    });

    // 检测是否和服务器是否连接成功了
    connect(tcp_, &QTcpSocket::connected, this, [=]() {
        ui->txe_message->append("恭喜, 连接服务器成功!!!");
        status_->setPixmap(QPixmap(":/resources/connect.png").scaled(20, 20));
        ui->pbn_connect->setEnabled(false);
        ui->pbn_disconnect->setEnabled(true);
    });

    // 检测服务器是否和客户端断开了连接
    connect(tcp_, &QTcpSocket::disconnected, this, [=]() {
        ui->txe_message->append("服务器已经断开了连接...");
        ui->pbn_connect->setEnabled(true);
        ui->pbn_disconnect->setEnabled(false);
        status_->setPixmap(
            QPixmap(":/resources/disconnect.png").scaled(20, 20));
    });

    // 设置连接状态的状态栏
    status_ = new QLabel(this);
    status_->setPixmap(QPixmap(":/resources/disconnect.png").scaled(20, 20));
    ui->statusbar->addWidget(new QLabel("连接状态:"));
    ui->statusbar->addWidget(status_);
}

TcpClient::~TcpClient()
{
    delete ui;
}

void TcpClient::on_pbn_connect_clicked()
{
    QString ip = ui->le_ip->text();
    unsigned short port = ui->le_port->text().toInt();
    // 连接服务器
    tcp_->connectToHost(QHostAddress(ip), port);
    ui->pbn_connect->setEnabled(false);
    ui->pbn_disconnect->setEnabled(true);
}

void TcpClient::on_pbn_disconnect_clicked()
{
    tcp_->close();
    ui->pbn_connect->setEnabled(true);
    ui->pbn_disconnect->setEnabled(false);
}

void TcpClient::on_pbn_send_message_clicked()
{
    QString send_msg = ui->txe_send_message->toPlainText();
    tcp_->write(send_msg.toUtf8());
    ui->txe_message->append("客户端Say: " + send_msg);
    ui->txe_send_message->clear();
}

运行效果:

TCP服务端

CMakeLists.txt

cmake_minimum_required(VERSION 3.20)

project(tcp_server LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt5 COMPONENTS Widgets REQUIRED Network)

if(ANDROID)
  add_library(tcp_server SHARED
    main.cpp
    tcpserver.cpp
    tcpserver.h
    tcpserver.ui
    resources.qrc
  )
else()
  add_executable(tcp_server
    main.cpp
    tcpserver.cpp
    tcpserver.h
    tcpserver.ui
    resources.qrc
  )
endif()

target_link_libraries(tcp_server PRIVATE Qt5::Widgets Qt5::Network)

tcpserver.ui

main.cpp

#include "tcpserver.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    TcpServer w;
    w.show();
    w.setWindowTitle("TCP - 服务器");
    return a.exec();
}

tcpserver.h

#ifndef TCPSERVER_H
#define TCPSERVER_H

#include <QMainWindow>
#include <QTcpServer>
#include <QTcpSocket>
#include <QLabel>

QT_BEGIN_NAMESPACE
namespace Ui {
class TcpServer;
}
QT_END_NAMESPACE

class TcpServer : public QMainWindow
{
    Q_OBJECT

public:
    TcpServer(QWidget *parent = nullptr);
    ~TcpServer();

private slots:
    void on_pbn_set_listen_clicked();

    void on_pbn_send_data_clicked();

private:
    Ui::TcpServer *ui;

    QTcpServer *server_{ nullptr };
    QTcpSocket *tcp_{ nullptr };
    QLabel *status_{ nullptr };
};
#endif // TCPSERVER_H

tcpserver.cpp

#include "tcpserver.h"
#include "./ui_tcpserver.h"

TcpServer::TcpServer(QWidget *parent)
    : QMainWindow(parent), ui(new Ui::TcpServer)
{
    ui->setupUi(this);

    ui->le_port->setText("");

    // 第一步:创建监听的服务对象
    server_ = new QTcpServer(
        this); // 指定实例化父类this,即QMainWindow,待页面析构时,server_也被析构

    // 第三步:通过 QTcpServer::newConnection()信号检测是否有新的客户端连接
    // 如果有新的客户端连接调用 QTcpSocket *QTcpServer::nextPendingConnection()
    // 得到通信的套接字对象
    connect(server_, &QTcpServer::newConnection, this, [=]() {
        tcp_ = server_->nextPendingConnection();
        ui->txe_record->append("成功和客户端建立了新的连接...");
        status_->setPixmap(QPixmap(":/resources/connect.png").scaled(20, 20));

        // 检测是否有客户端数据
        connect(tcp_, &QTcpSocket::readyRead, this, [=]() {
            // 接收数据
            QByteArray data = tcp_->readAll();
            ui->txe_record->append("客户端Say:" + data);
        });

        // 检测客户端是否断开了连接
        connect(tcp_, &QTcpSocket::disconnected, this, [=]() {
            ui->txe_record->append("客户端已经断开了连接...");
            tcp_->deleteLater();
            status_->setPixmap(
                QPixmap(":/resources/disconnect.png").scaled(20, 20));
        });
    });

    // 设置连接状态的状态栏
    status_ = new QLabel(this);
    status_->setPixmap(QPixmap(":/resources/disconnect.png").scaled(20, 20));
    ui->statusbar->addWidget(new QLabel("连接状态:"));
    ui->statusbar->addWidget(status_);
}

TcpServer::~TcpServer()
{
    delete ui;
}

// 第二步:通过 QTcpServer 对象设置监听,即:QTcpServer::listen()
void TcpServer::on_pbn_set_listen_clicked()
{
    unsigned short port = ui->le_port->text().toUShort();
    // 设置服务器监听
    server_->listen(QHostAddress::Any, port);
    ui->pbn_set_listen->setEnabled(false);
}

void TcpServer::on_pbn_send_data_clicked()
{
    // 将txe_send_message中输入内容转为纯文本的QString形式
    QString msg = ui->txe_send_message->toPlainText();
    // 将QSting类型转为QByteArray类型
    tcp_->write(msg.toUtf8());
    ui->txe_record->append("服务端Say:" + msg);
    ui->txe_send_message->clear();
}

运行效果:

使用

1.打开TCP-服务端TCP-客户端

2.点击TCP-服务器启动监听服务器,打开端口的监听,修改TCP-客户端服务端端口,改为,点击连接服务器。完成客户端和服务端的连接。

3.客户端和服务端之间可进行通讯。

3.使用海康VisionMaster和TCP客户端进行通信

因篇幅问题不能全部显示,请点此查看更多更全内容