keker123

Untitled

Apr 16th, 2023
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 6.89 KB | None | 0 0
  1. #pragma once
  2.  
  3. #include <string>
  4. #include <chrono>
  5. #include "tcp_connect.h"
  6. #include "byte_tools.h"
  7.  
  8. #include <sys/socket.h>
  9. #include <arpa/inet.h>
  10. #include <stdexcept>
  11. #include <cstring>
  12. #include <iostream>
  13. #include <chrono>
  14. #include <netinet/in.h>
  15. #include <unistd.h>
  16. #include <fcntl.h>
  17. #include <sys/poll.h>
  18. #include <limits>
  19. #include <utility>
  20. /*
  21.  * Обертка над низкоуровневой структурой сокета.
  22.  */
  23. class TcpConnect {
  24. public:
  25.     TcpConnect(std::string ip, int port, std::chrono::milliseconds connectTimeout, std::chrono::milliseconds readTimeout) :
  26.             ip_(std::move(ip)), port_(port), connectTimeout_(connectTimeout), readTimeout_(readTimeout) {}
  27.     ~TcpConnect() {
  28.         if (status != 2){
  29.             CloseConnection();
  30.         }
  31.     };
  32.  
  33.     /*
  34.      * Установить tcp соединение.
  35.      * Если соединение занимает более `connectTimeout` времени, то прервать подключение и выбросить исключение.
  36.      * Полезная информация:
  37.      * - https://man7.org/linux/man-pages/man7/socket.7.html
  38.      * - https://man7.org/linux/man-pages/man2/connect.2.html
  39.      * - https://man7.org/linux/man-pages/man2/fcntl.2.html (чтобы включить неблокирующий режим работы операций)
  40.      * - https://man7.org/linux/man-pages/man2/select.2.html
  41.      * - https://man7.org/linux/man-pages/man2/setsockopt.2.html
  42.      * - https://man7.org/linux/man-pages/man2/close.2.html
  43.      * - https://man7.org/linux/man-pages/man3/errno.3.html
  44.      * - https://man7.org/linux/man-pages/man3/strerror.3.html
  45.      */
  46.     void EstablishConnection(){
  47.         struct pollfd pfd;
  48.         int ret;
  49.  
  50.         // Set up the socket address and port
  51.         struct sockaddr_in address;
  52.         memset(&address, 0, sizeof(address));
  53.         address.sin_family = AF_INET;
  54.         address.sin_addr.s_addr = inet_addr(ip_.c_str());
  55.         address.sin_port = htons(port_);
  56.         // Create the socket
  57.         sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
  58.         if (sockfd_ < 0) {
  59.             throw std::runtime_error("Failed to create socket");
  60.         }
  61.  
  62.         // Set socket to non-blocking mode
  63.         int flags = fcntl(sockfd_, F_GETFL, 0);
  64.         fcntl(sockfd_, F_SETFL, flags | O_NONBLOCK);
  65.  
  66.         // Connect to the remote host
  67.         ret = connect(sockfd_, (struct sockaddr*)&address, sizeof(address));
  68.         if (ret == 0) {
  69.             // Connection established immediately
  70.             flags = fcntl(sockfd_, F_GETFL, 0);
  71.             fcntl(sockfd_, F_SETFL, flags & ~O_NONBLOCK);
  72.             return;
  73.         }
  74.  
  75.         // Wait for connection to be established, or for timeout to occur
  76.         pfd.fd = sockfd_;
  77.         pfd.events = POLLOUT;
  78.         ret = poll(&pfd, 1, connectTimeout_.count());
  79.         if (ret == 0) {
  80.             // Timeout occurred
  81.             throw std::runtime_error("Connection timed out");
  82.         } else if (ret < 0) {
  83.             // Error occurred
  84.             throw std::runtime_error("Error while connecting to remote host");
  85.         } else {
  86.             // Connection established
  87.             flags = fcntl(sockfd_, F_GETFL, 0);
  88.             fcntl(sockfd_, F_SETFL, flags & ~O_NONBLOCK);
  89.             return;
  90.         }
  91.     };
  92.  
  93.     /*
  94.      * Послать данные в сокет
  95.      * Полезная информация:
  96.      * - https://man7.org/linux/man-pages/man2/send.2.html
  97.      */
  98.     void SendData(const std::string& data) const{
  99.         const char* buf = data.c_str();
  100.         int len = data.length();
  101.         while (len > 0) {
  102.             int sent = send(sockfd_, buf, len, 0);
  103.             if (sent < 0) {
  104.                 throw std::runtime_error("Failed to send data");
  105.             }
  106.             buf += sent;
  107.             len -= sent;
  108.         }
  109.     }
  110.  
  111.     /*
  112.      * Прочитать данные из сокета.
  113.      * Если передан `bufferSize`, то прочитать `bufferSize` байт.
  114.      * Если параметр `bufferSize` не передан, то сначала прочитать 4 байта, а затем прочитать количество байт, равное
  115.      * прочитанному значению.
  116.      * Первые 4 байта (в которых хранится длина сообщения) интерпретируются как целое число в формате big endian,
  117.      * см https://wiki.theory.org/BitTorrentSpecification#Data_Types
  118.      * Полезная информация:
  119.      * - https://man7.org/linux/man-pages/man2/poll.2.html
  120.      * - https://man7.org/linux/man-pages/man2/recv.2.html
  121.      */
  122.     std::string ReceiveData(size_t bufferSize = 0) const{
  123.         int len;
  124.         if (bufferSize > 0) {
  125.             len = bufferSize;
  126.         } else {
  127.             // read message length
  128.             char lenbuf[4];
  129.             struct timeval tv;
  130.             tv.tv_sec = readTimeout_.count() / 1000;
  131.             tv.tv_usec = (readTimeout_.count() % 1000) * 1000;
  132.             if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
  133.                 throw std::runtime_error("Failed to set socket timeout");
  134.             }
  135.             int n = recv(sockfd_, lenbuf, 4, 0);
  136.             if (n < 0) {
  137.                 throw std::runtime_error("Failed to receive data");
  138.             }
  139.             if (n == 0) {
  140.                 return "";
  141.             }
  142.             if (n < 4) {
  143.                 throw std::runtime_error("Invalid message length");
  144.             }
  145.             len = BytesToInt(lenbuf);
  146.         }
  147.  
  148.         // read message
  149.         std::string data(len, 0);
  150.         char* buf = &data[0];
  151.         while (len > 0) {
  152.             struct timeval tv;
  153.             tv.tv_sec = readTimeout_.count() / 1000;
  154.             tv.tv_usec = (readTimeout_.count() % 1000) * 1000;
  155.             if (setsockopt(sockfd_, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
  156.                 throw std::runtime_error("Failed to set socket timeout");
  157.             }
  158.             int n = recv(sockfd_, buf, len, 0);
  159.             if (n < 0) {
  160.                 throw std::runtime_error("Failed to receive data");
  161.             }
  162.             if (n == 0) {
  163.                 return "";
  164.             }
  165.             buf += n;
  166.             len -= n;
  167.         }
  168.         return data;
  169.     }
  170.  
  171.     /*
  172.      * Закрыть сокет
  173.      */
  174.     void CloseConnection() {
  175.         close(sockfd_);
  176.         status = 2;
  177.     }
  178.  
  179.     const std::string& GetIp() const {
  180.         return ip_;
  181.     }
  182.     int GetPort() const {
  183.         return port_;
  184.     }
  185. private:
  186.     const std::string ip_;
  187.     const int port_;
  188.     std::chrono::milliseconds connectTimeout_, readTimeout_;
  189.     int sockfd_;
  190.     int status = 0; // 0 - не открыто, 1 - открыто, 2 - закрыто
  191. };
  192.  
  193.  
  194.  
Add Comment
Please, Sign In to add comment