跳转至

socket技术及TCP、UDP

#pragma once

#include <arpa/inet.h>
#include <fcntl.h>
#include <ifaddrs.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

#include <iostream>
#include <sstream>
#include <string>
#include <unordered_set>

#include "logging.h"
#include "macros.h"

namespace Common {
struct SocketCfg {
  std::string ip_;
  std::string iface_;
  int port_ = -1;
  bool is_udp_ = false;
  bool is_listening_ = false;
  bool needs_so_timestamp_ = false;

  auto toString() const {
    std::stringstream ss;
    ss << "SocketCfg[ip:" << ip_ << " iface:" << iface_ << " port:" << port_
       << " is_udp:" << is_udp_ << " is_listening:" << is_listening_
       << " needs_SO_timestamp:" << needs_so_timestamp_ << "]";

    return ss.str();
  }
};

/// Represents the maximum number of pending / unaccepted TCP connections.
constexpr int MaxTCPServerBacklog = 1024;

/// Convert interface name "eth0" to ip "123.123.123.123".
inline auto getIfaceIP(const std::string& iface) -> std::string {
  char buf[NI_MAXHOST] = {'\0'};
  ifaddrs* ifaddr = nullptr;

  if (getifaddrs(&ifaddr) != -1) {
    for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
      if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET &&
          iface == ifa->ifa_name) {
        getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), buf, sizeof(buf), NULL,
                    0, NI_NUMERICHOST);
        break;
      }
    }
    freeifaddrs(ifaddr);
  }

  return buf;
}

/// Sockets will not block on read, but instead return immediately if data is
/// not available.
inline auto setNonBlocking(int fd) -> bool {
  const auto flags = fcntl(fd, F_GETFL, 0);
  if (flags & O_NONBLOCK) return true;
  return (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1);
}

/// Disable Nagle's algorithm and associated delays.
inline auto disableNagle(int fd) -> bool {
  int one = 1;
  return (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
                     reinterpret_cast<void*>(&one), sizeof(one)) != -1);
}

/// Allow software receive timestamps on incoming packets.
inline auto setSOTimestamp(int fd) -> bool {
  int one = 1;
  return (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP,
                     reinterpret_cast<void*>(&one), sizeof(one)) != -1);
}

/// Add / Join membership / subscription to the multicast stream specified and
/// on the interface specified.
inline auto join(int fd, const std::string& ip) -> bool {
  const ip_mreq mreq{{inet_addr(ip.c_str())}, {htonl(INADDR_ANY)}};
  return (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) !=
          -1);
}

/// Create a TCP / UDP socket to either connect to or listen for data on or
/// listen for connections on the specified interface and IP:port information.
[[nodiscard]] inline auto createSocket(Logger& logger,
                                       const SocketCfg& socket_cfg) -> int {
  std::string time_str;

  const auto ip =
      socket_cfg.ip_.empty() ? getIfaceIP(socket_cfg.iface_) : socket_cfg.ip_;
  logger.log("%:% %() % cfg:%\n", __FILE__, __LINE__, __FUNCTION__,
             Common::getCurrentTimeStr(&time_str), socket_cfg.toString());

  const int input_flags = (socket_cfg.is_listening_ ? AI_PASSIVE : 0) |
                          (AI_NUMERICHOST | AI_NUMERICSERV);
  const addrinfo hints{input_flags,
                       AF_INET,
                       socket_cfg.is_udp_ ? SOCK_DGRAM : SOCK_STREAM,
                       socket_cfg.is_udp_ ? IPPROTO_UDP : IPPROTO_TCP,
                       0,
                       0,
                       nullptr,
                       nullptr};

  addrinfo* result = nullptr;
  const auto rc = getaddrinfo(
      ip.c_str(), std::to_string(socket_cfg.port_).c_str(), &hints, &result);
  ASSERT(!rc, "getaddrinfo() failed. error:" + std::string(gai_strerror(rc)) +
                  "errno:" + strerror(errno));

  int socket_fd = -1;
  int one = 1;
  for (addrinfo* rp = result; rp; rp = rp->ai_next) {
    ASSERT((socket_fd =
                socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) != -1,
           "socket() failed. errno:" + std::string(strerror(errno)));

    ASSERT(setNonBlocking(socket_fd),
           "setNonBlocking() failed. errno:" + std::string(strerror(errno)));

    if (!socket_cfg.is_udp_) {  // disable Nagle for TCP sockets.
      ASSERT(disableNagle(socket_fd),
             "disableNagle() failed. errno:" + std::string(strerror(errno)));
    }

    if (!socket_cfg
             .is_listening_) {  // establish connection to specified address.
      ASSERT(connect(socket_fd, rp->ai_addr, rp->ai_addrlen) != 1,
             "connect() failed. errno:" + std::string(strerror(errno)));
    }

    if (socket_cfg.is_listening_) {  // allow re-using the address in the call
                                     // to bind()
      ASSERT(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR,
                        reinterpret_cast<const char*>(&one), sizeof(one)) == 0,
             "setsockopt() SO_REUSEADDR failed. errno:" +
                 std::string(strerror(errno)));
    }

    if (socket_cfg.is_listening_) {
      // bind to the specified port number.
      const sockaddr_in addr{
          AF_INET, htons(socket_cfg.port_), {htonl(INADDR_ANY)}, {}};
      ASSERT(bind(socket_fd,
                  socket_cfg.is_udp_
                      ? reinterpret_cast<const struct sockaddr*>(&addr)
                      : rp->ai_addr,
                  sizeof(addr)) == 0,
             "bind() failed. errno:%" + std::string(strerror(errno)));
    }

    if (!socket_cfg.is_udp_ &&
        socket_cfg.is_listening_) {  // listen for incoming TCP connections.
      ASSERT(listen(socket_fd, MaxTCPServerBacklog) == 0,
             "listen() failed. errno:" + std::string(strerror(errno)));
    }

    if (socket_cfg
            .needs_so_timestamp_) {  // enable software receive timestamps.
      ASSERT(setSOTimestamp(socket_fd),
             "setSOTimestamp() failed. errno:" + std::string(strerror(errno)));
    }
  }

  return socket_fd;
}
}  // namespace Common
#pragma once

#include <functional>

#include "logging.h"
#include "socket_utils.h"

namespace Common {
/// Size of send and receive buffers in bytes.
constexpr size_t McastBufferSize = 64 * 1024 * 1024;

struct McastSocket {
  McastSocket(Logger& logger) : logger_(logger) {
    outbound_data_.resize(McastBufferSize);
    inbound_data_.resize(McastBufferSize);
  }

  /// Initialize multicast socket to read from or publish to a stream.
  /// Does not join the multicast stream yet.
  auto init(const std::string& ip, const std::string& iface, int port,
            bool is_listening) -> int;

  /// Add / Join membership / subscription to a multicast stream.
  auto join(const std::string& ip) -> bool;

  /// Remove / Leave membership / subscription to a multicast stream.
  auto leave(const std::string& ip, int port) -> void;

  /// Publish outgoing data and read incoming data.
  auto sendAndRecv() noexcept -> bool;

  /// Copy data to send buffers - does not send them out yet.
  auto send(const void* data, size_t len) noexcept -> void;

  int socket_fd_ = -1;

  /// Send and receive buffers, typically only one or the other is needed, not
  /// both.
  std::vector<char> outbound_data_;
  size_t next_send_valid_index_ = 0;
  std::vector<char> inbound_data_;
  size_t next_rcv_valid_index_ = 0;

  /// Function wrapper for the method to call when data is read.
  std::function<void(McastSocket* s)> recv_callback_ = nullptr;

  std::string time_str_;
  Logger& logger_;
};
}  // namespace Common
#include "mcast_socket.h"

namespace Common {
/// Initialize multicast socket to read from or publish to a stream.
/// Does not join the multicast stream yet.
auto McastSocket::init(const std::string& ip, const std::string& iface,
                       int port, bool is_listening) -> int {
  const SocketCfg socket_cfg{ip, iface, port, true, is_listening, false};
  socket_fd_ = createSocket(logger_, socket_cfg);
  return socket_fd_;
}

/// Add / Join membership / subscription to a multicast stream.
bool McastSocket::join(const std::string& ip) {
  return Common::join(socket_fd_, ip);
}

/// Remove / Leave membership / subscription to a multicast stream.
auto McastSocket::leave(const std::string&, int) -> void {
  close(socket_fd_);
  socket_fd_ = -1;
}

/// Publish outgoing data and read incoming data.
auto McastSocket::sendAndRecv() noexcept -> bool {
  // Read data and dispatch callbacks if data is available - non blocking.
  const ssize_t n_rcv =
      recv(socket_fd_, inbound_data_.data() + next_rcv_valid_index_,
           McastBufferSize - next_rcv_valid_index_, MSG_DONTWAIT);
  if (n_rcv > 0) {
    next_rcv_valid_index_ += n_rcv;
    logger_.log("%:% %() % read socket:% len:%\n", __FILE__, __LINE__,
                __FUNCTION__, Common::getCurrentTimeStr(&time_str_), socket_fd_,
                next_rcv_valid_index_);
    recv_callback_(this);
  }

  // Publish market data in the send buffer to the multicast stream.
  if (next_send_valid_index_ > 0) {
    ssize_t n = ::send(socket_fd_, outbound_data_.data(),
                       next_send_valid_index_, MSG_DONTWAIT | MSG_NOSIGNAL);

    logger_.log("%:% %() % send socket:% len:%\n", __FILE__, __LINE__,
                __FUNCTION__, Common::getCurrentTimeStr(&time_str_), socket_fd_,
                n);
  }
  next_send_valid_index_ = 0;

  return (n_rcv > 0);
}

/// Copy data to send buffers - does not send them out yet.
auto McastSocket::send(const void* data, size_t len) noexcept -> void {
  memcpy(outbound_data_.data() + next_send_valid_index_, data, len);
  next_send_valid_index_ += len;
  ASSERT(next_send_valid_index_ < McastBufferSize,
         "Mcast socket buffer filled up and sendAndRecv() not called.");
}
}  // namespace Common