发送端系统: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

标签: 接字, linux, Socket, printf, size, include, struct, tv

相关文章推荐

添加新评论,含*的栏目为必填