Socket

Socket::~Socket()
{
  sockets::close(sockfd_);
}

bool Socket::getTcpInfo(struct tcp_info* tcpi) const
{
  socklen_t len = sizeof(*tcpi);
  memZero(tcpi, len);
  return ::getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len) == 0;
}

bool Socket::getTcpInfoString(char* buf, int len) const
{
  struct tcp_info tcpi;
  bool ok = getTcpInfo(&tcpi);
  if (ok)
  {
    snprintf(buf, len, "unrecovered=%u "
             "rto=%u ato=%u snd_mss=%u rcv_mss=%u "
             "lost=%u retrans=%u rtt=%u rttvar=%u "
             "sshthresh=%u cwnd=%u total_retrans=%u",
             tcpi.tcpi_retransmits,  // Number of unrecovered [RTO] timeouts
             tcpi.tcpi_rto,          // Retransmit timeout in usec
             tcpi.tcpi_ato,          // Predicted tick of soft clock in usec
             tcpi.tcpi_snd_mss,
             tcpi.tcpi_rcv_mss,
             tcpi.tcpi_lost,         // Lost packets
             tcpi.tcpi_retrans,      // Retransmitted packets out
             tcpi.tcpi_rtt,          // Smoothed round trip time in usec
             tcpi.tcpi_rttvar,       // Medium deviation
             tcpi.tcpi_snd_ssthresh,
             tcpi.tcpi_snd_cwnd,
             tcpi.tcpi_total_retrans);  // Total retransmits for entire connection
  }
  return ok;
}

void Socket::bindAddress(const InetAddress& addr)
{
  sockets::bindOrDie(sockfd_, addr.getSockAddr());
}

void Socket::listen()
{
  sockets::listenOrDie(sockfd_);
}

int Socket::accept(InetAddress* peeraddr)
{
  struct sockaddr_in6 addr;
  memZero(&addr, sizeof addr);
  int connfd = sockets::accept(sockfd_, &addr);
  if (connfd >= 0)
  {
    peeraddr->setSockAddrInet6(addr);
  }
  return connfd;
}

void Socket::shutdownWrite()
{
  sockets::shutdownWrite(sockfd_);
}

void Socket::setTcpNoDelay(bool on)
{
  int optval = on ? 1 : 0;
  ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME CHECK
}

void Socket::setReuseAddr(bool on)
{
  int optval = on ? 1 : 0;
  ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEADDR,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME CHECK
}

void Socket::setReusePort(bool on)
{
#ifdef SO_REUSEPORT
  int optval = on ? 1 : 0;
  int ret = ::setsockopt(sockfd_, SOL_SOCKET, SO_REUSEPORT,
                         &optval, static_cast<socklen_t>(sizeof optval));
  if (ret < 0 && on)
  {
    LOG_SYSERR << "SO_REUSEPORT failed.";
  }
#else
  if (on)
  {
    LOG_ERROR << "SO_REUSEPORT is not supported.";
  }
#endif
}

void Socket::setKeepAlive(bool on)
{
  int optval = on ? 1 : 0;
  ::setsockopt(sockfd_, SOL_SOCKET, SO_KEEPALIVE,
               &optval, static_cast<socklen_t>(sizeof optval));
  // FIXME CHECK
}

该类实际上就是封装了套接字描述符。使用RAII机制,保证描述符的正常处理。

其中getTcpInfo使用了getsockopt(sockfd_, SOL_TCP, TCP_INFO, tcpi, &len);TCP_INFO我没有在man手册找到,在头文件/usr/include/netinet/tcp.h中找到了定义:

#define TCP_INFO         11 /* Information about this connection. */

就是用来获取信息的,信息有很多,其中作者使用的是

tcpi_retransmits:重传数,表示当前待重传的包数,这个值在重传完毕后清零

tcpi_rto:重传超时时间,这个和RTT有关系,RTT越大,rto越大

tcpi_ato:用来延时确认的估值,单位为微秒.

tcpi_snd_mss:本端的MSS

tcpi_rcv_mss:对端的MSS

tcpi_lost:本端在发送出去被丢失的报文数。重传完成后清零

tcpi_retrans:重传且未确认的数据段数

tcpi_rtt:RTT,往返时间

tcpi_rttvar:描述RTT的平均偏差,该值越大,说明RTT抖动越大

tcpi_snd_ssthresh:拥塞控制慢开始阈值

tcpi_snd_cwnd:拥塞控制窗口大小

tcpi_total_retrans:统计总重传的包数,持续增长。

除了这个我之前没见过的用法,其他的都是比较基本的套接字操作。基本上都是间接调用的SocketsOps封装的接口实现的。其中一个比较有意思的就是accept使用的是sockaddr_in6,并没有区分IPv4还是IPv6,原因我在上一篇博客InetAddress 中说了。

在使用setsockopt设置的各属性中,基本都是很常见的一些功能,不过SO_REUSEPORT这个属性虽然知道一些,但实际并没有用过,所以借这个机会,又重新查了下,man手册的说明:

       SO_REUSEPORT
              Permits  multiple  AF_INET  or  AF_INET6  sockets  to  be bound to an identical socket address.  This option must be set on each socket (including the first
              socket) prior to calling bind(2) on the socket.  To prevent port hijacking, all of the processes binding to the same address must have  the  same  effective
              UID.  This option can be employed with both TCP and UDP sockets.

              For  TCP  sockets,  this  option  allows  accept(2) load distribution in a multi-threaded server to be improved by using a distinct listener socket for each
              thread.  This provides improved load distribution as compared to traditional techniques such using a single accept(2)ing  thread  that  distributes  connec-
              tions, or having multiple threads that compete to accept(2) from the same socket.

              For  UDP  sockets, the use of this option can provide better distribution of incoming datagrams to multiple processes (or threads) as compared to the tradi-
              tional technique of having multiple processes compete to receive datagrams on the same socket.

我只对tcp的部分感兴趣,大体意思就是该功能可以用来在多线程中实现使用多个socket来进行accept,改进多线程服务器中的accept负载分布,举例说明:

#include "../common/common.h"
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>

#define MAXLINE 1024

const char *port;

void *func(void *)
{
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1)
    {
        perror("socket fail");
        exit(EXIT_FAILURE);
    }
    int val = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) < 0)
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(atoi(port));
    if (bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        perror("bind fail");
        exit(EXIT_FAILURE);
    }

    if (listen(listen_fd, 1024) == -1)
    {
        perror("listen fail");
        exit(EXIT_FAILURE);
    }

    while (1)
    {
        struct sockaddr_in cliaddr;
        socklen_t clilen = sizeof(cliaddr);
        int connfd = accept(listen_fd, (struct sockaddr *)&cliaddr, &clilen);
        if (connfd < 0)
        {
            if (errno == EINTR)
                continue;
            perror("accept fail\n");
            exit(EXIT_FAILURE);
        }
        char buf[64];
        printf("%s listen_fd=%d--- accept %s\n", 
            __FUNCTION__, listen_fd, inet_ntop(AF_INET, &cliaddr.sin_addr, buf, sizeof(buf)));
    }
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        printf("usage : %s port\n", argv[0]);
        exit(EXIT_FAILURE);
    }

    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1)
    {
        perror("socket fail");
        exit(EXIT_FAILURE);
    }
    int val = 1;
    if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEPORT, &val, sizeof(val)) < 0)
    {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }
    port = argv[1];
    struct sockaddr_in servaddr;
    memset(&servaddr, 0, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(atoi(port));
    if (bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr)) == -1)
    {
        perror("bind fail");
        exit(EXIT_FAILURE);
    }

    if (listen(listen_fd, 1024) == -1)
    {
        perror("listen fail");
        exit(EXIT_FAILURE);
    }
    pthread_t tid;
    pthread_create(&tid, NULL, func, NULL);

    while (1)
    {
        struct sockaddr_in cliaddr;
        socklen_t clilen = sizeof(cliaddr);
        int connfd = accept(listen_fd, (struct sockaddr *)&cliaddr, &clilen);
        if (connfd < 0)
        {
            if (errno == EINTR)
                continue;
            perror("accept fail\n");
            exit(EXIT_FAILURE);
        }
        char buf[64];
        printf("%s listen_fd=%d--- accept %s\n", 
            __FUNCTION__, listen_fd, inet_ntop(AF_INET, &cliaddr.sin_addr, buf, sizeof(buf)));
    }
}

我在两个线程中绑定和监听了同一个地址和端口号,在这种情况下,如果使用客户端向服务端建立多个连接,会有这样的输出:

func listen_fd=5--- accept 11.11.22.106
main listen_fd=3--- accept 11.11.22.106
main listen_fd=3--- accept 11.11.22.106
func listen_fd=5--- accept 11.11.22.106

会采用某种分配策略,但具体怎么分配的我也不清楚,我测试了几次,每次的分配都不同。选项SO_REUSEPORT必须在两个线程都都设置,如果有一个线程不设置,就会绑定失败。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值