tcp网络编程性能优化点

0. 概述

TCP网络编程是网络编程中常见的一种方式,广泛应用于服务器端和客户端开发。为了提高TCP网络程序的性能,需要对程序进行优化。本文将介绍一些常见的TCP网络编程性能优化点。

1、修改select recv缓存区大小

在TCP网络编程中,通常使用select函数来监控多个套接字的读写事件。当有数据可读时,select函数会返回。然后,程序就可以使用recv函数从套接字中读取数据。

默认情况下,recv函数的缓冲区大小为4096字节。如果每次读取的数据量小于4096字节,则会导致多次系统调用,降低程序的性能。

char tmp[8192];
while (1) {
  int ret = select(FD_SETSIZE, &fds, NULL, NULL, &timeout);
  if (ret == 0) {
    break;
  }

  for (int i = 0; i < FD_SETSIZE; ++i) {
    if (FD_ISSET(i, &fds)) {
      ret = recv(i, tmp, sizeof(tmp), 0);
      if (ret == -1) {
        // 处理错误
      } else if (ret == 0) {
        // 处理连接断开
      } else {
        // 处理接收到的数据
      }
    }
  }
}

2、应用程序级修改缓冲区大小

除了修改recv函数的缓冲区大小之外,还可以通过应用程序级的代码来修改缓冲区大小。例如,可以使用setsockopt函数来设置套接字的发送缓冲区和接收缓冲区大小

  • 参考代码:
int sock, sock_buf_size;

sock = socket(AF_INET, SOCK_STREAM, 0);
sock_buf_size = 65536;

setsockopt(sock, SOL_SOCKET, SO_SNDBUF, (char *)&sock_buf_size, sizeof(sock_buf_size));
setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&sock_buf_size, sizeof(sock_buf_size));

修改缓冲区大小可以提高程序的性能,但需要注意的是,缓冲区大小不能设置太大,否则可能会导致内存溢出。

参考文章:Socket缓冲区大小修改与系统设置

3、端口复用

在高并发情况下,传统的单进程或单线程服务器可能会成为瓶颈。因为每个连接都需要占用一个进程或线程,这会导致大量的系统资源消耗。

端口复用是一种可以提高服务器性能的技术。它允许一个进程或线程同时处理多个连接。

在Linux系统中,可以使用SO_REUSEPORT选项来启用端口复用。

#include <sys/types.h>
#include <sys/socket.h>

// 设置套接字属性
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

// 参数:
// - sockfd: 要操作的文件描述符
// - level: 级别 -> SOL_SOCKET (端口复用的级别)
// - optname: 端口复用的级别(二选一,随便选,对于端口复用都可以)
//  - SO_REUSEADDR
//  - SO_REUSEPORT
// - optval: 端口复用-> 对应的是整形数
//  - 1: 可以复用
//  - 0: 不能复用
// - optlen: optval参数对应的内存大小

// 设置端口复用, 设置的时机: 服务器绑定端口之前, 设置端口复用。例如:
setsockopt();
bind();

4、内核参数修改

Linux内核提供了许多可以用来调整TCP/IP性能的参数。可以通过修改这些参数来优化TCP网络程序的性能。

  • 参考文章:Linux内核 TCP/IP、Socket参数调优

  • 修改linux系统文件句柄限制

    执行echo -e "* soft nofile 40960 \n* hard nofile 65536" >> /etc/security/limits.conf 修改linux系统文件句柄限制

     cat /etc/security/limits.conf
    将在limits.conf中添加以下参数
    * soft   nofile   40960
    * hard   nofile   65536
    

    参数

    * 代表所有用户有效
    soft:软限制,超过会报warn
    hard:实际限制
    nofile:文件句柄参数
    number:最大文件句柄数
    

    使用ulimit -n 查看open files 参数是否改变。

  • TCP/IP内核参数优化,建议参数如下:

    /etc/sysctl.conf添加以下代码,可以执行sysctl -p命令或者重启后永久生效

    net.core.rmem_default = 8388608
    net.core.rmem_max = 16777216
    net.core.wmem_default = 8388608
    net.core.wmem_max = 16777216
    net.core.netdev_max_backlog = 2000
    net.core.somaxconn = 2048
    net.core.optmem_max = 81920
    net.ipv4.tcp_mem = 131072  262144  524288
    net.ipv4.tcp_rmem = 8760  8388608  16777216
    net.ipv4.tcp_wmem = 8760  8388608  16777216
    net.ipv4.tcp_keepalive_time = 60
    net.ipv4.tcp_keepalive_probes = 3
    net.ipv4.tcp_keepalive_intvl = 20
    
    vm.overcommit_memory=1
    

5、设置TcpNodelay和TcpKeepAlive

  • TCPNoDelay和TCPkeepalive都是常用的TCP选项,前者的作用是禁用Nagle 算法6,避免连续发包出现延迟,这对编写低延迟网络服务很重要。后者的作用是定期探查TCP连接是否还存在。一般来说如果有应用层心跳的话,TCPkeepalive不是必需的。

  • TCPNoDelay设置参考代码:

    int sock, flag, ret;
    /* Create new stream socket */
    sock = socket( AF_INET, SOCK_STREAM, 0 );
    /* Disable the Nagle (TCP No Delay) algorithm */
    flag = 1;
    ret = setsockopt( sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(flag) );
    if (ret == -1) {
      printf("Couldn't setsockopt(TCP_NODELAY)\n");
      exit(-1);
    }
    
  • TCPkeepalive内核中设置参考上面第4点,应用程序中设置参考如下:

    int keepAlive = 1; // 开启keepalive属性
    int keepIdle = 60; // 如该连接在60秒内没有任何数据往来,则进行探测 
    int keepInterval = 5; // 探测时发包的时间间隔为5 秒
    int keepCount = 3; // 探测尝试的次数.如果第1次探测包就收到响应了,则后2次的不再发.
    
    setsockopt(rs, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepAlive, sizeof(keepAlive));
    setsockopt(rs, SOL_TCP, TCP_KEEPIDLE, (void*)&keepIdle, sizeof(keepIdle));
    setsockopt(rs, SOL_TCP, TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
    setsockopt(rs, SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
    
  • 见《Linux多线程服务编程》8.9节

6、使用std::vector作为数据缓冲区

使用 std::vector 作为数据缓冲区的优化方案。该方案的核心在于动态内存分配,这可能会让人联想到内存的存放位置以及性能影响。

  • 内存分配位置:
    std::vector 内部维护了一个动态数组,用于存储元素。该动态数组的内存空间位于堆上,而非栈上。

    原因分析:
    栈内存通常用于存储函数局部变量和临时数据,其空间大小由编译器预先分配,并随着函数的调用和销毁而自动管理。
    std::vector 的元素数量和大小可能在运行时发生变化,因此无法预先分配固定大小的栈空间。
    堆内存可以动态分配和释放,满足 std::vector 动态管理内存的需求。

  • 性能影响:

    使用 std::vector 作为数据缓冲区可能会带来一定的性能开销,主要体现在以下方面:

    内存分配/释放: 动态分配和释放内存空间需要额外的系统调用和操作,相比栈内存的自动管理,会带来一定的性能损耗。
    内存碎片: 频繁的内存分配和释放可能会导致内存碎片,降低内存的使用效率,并增加额外的内存管理开销。
    数据复制: 当 std::vector 的容量不足时,需要重新分配内存空间并复制原有数据,这也会带来额外的性能开销。
    具体请参考https://gitee.com/liudegui/finger_server/blob/master/common/Buffer.cpp


7、对于tcp 客户端

# 1、修改/etc/sysctl.conf文件,增加下面的几个参数
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_tw_buckets = 10000

优化完内核参数后,可以执行sysctl -p命令,来激活上面的设置永久生效


# 2、解释
# 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭;
net.ipv4.tcp_tw_reuse = 1
# 表示系统同时保持TIME_WAIT套接字的最大数量,(默认是18000).
net.ipv4.tcp_max_tw_buckets = 10000
# 参考:https://zhuanlan.zhihu.com/p/567088021
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值