发送端系统:ubuntu18.04
接收端系统:ubuntu18.04
最近要做一些socket的实验,我对socket也不大了解,不过socket还算是比较好学的,算是在应用层和传输层中间,给你提供了调用了传输协议的api,还是很友好的哦!
哦吼!我要对socket发送文件的速率进行限制,想要把文件传输速率限制到想要设置的速率。大概原理如下:
比如说,我要把文件的传输速率限制到10Mbps,他等同于,在一秒钟传输10Mbit的内容。所以我们需要定时器+文件传输限制。大概就这两部分。
socket传输文件可以看一下这篇文章:Linux下基于TCP的简易文件传输(socket编程)
定时器的话采用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; /* 纳秒*/ };
我们采用fread函数传输文件,其传输过程的文件大小是可以指定的,函数原型如下:
size_t fread( void *buffer, size_t size, size_t count, FILE *stream )
函数从输入文件(数据流)中读取size*count字节 存放到buffer中,并返回内容数量
不同文章描述的发送端和接收端还有客户端和服务器端不一致,最开始看的很糊涂,这里就只用发送端和接收端描述。
功能:可以实现socket文件传输的速率控制,并输出socket程序运行时间,输出抓取文件的速率。我测试了好多次,情况大概如下:
也就是RATE(设置的限速)大于客户端与接收端的bottleneck bandwidth(瓶颈带宽)的时候,会产生段错误,我也不是很清楚为什么会这样。
关于如何得到瓶颈带宽的大小,可以用iperf对带宽进行测量,iperf命令可以参考:iperf命令详解
但是对网卡进行限速时就不会有这样的情况,比如说,我把网卡限速为100Mbps,即限制出网卡流量速率为100Mbps,传输过程的瓶颈带宽为800Mbps,这时候对socket文件传输设置为200Mbps,那么能够得到的socket文件的传输速率就为100Mbps,因为网卡的限速啊。
在不限制速率的情况下可以测试瓶颈带宽,不过需要删掉“定时器”,让它传就可以了,不用限时。比较耗费带宽资源。而且程序需要更改。不如直接用iperf,哈哈哈!但是如果要自己做测量带宽的工具的话,可以作一下参考。呃!可能也没啥用[捂脸笑]!!!
在这篇文章的基础上改动的,所以可能在输出的时候不是很好看。呃,太懒了,不太想改了…太难了…
#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; }
#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; }
限速为100Mbps
限速为20Mbps
限速为50Mbps,网卡限速20Mbps
之所以比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