Socket套接字的速率控制(linux)

发送端系统:ubuntu18.04
接收端系统:ubuntu18.04
最近要做一些socket的实验,我对socket也不大了解,不过socket还算是比较好学的,算是在应用层和传输层中间,给你提供了调用了传输协议的api,还是很友好的哦!
哦吼!我要对socket发送文件的速率进行限制,想要把文件传输速率限制到想要设置的速率。大概原理如下:

一、主要原理

比如说,我要把文件的传输速率限制到10Mbps,他等同于,在一秒钟传输10Mbit的内容。所以我们需要定时器+文件传输限制。大概就这两部分。
socket传输文件可以看一下这篇文章:Linux下基于TCP的简易文件传输(socket编程)

1.1 定时器

定时器的话采用linux C语言中的时间函数clock_gettime,函数原型如下:

int clock_gettime(clockid_t clk_id, struct timespec *tp);

其中,cld_id类型四种:

a、CLOCK_REALTIME:系统实时时间,随系统实时时间改变而改变
b、CLOCK_MONOTONIC,从系统启动这一刻起开始计时,不受系统时间被用户改变的影响
c、CLOCK_PROCESS_CPUTIME_ID,本进程到当前代码系统CPU花费的时间
d、CLOCK_THREAD_CPUTIME_ID,本线程到当前代码系统CPU花费的时间

其中,timespec结构包括:

struct timespec {
time_t tv_sec; /* 秒*/
long tv_nsec; /* 纳秒*/
};

1.2 限制文件传输大小

我们采用fread函数传输文件,其传输过程的文件大小是可以指定的,函数原型如下:

size_t   fread(   void   *buffer,   size_t   size,   size_t   count,   FILE   *stream   ) 

函数从输入文件(数据流)中读取size*count字节 存放到buffer中,并返回内容数量
Socket套接字的速率控制(linux)

2 功能描述

不同文章描述的发送端和接收端还有客户端和服务器端不一致,最开始看的很糊涂,这里就只用发送端和接收端描述。
功能:可以实现socket文件传输的速率控制,并输出socket程序运行时间,输出抓取文件的速率。我测试了好多次,情况大概如下:

2.1 文件传输速率设置超过瓶颈带宽时

也就是RATE(设置的限速)大于客户端与接收端的bottleneck bandwidth(瓶颈带宽)的时候,会产生段错误,我也不是很清楚为什么会这样。
关于如何得到瓶颈带宽的大小,可以用iperf对带宽进行测量,iperf命令可以参考:iperf命令详解
但是对网卡进行限速时就不会有这样的情况,比如说,我把网卡限速为100Mbps,即限制出网卡流量速率为100Mbps,传输过程的瓶颈带宽为800Mbps,这时候对socket文件传输设置为200Mbps,那么能够得到的socket文件的传输速率就为100Mbps,因为网卡的限速啊。

2.2 测量瓶颈带宽

在不限制速率的情况下可以测试瓶颈带宽,不过需要删掉“定时器”,让它传就可以了,不用限时。比较耗费带宽资源。而且程序需要更改。不如直接用iperf,哈哈哈!但是如果要自己做测量带宽的工具的话,可以作一下参考。呃!可能也没啥用[捂脸笑]!!!

3 编程

在这篇文章的基础上改动的,所以可能在输出的时候不是很好看。呃,太懒了,不太想改了…太难了…

3.1 发送端:

#include <sys/types.h> 		//socket
#include <sys/socket.h>		//socket
#include <arpa/inet.h>		//inet_pton,inet_ntop
#include <stdio.h>		//printf
#include <stdlib.h>		//exit
#include <string.h>		//bzero
#include <netinet/in.h>		//sockaddr_in
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>		//struct stat
#include <time.h>		//clock

#pragma comment (lib, "ws2_32.lib") //加载 ws2_32.dll
#define SERVER_PORT 8000 //监听本机8000端口
#define MAX 4096
#define BUF_SIZE 1024
#define Count_buff 10 //最好设置为10,可以最大化带宽利用
//设置需要限制的速率,单位为Mbps
#define RATE 50    // Mbps

void empty_stdin() {
    int c;
    do {
        c = getchar();
    } while (c != '\n' && c != EOF);
}

//定义时间差函数,获取时间差
struct timespec diff(struct timespec start, struct timespec end)
{
	struct timespec temp;
	temp.tv_sec = end.tv_sec - start.tv_sec;
	temp.tv_nsec = end.tv_nsec - start.tv_nsec;
	if(temp.tv_sec < 0)
	{
		printf("time getting error\n");
		temp.tv_sec = -temp.tv_sec;
		temp.tv_nsec = -temp.tv_nsec;
	}

	return temp;
}



int main(void) 
{
	struct sockaddr_in serveraddr,clientaddr;
	int sockfd,addrlen,confd,len;
	char ipstr[128];

//	struct timespec time1,time2,temp;
	struct timespec time1 = {0, 0};
	struct timespec time2 = {0,0};
	struct timespec temp = {0,0};	
	struct timespec temp2 = {0,0};	
	
	pid_t pid;
	//1.socket
	sockfd = socket(AF_INET,SOCK_STREAM,0);
	//2.bind
	bzero(&serveraddr,sizeof(serveraddr));
	//地址族协议ipv4
	serveraddr.sin_family = AF_INET;
	//ip地址
	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
	serveraddr.sin_port = htons(SERVER_PORT);
	bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	//3.listen
	listen(sockfd,20);//128作为可同时链接的数量上线
	//4. accept阻塞监听客户端的链接请求
	addrlen = sizeof(clientaddr);
	confd = accept(sockfd,(struct sockaddr *)&clientaddr,&addrlen);
	//如果有客户端连接上服务器,就输出客户端的ip地址和端口号
	printf("client ip %s\tport %d\n",
	inet_ntop(AF_INET,(struct sockaddr *)&clientaddr.sin_addr.s_addr,ipstr,sizeof(ipstr)),ntohs(clientaddr.sin_port));
    
	int flag=1;
	while(flag){
		char fdownload[100] = {0};
        	recv(confd,fdownload,100,0);//接收客户端的下载请求获得文件名
        
		if(!strcmp(fdownload,"quit")) break;//客户端输入quit退出程序  
		else{
			FILE *fp = fopen(fdownload, "rb"); //以二进制方式打开文件
       			if(fp == NULL){
            			printf("Cannot open file, press any key to exit!\n");
				break;
           		 }
			struct stat statbuf; //这三行代码获得文件的大小,得到文件字节数
        		stat(fdownload,&statbuf);
        		int size=statbuf.st_size;
			char t[20];
			printf("%d \n",size);//文件大小
			sprintf(t,"%d",size);//转换整数到字符型数据
			//printf("start transfer\n");
			send(confd,t,20,0);//把文件大小发给客户端
	   		char buffer[BUF_SIZE] = {0}; //缓冲区
       			long nCount,mc=0;
			long si,sj,m,c;
      			recv(confd,t,4,0);//获得开始传输指令
			if(t[0]=='o'){
			printf("start transfer\n");
			clock_gettime(CLOCK_MONOTONIC,&time1);
			int n_fread = 0;	//n_fread为读取文件的总次数
			int n = 0;
			//计算1s中读取的次数 
			int times = (RATE*1024*1024)/(Count_buff * BUF_SIZE);
			long transflag = 1;
			
			while(transflag){
				n = 0;
				//temp2清0
				temp2 = diff(time1,time1);
//				printf("n is %d,temp2.tv_sec is %d\n", n, temp2.tv_sec);
				clock_gettime(CLOCK_MONOTONIC,&time2);
//当传输时间小于1s,并且发送次数小于times时,进行文件传输,而当限制的速率大于瓶颈带宽时,只能跑到带宽限速
				while((n < times) && (temp2.tv_sec < 1)){
/*一下内容开始发送文件内容 ,因为文件大小会超出发送缓冲区大小,因此在这里循环调用send()函数进行发送,每一次发送nCount个字节,每次读取Count_buff次,每次读取字节大小为BUF_SIZE*/
				nCount = fread(buffer, Count_buff, BUF_SIZE, fp);
				n_fread = n_fread + 1;
				mc=mc+nCount;
				si=mc*30/size;//这里开始计算传输比例,式字计算顺序不能更改,否则出现数据溢出,而产生错误。
				m=si-sj;
				 
//		  	 	printf("%*s|%d%%",30-si,"",(mc*100/size));//在固定位置打印百分数
		  		printf("%*s|%d%%",30-si,"",(Count_buff*mc*100/size));//在固定位置打印百分数
				printf("\r\033["); //退格
            			for(int t=0;t<si+1;t++)
				 {
					  printf(">");
					  setbuf(stdout, NULL);
				 }

            			send(confd, buffer, nCount, 0);
				 
				sj=si;
				transflag = nCount;
				n++;
				}
				while(temp2.tv_sec < 1){	
					clock_gettime(CLOCK_MONOTONIC,&temp);
					temp2 = diff(time2,temp);
					sleep(0.001);
				}
				
         		}
			printf("transfer success!\n");
			clock_gettime(CLOCK_MONOTONIC,&time2);
			temp = diff(time1,time2);
			//计算文件传输速率,这里的文件传输速率并不是很精准,用的是文件大小除以总文件传输时间
			double rate = (double) size / (1048576 * (temp.tv_sec + temp.tv_nsec/1000000000)); 
			//计算每次读取文件所需要的时间,即用总时间除以读取总次数
			double fre = (double) (1000 * temp.tv_sec + temp.tv_nsec/1000000)/n_fread;
			printf("The frequency of extracting file:%-.4f ms per reading\n",fre);
			printf("The rate of transmission:%-.4f Mbps\n",rate);
			printf("Time of Program:%-.4Fs\n",(float)(temp.tv_sec + temp.tv_nsec/1000000000));
			fclose(fp);
		}
		}
	}
	close(confd);
	//close(sockfd);
		

	return 0;
}

3.2接收端:

#include <netinet/in.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <ctype.h>
#include <stdlib.h>
 
#define HELLO_WORLD_SERVER_PORT       6666  
#define BUFFER_SIZE                   1024  
#define FILE_NAME_MAX_SIZE            512  
#define BUF_SIZE 10240  
 
#define SERVER_PORT 8000
#define MAXLINE 4096
 
int main(void)
{
	struct sockaddr_in serveraddr;
	int confd,len;
	char ipstr[] = "210.26.118.200";//这是服务器的地址,使用ifconfig来查看
	char buf[MAXLINE];
	//1.创建一个socket
	confd = socket(AF_INET,SOCK_STREAM,0);
	//2.初始化服务器地址,指明我要连接哪个服务器
	bzero(&serveraddr,sizeof(serveraddr));
	serveraddr.sin_family = AF_INET;
	inet_pton(AF_INET,ipstr,&serveraddr.sin_addr.s_addr);
	serveraddr.sin_port = htons(SERVER_PORT);
	//3.链接服务器
	connect(confd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
	

    int flag=1,size;
    char t[20];
    memset(&t,0,sizeof(t));
    while(flag){
         char fdownload[100] = {0};
         printf("Input filename to download: ");
         gets(fdownload);
         if(!strcmp(fdownload,"quit")) break;
         send(confd,fdownload,100,0);
         char filename[100] = {0}; //文件名
         len=recv(confd,t,20,0);
         size=atoi(t);
         double sizel;
         sizel=size/1048576.0;
         printf("filesize; %.2f MB\n ",sizel);
         printf("Input filename to save: ");
         gets(filename);
         if(!strcmp(filename,"quit")) break;
         else{
                FILE *fp = fopen(filename, "wb"); //以二进制方式打开(创建)文件
                if(fp == NULL){
                    printf("Cannot open file, press any key to exit!\n");
                    break;
                }
                send(confd,"o",4,0);
                char buffer[BUF_SIZE] = {0}; //文件缓冲区
                long nCount,mc=0;
                long si,sj,m,c;
                printf("Start receive!\n");
            
                while( (nCount = recv(confd, buffer, BUF_SIZE, 0)) > 0 ){
	                    mc=mc+nCount;
			            si=mc*30/size;
			            m=si-sj;
				 
		                printf("%*s|%d%%",30-si,"",(mc*100/size));
			              printf("\r\033["); //退格
                        for(int t=0;t<si+1;t++)
				                {
					                 printf(">");
					                 setbuf(stdout, NULL);
				                }
                        fwrite(buffer, nCount, 1, fp);
                        if(mc==size) break;
                }
            printf("Transfer success!\n");
            fclose(fp);
        }
    }
close(confd);
return 0;
}

3.3 实验结果

限速为100Mbps
Socket套接字的速率控制(linux)

限速为20Mbps
Socket套接字的速率控制(linux)
限速为50Mbps,网卡限速20Mbps
Socket套接字的速率控制(linux)
之所以比20Mbps大一丢丢,我觉得是因为测速率的算法太粗糙了。上面都是显示的文件传输99%,其实是传输完的,只不过输出显示并不是很适配。

啊!以上!顺心顺意!!!

参考:
https://blog.csdn.net/qq_43212988/article/details/106901493?&spm=1001.2101.3001.4242
https://blog.csdn.net/weixin_30588907/article/details/99359801
https://www.cnblogs.com/melons/p/5791874.html

上一篇:史上最牛逼的导航网站(很全很详细)


下一篇:Redis五种数据结构