[TCP]TCP 文件传输速度优化

最近写了一个TCP文件传输的C/S程序。
Client是windows C++程序。
Server是Linux C++(ubuntu)程序,虚拟机。
作用是,可以从客户端向服务端传输文件(单线程)。

测试用例是一个wireshark抓包文件,文件大小约235m。

初始的测试结果发现,传输完文件需要
Client debug版:5’42’‘,
Client release版: 2’30’'。

同样的文件,vscode从ubuntu下载到windows,耗时约19‘’半(测试了3次都是19’'多一点),
使用FileZilla Client,只要2‘’半(怎么做到这么快的?)。
时间差距这么大,感觉很离谱,因此我想借这个机会研究一下,怎么增大C++程序的TCP传输速度。
完整代码在这里

优化TCP传输速度的方法

  1. 把一个文件分成几块,每块用一个线程来传输,这样应该更快。(我觉得这个方法可以,但是还没试,这个文章中也不准备尝试这个方法。)
  2. 因为是文件传输,所以优化文件读写,应该可以节省一些时间。(按字节读写文件,按照指定buff大小循环读写文件,零拷贝sendfile)
  3. 设置TCP缓冲区的大小(LINUX)
  4. TCP_NODELAY选项,禁用 Nagle 算法
  5. TCP_CORK选项
  6. 虚拟机由桥接改成NAT
    暂时搜集到这些方法,No1后面再尝试。

二、优化发送和接受的代码

目前用的方法就是对文件read和write指定大小的数组。尝试以下几种办法。
linux缓冲区设置为:
查看当前的TCP缓冲区大小设置

zlc@192:~/share$ sysctl  net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 2048        2048    4096

代码中不使用setsockopt修改SO_RCVBUFF。

按照字节读写文件

这个先不考虑了。

按照指定buff大小循环读写文件

这个就是当前使用的方法,文件传送时间:56‘’
在这里插入图片描述

零拷贝sendfile()

因为我这个工程是windwos+linux的,winddows没有提供零拷贝的系统函数,好像没法进行零拷贝。
所以零拷贝会单独写一个文章来学习Linux中的使用。

三、修改TCP缓冲区的大小

TCP的缓冲区会影响TCP的窗口大小,所以是会影响传输速度的。
关于缓冲区和窗口的关系,可以看这篇文章:
linux高性能网络编程之tcp连接的内存使用

可以通过代码setsockopt来修改单个FD的SO_ECVBUFF的大小,
也可以通过sysctl.conf来修改所有TCP连接的接受缓冲区大小。

查看允许的TCP接受缓冲区最大值

zlc@192:~/share$ sysctl  net.core.rmem_max
net.core.rmem_max = 212992

查看当前的TCP缓冲区大小设置

zlc@192:~/share$ sysctl  net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 1024        1024    2048

使用默认系统设置

此时的代码逻辑:
Client:每个循环从文件中读取64kb字节,然后send 64kb字节。
Server:每个循环为recv准备64kb的buff,把读取到的字节,全部写入文件。
Client部分:

	int buffsize = 64 * 1024; //64KB
    char* buff = new char[buffsize];

    ifs.seekg(0);
	while (!ifs.eof()&& remain_size > 0 && ifs.read(buff, remain_size > buffsize ? buffsize : remain_size))
	{
		int count = send(conn->conn_socket, buff, remain_size > buffsize ? buffsize : remain_size, 0);
		if (count < 0)
		{
			if (WSAGetLastError() == EWOULDBLOCK || WSAGetLastError() == EAGAIN)
			{
				perror("send file try again");
				return 0;
			}
			perror("send file");
			char msg_ipv4[128] = { 0x00 };
			inet_ntop(AF_INET, &conn->ipv4_addr.sin_addr, msg_ipv4, 128);
			LOG(3, "disconnect", msg_ipv4, I2A(conn->ipv4_addr.sin_port));
			FD_CLR(conn->conn_socket, fds);
			closesocket(conn->conn_socket);
			return -1;
		}
		else if (count == 0)
		{
			if (WSAGetLastError() != EINTR)
			{
				perror("trans_init send");
				char msg_ipv4[128] = { 0x00 };
				inet_ntop(AF_INET, &conn->ipv4_addr.sin_addr, msg_ipv4, 128);
				LOG(3, "disconnect", msg_ipv4, I2A(conn->ipv4_addr.sin_port));
				FD_CLR(conn->conn_socket, fds);
				closesocket(conn->conn_socket);
				return 0;
			}
		}
		remain_size -= count;
		sendsize += count;
        //std::cerr << "remain_size:" << remain_size << std::endl;
	}

Server部分:

	int buffsize = 64*1024;
    char* buff = new char[buffsize];   

    std::cout << std::endl;
    int max_recv_size_every_loop = 0;
    conns[event_fd]->ofs.seekg(0);
    while (max_count < conns[event_fd]->filesize)
    {   
        int count = recv(event_fd,buff, buffsize, 0);   
        if(count == 0 && errno != EINTR)
        {
            LOG(1, "recv disconnect.");
            RestoreFDstat(event_fd);
            epoll_clt_del(event_fd);
            close(event_fd);
            return -1;
        }
        else if(count == -1)
        {
            if(errno == EINTR)
                continue;
            else if(errno == EAGAIN || errno == EWOULDBLOCK)
            {
                LOG(1, "recv end.");
                RestoreFDstat(event_fd);
                return max_count;
            }
            perror("\nrecv error:");
            RestoreFDstat(event_fd, msg_fail);
            // epoll_clt_del(event_fd);
            // close(event_fd);
            return -1;
        }
        max_count += count;        
        conns[event_fd]->ofs.write(buff,count);

        if(max_recv_size_every_loop < count)
        {
            max_recv_size_every_loop = count;
            std::cout << "max_recv_size_in_loop is " << max_recv_size_every_loop << std::endl;
        }        
    }

试一下,这样的情况,传输文件需要的时间。
Server处用getsockopt取得了SO_RCVBUFF的值,此时accept到的连接FD的recv缓冲区是2048字节。
在这里插入图片描述
传输完成,传输用时1’43’'。循环中,recv返回的最大字节数是512字节。
在这里插入图片描述

修改sysctl.conf

把net.ipv4.tcp_rmem大幅度调大

上面recv函数的返回值最大就到了512就不再增长了。
我认为是这个512太小了。
发现net.ipv4.tcp_rmem的默认值是1024,是不是跟他有关。

zlc@192:~/share$ sudo sysctl -a
net.ipv4.tcp_rmem = 1024        1024    2048

编辑/etc/sysctl.conf然后执行sudo sysctl -p把net.ipv4.tcp_rmem调整成:

zlc@192:~/share$ sudo sysctl -a
net.ipv4.tcp_rmem = 65535       65535   131070

执行结果:
在这里插入图片描述
WTF?竟然半小时多???
在这里插入图片描述
我又放了一个晚上,第二天早上再传一次(虚拟机时间不准确,显示18:00,其实已经第二天早上10:00了)。
在这里插入图片描述
TMD!!!又13‘’了。。。。。
在这里插入图片描述
因为我调整了TCP缓冲区的大小,扩大了不少,我认为13‘’这个才是预期的数值。传输时间是半小时,太离谱了。
我又重复了几遍,就不截图了,只记录执行传输的时间:
(1)15‘’
(2)14‘’
(3)12‘’
(4)重启windows+重启ubuntu虚拟机,2’39’’
(5)再次传输,2‘33‘’
目前很苦恼,这是为什么,出现这么大的起伏。
想起网上的各种攻略中,也说到缓冲区调的太大,不一定会对网络传输启动积极作用,反而会增加拥塞风险。

破案了

因为我用的是wifi,平时家里爸爸妈妈会连wifi看抖音…之前测试的十几秒的情况,都是早上或者晚上,爸妈休息的时候测出来的。爸妈一开始看抖音,我这边传输就会拥塞。
今天又趁着爸妈出门溜达的时间,测了几次,传输时间又回归20’'了。
在这里插入图片描述在这里插入图片描述

把net.ipv4.tcp_rmem小幅度调大

net.ipv4.tcp_rmem的默认值是1024,给他翻倍。
编辑/etc/sysctl.conf然后执行sudo sysctl -p把net.ipv4.tcp_rmem调整成:

zlc@192:~/share$ sudo sysctl -a
net.ipv4.tcp_rmem = 2048   2048  4096

执行结果:56’‘。
在这里插入图片描述
在这里插入图片描述
重复几次:
(1)1’05’’
(2)1’03’’
(3)重启ubuntu,再次测试:1’05’’
跟上一个比,这个速度很稳定。

通过sersockopt设置FD的recv缓冲区大小

net.ipv4.tcp_rmem还原成1024 1024 2048
查看net.core.rmem_max

net.core.rmem_max = 212992

把SO_RCVBUFF设置成:4 * 1024。Accept部分添加以下代码:

	//Check sock opt
    //set recv buff
    int recvbufflength = 4 * 1024;
    len = sizeof(recvbufflength);
    setsockopt(conn_fd, SOL_SOCKET, SO_RCVBUF, &recvbufflength,len ); // ←新增
    recvbufflength = 0;
    getsockopt(conn_fd, SOL_SOCKET, SO_RCVBUF, &recvbufflength,&len );
    LOG(2, "SOCKET recv buff length:", I2A(recvbufflength));

执行传输。
在这里插入图片描述
用时2分半。(‘ 。’ 我家的网络无法承受这么大的缓冲区吗 )
在这里插入图片描述

再试一下Setsockopt是否真的生效了

调整tcp_rmem:

net.ipv4.tcp_rmem = 2048 2048 4096

调整代码:

	//Check sock opt
    //set recv buff
    int recvbufflength = 512;
    len = sizeof(recvbufflength);
    setsockopt(conn_fd, SOL_SOCKET, SO_RCVBUF, &recvbufflength,len ); // ←新增
    recvbufflength = 0;
    getsockopt(conn_fd, SOL_SOCKET, SO_RCVBUF, &recvbufflength,&len );
    LOG(2, "SOCKET recv buff length:", I2A(recvbufflength));

执行传输。
在这里插入图片描述

这里有点问题,如果不进行setsockopt,获取的SO_RCVBUFF是2048。设置了以后,获取的SO_RCVBUFF不是512 * 2,而是2304。

linux高性能网络编程之tcp连接的内存使用
这篇文章有以下解释:

当设置了SO_SNDBUF时,就相当于划定了所操作的TCP连接上的写缓存能够使用的最大内存。然而,这个值也不是可以由着进程随意设置的,它会受制于系统级的上下限,当它大于上面的系统配置wmem_max(net.core.wmem_max)时,将会被wmem_max替代(同样翻一倍);而当它特别小时,例如在2.6.18内核中设计的写缓存最小值为2K字节,此时也会被直接替代为2K。

我看见网上有人写,setsockopt要放到listen()之前,我试下来如果操作accept到的fd,放到accept之后也可以生效。

四、 TCP_NODELAY选项

此时ubuntu系统设置:

net.ipv4.tcp_rmem = 2048 2048 4096

不激活TCP_NODELAY的传输时间:45‘’
在这里插入图片描述
激活TCP_NODELAY,加在listen之前:

	 //set TCP_NODELAY 
    int flag = 1;
    setsockopt(listen_fd, IPPROTO_TCP, TCP_NODELAY, &flag, sizeof(flag));

56‘’,没有明显变化。
在这里插入图片描述
在accept之后也加一个,54‘’
在发送端,connect之前也加一个,55’'。
TCP_NODELAY会禁用Nagle算法(合并小包),减少延迟,但是看来在我这个需求中,没有明显作用。

五、 TCP_CORK选项

TCP_CORK选项用来关闭CORK算法。
Nagle和CORK算法的区别:
TCP_CORK配置选项和TCP_NODELAY Nagle算法的异同
按照“四”中的方法, 添加TCP_CORK选项(windows好像无法设置)。传输用时56’'。跟“四”比没有明显提升。
在这里插入图片描述
没有提升也可以理解,Nagle等算法的目的是减少网络中的小包,但是这里传输的是文件,不停的把文件buff拷贝到内核缓冲区,应该不会存在很多小包,所以本身也没有算法导致的延迟。

六、虚拟机改成NAT模式

由于不经过路由转发,传输时间几乎稳定在2’'。
VMWARE NAT设置:
虚拟机网络模式(NAT模式主机访问虚拟机)

在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现一个基于 TCP文件传输服务器,你可以按照以下步骤进行: 1. 创建一个 TCP 服务器端程序。你可以使用编程语言(如Python、Java、C++等)提供的网络编程库来实现。在服务器端程序中,你需要创建一个 TCP socket,并绑定到指定的 IP 地址和端口上。 2. 等待客户端的连接请求。使用 socket 的 listen() 函数开始监听来自客户端的连接请求。 3. 接受客户端连接。一旦有客户端发起连接请求,使用 socket 的 accept() 函数接受连接,并返回一个新的 socket 对象用于与客户端进行通信。 4. 接收文件请求。与客户端建立连接后,你可以定义一种协议来传输文件。例如,可以约定客户端发送一个特定的命令来请求文件传输。 5. 打开文件并读取内容。在服务器端,根据客户端请求的文件名,打开相应的文件,并读取文件内容。 6. 将文件内容发送给客户端。使用 socket 的 send() 函数将文件内容逐个数据包发送给客户端。你可以定义合适的数据包大小(如 1024 字节)来进行分包发送。 7. 客户端接收并保存文件内容。在客户端程序中,接收服务器发送的数据包,并将数据包逐个写入文件中,最终组合成完的文件。 8. 关闭连接。文件传输完成后,关闭与客户端的连接,释放相关资源。 需要注意的是,以上只是一个简单的文件传输服务器的基本流程。实际上,你可能还需要考虑错误处理、并发连接、文件权限等其他方面的问题。此外,为了提高性能和安全性,你还可以对文件传输进行优化和加密处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值