Используйте gRPC для передачи больших файлов или потоков данных на основе Protobuf.
Используйте gRPC для передачи больших файлов или потоков данных на основе Protobuf.

Используйте gRPC для передачи больших файлов или потоков данных на основе Protobuf.

При разработке современного программного обеспечения производительность часто является одним из ключевых факторов, особенно при передаче больших файлов. Эффективные протоколы и инструменты могут значительно повысить скорость и надежность передачи. В этой статье подробно описано, как использовать gRPC и Protobuf для передачи больших файлов, а также сравнивается производительность с традиционной передачей TCP.

1. Предыстория и выбор технологии

Раньше для передачи больших файлов часто использовался традиционный протокол TCP/IP. Хотя он и был простым, он часто работал медленно при передаче крупномасштабных данных, особенно в средах с плохими условиями сети. В недавнем проекте возникла необходимость передавать большие файлы данных. Тестирование с использованием традиционных методов показало, что задержка передачи не соответствует требованиям, поэтому я поискал информацию в Интернете и нашел лучшее решение.

По сравнению с TCP, gRPC представляет собой современное высокопроизводительное решение. gRPC — это высокопроизводительная платформа удаленного вызова процедур (RPC), разработанная Google. Она использует HTTP/2 в качестве протокола транспортного уровня и поддерживает несколько языков разработки, таких как C++, Java, Python и Go. Protobuf (Protocol Buffers) — это облегченный формат обмена данными, который позволяет эффективно сериализовать структурированные данные.

1.1 Преимущества gRPC
  • высокая производительность: Используя протокол HTTP/2, он поддерживает современные сетевые технологии, такие как мультиплексирование и передача данных на сервер.
  • Межъязыковая поддержка: Поддерживает несколько языков программирования для облегчения взаимодействия между различными системами.
  • Определение интерфейса: служба определения файлов use.proto,Автоматически генерировать клиентский код Сервери,Уменьшите дублирование работы.
  • контроль потока: Поддерживает потоковую передачу данных, подходит для передачи больших файлов и обработки данных в реальном времени.
1.2 Преимущества протокольных буферов
  • Эффективный: Кодирование и декодирование выполняются быстро, а размер получаемых пакетов в 3–10 раз меньше, чем у XML.
  • гибкий: Поддержка обратной совместимости позволяет легко конвертировать новые и старые форматы данных.
  • краткий: Он упрощает обработку сложных структур данных и прост в использовании для разработчиков.

2. Конфигурация проекта и настройка среды.

Чтобы использовать gRPC для разработки проектов, сначала необходимо установить gRPC и зависимые от него библиотеки в среде разработки. Ниже приведены шаги по установке gRPC, которые применимы к различным операционным системам, включая Windows, Linux и macOS.

2.1 Установите gRPC и буферы протоколов

Установку gRPC можно выполнить различными способами, включая использование менеджера пакетов или компиляцию из исходного кода. Ниже описано, как установить версию gRPC для C++ в Ubuntu (в комплекте с буферами протокола).

Примечание. Если версии gRPC и буферов протокола не совпадают, возникнут проблемы, и он не будет работать должным образом.

2.1.1 Установите Cmake
Язык кода:javascript
копировать
sudo apt install -y cmake
2.1.2 Установка переменных среды
Язык кода:javascript
копировать
export MY_INSTALL_DIR=$HOME/.local
mkdir -p $MY_INSTALL_DIR
export PATH="$MY_INSTALL_DIR/bin:$PATH" # Чтобы эта команда вступила в силу навсегда, вы можете записать эту команду в файл ~/.bashrc.
2.1.3 Установите необходимые зависимости
Язык кода:javascript
копировать
sudo apt install -y build-essential autoconf libtool pkg-config
2.1.4 Загрузите исходный код gRPC
Язык кода:javascript
копировать
git clone --recurse-submodules -b v1.62.0 --depth 1 --shallow-submodules https://github.com/grpc/grpc
2.1.5 Скомпилируйте gRPC и Protocol Buffers
Язык кода:javascript
копировать
cd grpc
mkdir -p cmake/build
pushd cmake/build
cmake -DgRPC_INSTALL=ON \
      -DgRPC_BUILD_TESTS=OFF \
      -DCMAKE_INSTALL_PREFIX=$MY_INSTALL_DIR \
      ../..
make -j 4
make install
popd
2.2 Подробное объяснение конфигурации CMake
2.1.1 Общая конфигурация

common.cmake является вспомогательным средством CMake Файлы модулей обычно используются для хранения файлов, совместно используемых проектами. CMake Конфигурация для упрощения и централизации управления CMakeLists.txt код в файле. Такой подход помогает улучшить ремонтопригодность и читабельность проекта.

существовать gRPC В проекте,в примере кодаcommon.cmake Включает следующее:

  • Переменные настройки:определение В проектеиспользовать Общие пути кипеременная,Например путь установки gRPC и protobuf,Так что существующие могут быть повторно использованы на протяжении всего проекта.
  • поиск в библиотеке:использовать find_package() или find_library() Команда для поиска и настройки зависимых библиотек, необходимых для проекта, таких как gRPC、protobuf、SSL ждать.
  • параметры компилятора:Устанавливайте флаги компилятора единообразно,Например C++ стандартная версия、Уровень оптимизации、Предупреждение об обработке ожидания.
  • Определение макроса:Создать многоразовый CMake Макрос или функции, например, используемые для обработки proto Файл генерирует макросы для связанных команд, что помогает избежать существования CMakeLists.txt В файле повторяется тот же блок кода.
Язык кода:javascript
копировать
# Copyright 2018 gRPC authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# cmake build file for C++ route_guide example.
# Assumes protobuf and gRPC have been installed using cmake.
# See cmake_externalproject/CMakeLists.txt for all-in-one cmake build
# that automatically builds all the dependencies before building route_guide.

cmake_minimum_required(VERSION 3.8)

if(MSVC)
  add_definitions(-D_WIN32_WINNT=0x600)
endif()

find_package(Threads REQUIRED)

if(GRPC_AS_SUBMODULE)
  # One way to build a projects that uses gRPC is to just include the
  # entire gRPC project tree via "add_subdirectory".
  # This approach is very simple to use, but the are some potential
  # disadvantages:
  # * it includes gRPC's CMakeLists.txt directly into your build script
  #   without and that can make gRPC's internal setting interfere with your
  #   own build.
  # * depending on what's installed on your system, the contents of submodules
  #   in gRPC's third_party/* might need to be available (and there might be
  #   additional prerequisites required to build them). Consider using
  #   the gRPC_*_PROVIDER options to fine-tune the expected behavior.
  #
  # A more robust approach to add dependency on gRPC is using
  # cmake's ExternalProject_Add (see cmake_externalproject/CMakeLists.txt).

  # Include the gRPC's cmake build (normally grpc source code would live
  # in a git submodule called "third_party/grpc", but this example lives in
  # the same repository as gRPC sources, so we just look a few directories up)
  add_subdirectory(../../.. ${CMAKE_CURRENT_BINARY_DIR}/grpc EXCLUDE_FROM_ALL)
  message(STATUS "Using gRPC via add_subdirectory.")

  # After using add_subdirectory, we can now use the grpc targets directly from
  # this build.
  set(_PROTOBUF_LIBPROTOBUF libprotobuf)
  set(_REFLECTION grpc++_reflection)
  set(_ORCA_SERVICE grpcpp_orca_service)
  if(CMAKE_CROSSCOMPILING)
    find_program(_PROTOBUF_PROTOC protoc)
  else()
    set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
  endif()
  set(_GRPC_GRPCPP grpc++)
  if(CMAKE_CROSSCOMPILING)
    find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
  else()
    set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:grpc_cpp_plugin>)
  endif()
elseif(GRPC_FETCHCONTENT)
  # Another way is to use CMake's FetchContent module to clone gRPC at
  # configure time. This makes gRPC's source code available to your project,
  # similar to a git submodule.
  message(STATUS "Using gRPC via add_subdirectory (FetchContent).")
  include(FetchContent)
  FetchContent_Declare(
    grpc
    GIT_REPOSITORY https://github.com/grpc/grpc.git
    # when using gRPC, you will actually set this to an existing tag, such as
    # v1.25.0, v1.26.0 etc..
    # For the purpose of testing, we override the tag used to the commit
    # that's currently under test.
    GIT_TAG        vGRPC_TAG_VERSION_OF_YOUR_CHOICE)
  FetchContent_MakeAvailable(grpc)

  # Since FetchContent uses add_subdirectory under the hood, we can use
  # the grpc targets directly from this build.
  set(_PROTOBUF_LIBPROTOBUF libprotobuf)
  set(_REFLECTION grpc++_reflection)
  set(_PROTOBUF_PROTOC $<TARGET_FILE:protoc>)
  set(_GRPC_GRPCPP grpc++)
  if(CMAKE_CROSSCOMPILING)
    find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
  else()
    set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:grpc_cpp_plugin>)
  endif()
else()
  # This branch assumes that gRPC and all its dependencies are already installed
  # on this system, so they can be located by find_package().

  # Find Protobuf installation
  # Looks for protobuf-config.cmake file installed by Protobuf's cmake installation.
  option(protobuf_MODULE_COMPATIBLE TRUE)
  find_package(Protobuf CONFIG REQUIRED)
  message(STATUS "Using protobuf ${Protobuf_VERSION}")

  set(_PROTOBUF_LIBPROTOBUF protobuf::libprotobuf)
  set(_REFLECTION gRPC::grpc++_reflection)
  if(CMAKE_CROSSCOMPILING)
    find_program(_PROTOBUF_PROTOC protoc)
  else()
    set(_PROTOBUF_PROTOC $<TARGET_FILE:protobuf::protoc>)
  endif()

  # Find gRPC installation
  # Looks for gRPCConfig.cmake file installed by gRPC's cmake installation.
  find_package(gRPC CONFIG REQUIRED)
  message(STATUS "Using gRPC ${gRPC_VERSION}")

  set(_GRPC_GRPCPP gRPC::grpc++)
  if(CMAKE_CROSSCOMPILING)
    find_program(_GRPC_CPP_PLUGIN_EXECUTABLE grpc_cpp_plugin)
  else()
    set(_GRPC_CPP_PLUGIN_EXECUTABLE $<TARGET_FILE:gRPC::grpc_cpp_plugin>)
  endif()
endif()
2.2.2 Конфигурация проекта

Этот файл конфигурации включает команды для генерации кода C++ из файлов прототипов.,и команды для компиляции этих сгенерированных файлов исходного кода в библиотеки и исполняемые файлы. Использование CMake,Мы можем гарантировать, что существующий проект может быть воспроизводимо построен в различных средах.

Язык кода:javascript
копировать
cmake_minimum_required(VERSION 3.15)

project(grpcDemo)

set(CMAKE_CXX_STANDARD 17)

include(common.cmake)

# Proto file
get_filename_component(transfer_proto "transferfile.proto" ABSOLUTE)
get_filename_component(transfer_proto_path "${transfer_proto}" PATH)

# Generated sources
set(transfer_proto_srcs "${CMAKE_CURRENT_BINARY_DIR}/transferfile.pb.cc")
set(transfer_proto_hdrs "${CMAKE_CURRENT_BINARY_DIR}/transferfile.pb.h")
set(transfer_grpc_srcs "${CMAKE_CURRENT_BINARY_DIR}/transferfile.grpc.pb.cc")
set(transfer_grpc_hdrs "${CMAKE_CURRENT_BINARY_DIR}/transferfile.grpc.pb.h")
add_custom_command(
      OUTPUT "${transfer_proto_srcs}" "${transfer_proto_hdrs}" "${transfer_grpc_srcs}" "${transfer_grpc_hdrs}"
      COMMAND ${_PROTOBUF_PROTOC}
      ARGS --grpc_out "${CMAKE_CURRENT_BINARY_DIR}"
        --cpp_out "${CMAKE_CURRENT_BINARY_DIR}"
        -I "${transfer_proto_path}"
        --plugin=protoc-gen-grpc="${_GRPC_CPP_PLUGIN_EXECUTABLE}"
        "${transfer_proto}"
      DEPENDS "${transfer_proto}")

# Include generated *.pb.h files
include_directories("${CMAKE_CURRENT_BINARY_DIR}")

# transfer_grpc_proto
add_library(transfer_grpc_proto
  ${transfer_grpc_srcs}
  ${transfer_grpc_hdrs}
  ${transfer_proto_srcs}
  ${transfer_proto_hdrs})
target_link_libraries(transfer_grpc_proto
  ${_REFLECTION}
  ${_GRPC_GRPCPP}
  ${_PROTOBUF_LIBPROTOBUF})


# Targets greeter_[async_](client|server)
foreach(_target
  transfer_client transfer_server)
  add_executable(${_target} "${_target}.cpp")
  target_link_libraries(${_target}
    transfer_grpc_proto
    ${_REFLECTION}
    ${_GRPC_GRPCPP}
    ${_PROTOBUF_LIBPROTOBUF})
endforeach()
2.3 Определение сервиса и файл прототипа

существоватьgRPCсередина,Определение служб и сообщений выполняется через файлы .proto. Например,Определить службу передачи файлов,Можетсуществоватьtransferfile.protoсередина如下определение:

Язык кода:javascript
копировать
syntax = "proto3";

package filetransfer;

service FileTransferService {
  rpc Upload(stream FileChunk) returns (UploadStatus) {}
}

message FileChunk {
  bytes content = 1;
}

message UploadStatus {
  bool success = 1;
  string message = 2;
}

Здесь определяетсяFileTransferServiceСлужить,содержитUploadметод,Этот метод принимаетFileChunkтип потока,и возвращаетUploadStatusсостояние。

3. Реализация клиента и сервера

Клиент и сервер реализуются через интерфейсы, созданные платформой gRPC, которые основаны на файлах .proto, определенных ранее.

3.1 реализация клиента gRPC

Основная обязанность клиента — открыть файл, прочитать данные и затем отправить их на сервер в виде потока. Код реализации следующий:

Язык кода:javascript
копировать
#include <iostream>
#include <string>
#include <fstream>
#include <chrono>
#include <grpcpp/grpcpp.h>
#include <grpcpp/channel.h>
#include <grpcpp/client_context.h>
#include <grpcpp/create_channel.h>
#include <grpcpp/security/credentials.h>
#include "transferfile.grpc.pb.h"

using grpc::Channel;
using grpc::ClientContext;
using grpc::ClientWriter;
using grpc::Status;
using transferfile::FileChunk;
using transferfile::FileUploadStatus;
using transferfile::TransferFile;

#define CHUNK_SIZE 3 * 1024 * 1024 // 3MB

class TransferClient
{
public:
    TransferClient(std::shared_ptr<Channel> channel) : stub_(TransferFile::NewStub(channel)) {}

    void uploadFile(const std::string &filename);

private:
    std::unique_ptr<TransferFile::Stub> stub_;
};

void TransferClient::uploadFile(const std::string &filename)
{
    FileChunk chunk;
    char *buffer = new char[CHUNK_SIZE];
    FileUploadStatus status;
    ClientContext context;
    std::ifstream infile;
    unsigned long len = 0;

    auto start = std::chrono::steady_clock::now();
    infile.open(filename, std::ios::binary | std::ios::in);
    if (!infile.is_open())
    {
        std::cerr << "Error: File not found" << std::endl;
        return;
    }

    std::unique_ptr<ClientWriter<FileChunk>> writer(stub_->UploadFile(&context, &status)); // Create a writer to send chunks of file
    while (!infile.eof())
    {
        infile.read(buffer, CHUNK_SIZE);
        chunk.set_buffer(buffer, infile.gcount());
        if (!writer->Write(chunk))
        {
            std::cerr << "Error: Failed to write chunk" << std::endl;
            break;
        }
        len += infile.gcount();
    }

    infile.close();
    delete[] buffer;

    writer->WritesDone();
    Status status1 = writer->Finish();
    if (status1.ok() && len != status.length())
    {
        auto end = std::chrono::steady_clock::now();
        std::cout << "File uploaded successfully" << std::endl;
        std::cout << "Time taken: " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms" << std::endl;
        std::cout << "File size: " << len << " bytes" << std::endl;
        auto speed = (len / std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()) * 1000 / 1024 / 1024;
        std::cout << "Speed: " << speed << " MB/s" << std::endl;
    }
    else
    {
        std::cerr << "Error: " << status1.error_message() << "len: " << len << " status.length(): " << status.length()
                  << std::endl;
    }
}

int main(int argc, char *argv[])
{
    if (argc < 2)
    {
        std::cerr << "Usage: " << argv[0] << " <filename> [server_ip]" << std::endl;
        return 1;
    }

    std::string filename(argv[1]);
    std::string server_ip = "localhost";

    if (argc == 3)
    {
        server_ip = argv[2];
    }

    TransferClient client(grpc::CreateChannel(server_ip + ":50051", grpc::InsecureChannelCredentials()));
    client.uploadFile(filename);

    return 0;
}

Код клиента показывает, как создать клиент gRPC, как открыть файл, как разрезать файл на фрагменты и как отправить эти фрагменты по сети на сервер.

3.2 реализация сервера gRPC

Реализация на стороне сервера отвечает за получение блоков данных от клиента и запись их в файлы на сервере. Код сервера следующий:

Язык кода:javascript
копировать
#include <iostream>
#include <string>
#include <fstream>
#include <grpcpp/grpcpp.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
#include <grpcpp/server_context.h>
#include <grpcpp/security/server_credentials.h>
#include "transferfile.grpc.pb.h"
#include <sys/resource.h>
#include <unistd.h>
#include <chrono>

using grpc::Server;
using grpc::ServerBuilder;
using grpc::ServerContext;
using grpc::ServerReader;
using grpc::Status;
using transferfile::FileChunk;
using transferfile::FileUploadStatus;
using transferfile::TransferFile;
using namespace std::chrono;

#define CHUNK_SIZE 3 * 1024 * 1024 // 3MB

class TransferServiceImpl final : public TransferFile::Service
{
public:
    Status UploadFile(ServerContext *context, ServerReader<FileChunk> *reader, FileUploadStatus *status) override;

protected:
    void printUsage(const struct rusage &start, const struct rusage &end)
    {
        std::cout << "CPU Usage: User time: " << (end.ru_utime.tv_sec - start.ru_utime.tv_sec) + (end.ru_utime.tv_usec - start.ru_utime.tv_usec) / 1000000.0
                  << "s, System time: " << (end.ru_stime.tv_sec - start.ru_stime.tv_sec) + (end.ru_stime.tv_usec - start.ru_stime.tv_usec) / 1000000.0 << "s" << std::endl;
        std::cout << "Max resident set size: " << (end.ru_maxrss - start.ru_maxrss) << " KB" << std::endl;
    }
};

Status TransferServiceImpl::UploadFile(ServerContext *context, ServerReader<FileChunk> *reader, FileUploadStatus *status)
{
    FileChunk chunk;
    std::ofstream outfile;
    const char *data;

    struct rusage usage_start, usage_end;
    getrusage(RUSAGE_SELF, &usage_start);

    auto start = high_resolution_clock::now();

    outfile.open("output.bin", std::ios::binary | std::ios::out | std::ios::trunc);
    if (!outfile.is_open())
    {
        std::cerr << "Error: Failed to open file" << std::endl;
        return Status::CANCELLED;
    }

    while (reader->Read(&chunk))
    {
        data = chunk.buffer().c_str();
        outfile.write(data, chunk.buffer().length());
    }

    long pos = outfile.tellp();
    status->set_length(pos);
    outfile.close();

    auto end = high_resolution_clock::now();
    getrusage(RUSAGE_SELF, &usage_end);
    auto duration = duration_cast<milliseconds>(end - start);

    printUsage(usage_start, usage_end);
    std::cout << "Total transmission time: " << duration.count() << " ms" << std::endl;
    std::cout << "Total data transmitted: " << pos << " bytes" << std::endl;
    std::cout << "Transmission rate: " << (pos * 1000.0 / duration.count()) / 1024 / 1024 << " MB/s" << std::endl;

    return Status::OK;
}

void RunServer()
{
    std::string server_address("0.0.0.0:50051");
    TransferServiceImpl service;

    ServerBuilder builder;
    builder.AddListeningPort(server_address, grpc::InsecureServerCredentials());
    builder.RegisterService(&service);

    std::unique_ptr<Server> server(builder.BuildAndStart());
    std::cout << "Server listening on " << server_address << std::endl;
    server->Wait();
}

int main(int argc, char **argv)
{
    RunServer();
    return 0;
}

Код сервера показывает, как создать сервер gRPC, как получать блоки данных, отправленные клиентом, и как записывать эти блоки данных в файл на диске.

3.3 Реализация TCP-клиента

Функции такие же, как у клиента gRPC.

Язык кода:javascript
копировать
#include <iostream>
#include <fstream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string>
#include <cstring>

int main(int argc, char *argv[])
{
    const char *server_ip = "127.0.0.1";
    int server_port = 8888;
    std::string file_path = argv[1];

    // Create socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "Socket creation failed." << std::endl;
        return 1;
    }

    // Define the server address
    struct sockaddr_in server_address;
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(server_port);
    inet_pton(AF_INET, server_ip, &server_address.sin_addr);

    // Connect to the server
    if (connect(sock, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
    {
        std::cerr << "Connection to server failed." << std::endl;
        close(sock);
        return 1;
    }

    std::ifstream file(file_path, std::ios::binary);
    if (!file.is_open())
    {
        std::cerr << "Failed to open file: " << file_path << std::endl;
        close(sock);
        return 1;
    }

    // Send file contents
    char *buffer = new char[1024];
    while (file.read(buffer, sizeof(buffer)) || file.gcount())
    {
        if (send(sock, buffer, file.gcount(), 0) < 0)
        {
            std::cerr << "Failed to send data." << std::endl;
            break;
        }
    }

    delete[] buffer;

    std::cout << "File sent successfully." << std::endl;

    file.close();
    close(sock);

    return 0;
}
3.4 Реализация TCP-сервера

Функции такие же, как у сервера gRPC.

Язык кода:javascript
копировать
#include <iostream>
#include <fstream>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>
#include <string>
#include <sys/resource.h>
#include <unistd.h>
#include <chrono>

#define CHUNK_SIZE 3 * 1024 * 1024 // 3MB

using namespace std::chrono;

void printUsage(const struct rusage &start, const struct rusage &end)
{
    std::cout << "CPU Usage: User time: " << (end.ru_utime.tv_sec - start.ru_utime.tv_sec) + (end.ru_utime.tv_usec - start.ru_utime.tv_usec) / 1000000.0
              << "s, System time: " << (end.ru_stime.tv_sec - start.ru_stime.tv_sec) + (end.ru_stime.tv_usec - start.ru_stime.tv_usec) / 1000000.0 << "s" << std::endl;
    std::cout << "Max resident set size: " << (end.ru_maxrss - start.ru_maxrss) << " KB" << std::endl;
}

int main()
{
    int server_port = 8888;
    const char *output_file = "output_received_file";

    // Create socket
    int server_sock = socket(AF_INET, SOCK_STREAM, 0);
    if (server_sock < 0)
    {
        std::cerr << "Socket creation failed." << std::endl;
        return 1;
    }

    // Bind socket to IP / port
    struct sockaddr_in server_address;
    memset(&server_address, 0, sizeof(server_address));
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(server_port);
    server_address.sin_addr.s_addr = INADDR_ANY;

    if (bind(server_sock, (struct sockaddr *)&server_address, sizeof(server_address)) < 0)
    {
        std::cerr << "Bind failed." << std::endl;
        close(server_sock);
        return 1;
    }

    // Listen
    if (listen(server_sock, 10) < 0)
    {
        std::cerr << "Listen failed." << std::endl;
        close(server_sock);
        return 1;
    }

    std::cout << "Server is listening on port " << server_port << std::endl;

    // Accept connection
    struct sockaddr_in client_address;
    socklen_t client_len = sizeof(client_address);
    int client_sock = accept(server_sock, (struct sockaddr *)&client_address, &client_len);
    if (client_sock < 0)
    {
        std::cerr << "Accept failed." << std::endl;
        close(server_sock);
        return 1;
    }

    struct rusage usage_start, usage_end;
    getrusage(RUSAGE_SELF, &usage_start);

    auto start = high_resolution_clock::now();

    std::ofstream file(output_file, std::ios::binary);
    if (!file.is_open())
    {
        std::cerr << "Failed to open file for writing." << std::endl;
        close(client_sock);
        close(server_sock);
        return 1;
    }

    // Receive data
    char *buffer = new char[CHUNK_SIZE];
    int bytes_received;
    while ((bytes_received = recv(client_sock, buffer, sizeof(buffer), 0)) > 0)
    {
        file.write(buffer, bytes_received);
    }

    if (bytes_received < 0)
    {
        std::cerr << "Error in recv()." << std::endl;
    }
    else
    {
        std::cout << "File received successfully." << std::endl;
        long pos = file.tellp();
        auto end = high_resolution_clock::now();
        getrusage(RUSAGE_SELF, &usage_end);
        auto duration = duration_cast<milliseconds>(end - start);

        printUsage(usage_start, usage_end);
        std::cout << "Total transmission time: " << duration.count() << " ms" << std::endl;
        std::cout << "Total data transmitted: " << pos << " bytes" << std::endl;
        std::cout << "Transmission rate: " << (pos * 1000.0 / duration.count()) / 1024 / 1024 << " MB/s" << std::endl;
    }

    delete[] buffer;

    file.close();
    close(client_sock);
    close(server_sock);
    return 0;
}

4. Тестирование и анализ производительности

Чтобы проверить эффективность gRPC и Protobuf, я провел тест производительности, чтобы сравнить разницу в производительности между использованием gRPC и традиционным TCP для прямой передачи больших файлов.

4.1 Методы испытаний

Методы испытаний включают в себя:

  1. Подготовьте тестовый файл определенного размера,Здесь генерируется случайным образом2GBфайлы。(fallocate -l 2G 2GBfile.txt
  2. используйтеgRPC и TCP для передачи этого файла соответственно, записывая общее время, необходимое для использования таких ресурсов, как процессор и память.
  3. Повторите тест, чтобы убедиться в точности данных.
4.1.1 Результаты теста gPRC
grpc
grpc
4.1.2 Результаты TCP-тестов
tcp
tcp
4.1.3 Результаты нескольких испытаний

User time

System time

Max resident set size

Transmission time

Transmission rate

gRPC

28.5267 s

17.2359 s

18240 KB

18.265 s

112.127 MB/s

TCP

43.2163 s

143.779 s

0 KB

187.408 s

10.928 MB/s

gRPC

29.3631 s

17.477 s

17724 KB

18.312 s

111.839 MB/s

TCP

42.2783 s

140.658 s

0 KB

183.123 s

11.1837 MB/s

gRPC

28.8431 s

17.7514 s

16852 KB

18.691 s

109.571 MB/s

TCP

43.4233 s

144.232 s

0 KB

189.021 s

10.834 MB/s

gRPC

37.5632 s

19.1164 s

15424 KB

18.589 s

110.173 MB/s

TCP

40.2059 s

140.3 s

0 KB

180.772 s

11.3292 MB/s

gRPC

36.712 s

18.1812 s

14016 KB

18.274 s

112.072 MB/s

TCP

37.9126 s

144.276 s

0 KB

182.392 s

11.2286 MB/s

4.2 Результаты сравнения производительности
vs
vs
4.2.1 Использование ЦП

gRPCсуществоватьCPUиспользоватьзначительно ниже традиционногоTCP метод сокета. Это происходит главным образом потому, что gRPC использует внутри себя более современный протокол HTTP/2, который поддерживает эффективные механизмы передачи данных, такие как мультиплексирование и передача данных на сервер, без необходимости устанавливать отдельное соединение для каждой задачи передачи файлов, например TCP. Кроме того, реализации gRPC могут включать более оптимизированные пути обработки данных, которые уменьшают накладные расходы на переключение контекста и системные вызовы.

4.2.2 Повторное использование памяти

Максимальный размер резидентного набора (максимальный размер резидентного набора) представляет собой максимальное пространство, занимаемое процессом, существующее в памяти. Максимальный размер резидентного набора для режима TCP всегда равен 0 КБ.,gRPC выделяет больше,Это связано с требованиями к памяти самой среды gRPC.,и возможные механизмы буферизации памяти,Это помогает повысить скорость и эффективность обработки данных.

4.2.3 Время и скорость передачи

gRPCсуществовать Скорость передача значительно превышает TCP socket。Эта огромная разница в основном обусловленаgRPCиспользоватьHTTP/2Преимущества,Например, сжатие заголовка, передача двоичных кадров и мультиплексирование соединений. Двоичная структура кадра HTTP/2 делает передачу более эффективной.,И сократить накладные расходы, вызванные синтаксическим анализом текста. также,Повторное использование соединения позволяет обмениваться сообщениями параллельно по одному соединению.,Таким образом, значительно повышается эффективность передачи данных.,Уменьшает задержку и потребление ресурсов, вызванное установкой и закрытием нескольких TCP-соединений.

Результаты тестирования показывают, что использование gRPCиProtobuf лучше традиционного TCP-метода при передаче больших файлов существует во многих аспектах:

  • Скорость передачи: gRPC использует возможности мультиплексирования HTTP/2.,существуют. Может передавать несколько файлов параллельно за одно соединение.,Значительно повысить эффективность передачи.
  • Использование ресурсов: Во время процесса передачи gRPC коэффициент использования ЦП низок, а уровень повторного использования памяти высок, что делает его более подходящим для сценариев длительного выполнения приложений.
  • Обработка ошибок: Обработка встроена в gRPC Механизм ошибок может эффективно управлять сетевыми проблемами и ошибками передачи данных, обеспечивая целостность данных.
  • Сериализация данных Эффективный: ProtobufОчень Эффективный,Размер сгенерированного пакета небольшой,Обычно от 3 до 10 раз меньше, чем эквивалентный XML. Это означает, что при передаче одного и того же объема данных по сети,Protobuf требует меньшей пропускной способности.
  • Быстрая сериализация и десериализация: Protobufпредоставил Очень Быстрая сериализация данныхи Возможности десериализации,Это особенно важно для приложений с высокими требованиями к производительности.,Время обработки данных может быть значительно сокращено.
  • четкое определение структуры: ИспользованиеProtobuf может сделать структуру данных более ясной и строгой, что полезно для общения внутри команды и последующего обслуживания.
  • Избегайте ручного анализа:По сравнению с пользовательскими двоичными форматами,Protobuf позволяет избежать ошибок и сложности ручного анализа данных.,Потому что работа по разбору автоматизирована,Поддерживается цепочкой инструментов.

5. Заключение

использоватьgRPCиProtobuf для передачи больших файлов,Не только улучшает скорость передачи,Это также обеспечивает более высокую надежность и меньшее потребление ресурсов. Это делает gRPC идеальным для крупномасштабной обработки данных и распределенных систем. будущее,По мере дальнейшего развития и оптимизации технологии,Ожидается, что gRPCсуществовать покажет свое превосходство в большем количестве сценариев.

Я надеюсь, что эта статья будет полезна разработчикам, стремящимся улучшить производительность передачи больших файлов и вдохновить на новые технические инновации и приложения.

boy illustration
Углубленный анализ переполнения памяти CUDA: OutOfMemoryError: CUDA не хватает памяти. Попыталась выделить 3,21 Ги Б (GPU 0; всего 8,00 Ги Б).
boy illustration
[Решено] ошибка установки conda. Среда решения: не удалось выполнить первоначальное зависание. Повторная попытка с помощью файла (графическое руководство).
boy illustration
Прочитайте нейросетевую модель Трансформера в одной статье
boy illustration
.ART Теплые зимние предложения уже открыты
boy illustration
Сравнительная таблица описания кодов ошибок Amap
boy illustration
Уведомление о последних правилах Points Mall в декабре 2022 года.
boy illustration
Даже новички могут быстро приступить к работе с легким сервером приложений.
boy illustration
Взгляд на RSAC 2024|Защита конфиденциальности в эпоху больших моделей
boy illustration
Вы используете ИИ каждый день и до сих пор не знаете, как ИИ дает обратную связь? Одна статья для понимания реализации в коде Python общих функций потерь генеративных моделей + анализ принципов расчета.
boy illustration
Используйте (внутренний) почтовый ящик для образовательных учреждений, чтобы использовать Microsoft Family Bucket (1T дискового пространства на одном диске и версию Office 365 для образовательных учреждений)
boy illustration
Руководство по началу работы с оперативным проектом (7) Практическое сочетание оперативного письма — оперативного письма на основе интеллектуальной системы вопросов и ответов службы поддержки клиентов
boy illustration
[docker] Версия сервера «Чтение 3» — создайте свою собственную программу чтения веб-текста
boy illustration
Обзор Cloud-init и этапы создания в рамках PVE
boy illustration
Корпоративные пользователи используют пакет регистрационных ресурсов для регистрации ICP для веб-сайта и активации оплаты WeChat H5 (с кодом платежного узла версии API V3)
boy illustration
Подробное объяснение таких показателей производительности с высоким уровнем параллелизма, как QPS, TPS, RT и пропускная способность.
boy illustration
Удачи в конкурсе Python Essay Challenge, станьте первым, кто испытает новую функцию сообщества [Запускать блоки кода онлайн] и выиграйте множество изысканных подарков!
boy illustration
[Техническая посадка травы] Кровавая рвота и отделка позволяют вам необычным образом ощипывать гусиные перья! Не распространяйте информацию! ! !
boy illustration
[Официальное ограниченное по времени мероприятие] Сейчас ноябрь, напишите и получите приз
boy illustration
Прочтите это в одной статье: Учебник для няни по созданию сервера Huanshou Parlu на базе CVM-сервера.
boy illustration
Cloud Native | Что такое CRD (настраиваемые определения ресурсов) в K8s?
boy illustration
Как использовать Cloudflare CDN для настройки узла (CF самостоятельно выбирает IP) Гонконг, Китай/Азия узел/сводка и рекомендации внутреннего высокоскоростного IP-сегмента
boy illustration
Дополнительные правила вознаграждения амбассадоров акции в марте 2023 г.
boy illustration
Можно ли открыть частный сервер Phantom Beast Palu одним щелчком мыши? Супер простой урок для начинающих! (Прилагается метод обновления сервера)
boy illustration
[Играйте с Phantom Beast Palu] Обновите игровой сервер Phantom Beast Pallu одним щелчком мыши
boy illustration
Maotouhu делится: последний доступный внутри страны адрес склада исходного образа Docker 2024 года (обновлено 1 декабря)
boy illustration
Кодирование Base64 в MultipartFile
boy illustration
5 точек расширения SpringBoot, супер практично!
boy illustration
Глубокое понимание сопоставления индексов Elasticsearch.
boy illustration
15 рекомендуемых платформ разработки с нулевым кодом корпоративного уровня. Всегда найдется та, которая вам понравится.
boy illustration
Аннотация EasyExcel позволяет экспортировать с сохранением двух десятичных знаков.