发送端系统: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中,并返回内容数量
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
限速为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