【C语言】TCP测速程序

一、服务端(recv)

下面是一个用 C 语言编写的测试 TCP 传输速度的基本程序示例。

这只是一个简单示例,没有做详细的错误检查和边缘情况处理。在实际应用中,可能需要增加更多的功能和完善的异常处理机制。

TCP 服务器 (server.c):

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 12345
#define BUFFER_SIZE 1024

int main() {
   int server_fd, new_socket;
   struct sockaddr_in address;
   int opt = 1;
   int addrlen = sizeof(address);
   char buffer[BUFFER_SIZE] = {0};

   // 创建 socket 文件描述符
   if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
       perror("socket failed");
       exit(EXIT_FAILURE);
   }

   // 设置 socket 选项
   if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
       perror("setsockopt");
       exit(EXIT_FAILURE);
   }

   address.sin_family = AF_INET;
   address.sin_addr.s_addr = INADDR_ANY;
   address.sin_port = htons(PORT);

   // 绑定 socket 到端口
   if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
       perror("bind failed");
       exit(EXIT_FAILURE);
   }

   // 监听端口
   if (listen(server_fd, 3) < 0) {
       perror("listen");
       exit(EXIT_FAILURE);
   }

   // 接受连接
   if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0) {
       perror("accept");
       exit(EXIT_FAILURE);
   }

   ssize_t bytes_read;
   printf("Server started, waiting for data...\n");

   // 清空 buffer
   memset(buffer, 0, BUFFER_SIZE);

   // 接收数据
   while ((bytes_read = recv(new_socket, buffer, BUFFER_SIZE, 0)) > 0) {
       // 可以在这里处理 buffer 数据,也可以添加其他逻辑,比如计算接收到的总字节数
       // 这里只做简单的演示,仅打印接收到的字节数
       printf("Bytes received: %zd\n", bytes_read);
   }

   if (bytes_read < 0) {
       perror("recv");
       exit(EXIT_FAILURE);
   }

   printf("Receiving data finished.\n");

   // 关闭连接
   close(new_socket);
   close(server_fd);

   return 0;
}

二、客户端(send)

下面是用 C 语言编写的一个简单的 TCP 客户端程序,用来测试与 TCP 服务器之间的传输速度。这个程序会连接到指定服务器的指定端口,并发送固定大小的数据包一定次数,然后统计并显示传输速度。

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>

#define SERVER_IP   "127.0.0.1" // 服务器 IP 地址
#define SERVER_PORT 12345        // 服务器端口号
#define DATA_SIZE   1024         // 发送数据包的大小 (bytes)
#define TOTAL_PACKS 10000        // 发送数据包的总数

int main() {
   int sock;
   struct sockaddr_in server_addr;
   char data[DATA_SIZE];
   ssize_t sent_bytes;
   clock_t start_time, end_time;
   double total_time, speed;

   // 创建 socket
   sock = socket(AF_INET, SOCK_STREAM, 0);
   if (sock < 0) {
       perror("socket");
       exit(EXIT_FAILURE);
   }

   // 指定服务器地址
   memset(&server_addr, 0, sizeof(server_addr));
   server_addr.sin_family = AF_INET;
   server_addr.sin_port = htons(SERVER_PORT);
   inet_aton(SERVER_IP, &server_addr.sin_addr);

   // 连接到服务器
   if (connect(sock, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
       perror("connect");
       close(sock);
       exit(EXIT_FAILURE);
   }

   // 准备发送的数据
   memset(data, 'A', sizeof(data));

   // 开始计时
   start_time = clock();

   // 发送数据
   for (int i = 0; i < TOTAL_PACKS; i++) {
       sent_bytes = send(sock, data, sizeof(data), 0);
       if (sent_bytes < 0) {
           perror("send");
           close(sock);
           exit(EXIT_FAILURE);
       }
   }

   // 结束计时
   end_time = clock();

   // 关闭连接
   close(sock);

   // 计算总时间 (秒)
   total_time = (double)(end_time - start_time) / CLOCKS_PER_SEC;

   // 计算传输速度
   speed = (TOTAL_PACKS * DATA_SIZE) / (total_time * 1024 * 1024); // Mbps

   printf("Total time: %.3f seconds\n", total_time);
   printf("Data sent: %d MB\n", (TOTAL_PACKS * DATA_SIZE) / (1024 * 1024));
   printf("Transmission speed: %.3f Mbps\n", speed);

   return 0;
}

要注意的几点:

这个程序仅用于示例,未包含复杂的错误检查和异常情况处理。

SERVER_IP 和 SERVER_PORT 应当设置为要连接的服务器的 IP 地址和端口号。

DATA_SIZE 和 TOTAL_PACKS 可以根据需求调整数据包大小和发送次数以测试不同的传输速度。

程序计算的传输速度是理论值,实际传输速度会受到网络条件、服务器处理能力等多种因素的影响。

根据操作系统和编译环境,编译命令可能稍有不同。

编译和运行该程序前,确保服务器端程序正在运行并且监听指定端口。程序编译命令示例:

bash 复制代码
gcc -o tcp_client tcp_client.c

运行:

bash 复制代码
./tcp_client

如果需要更精确的时间测量,可以考虑使用更高精度的时间函数,如 gettimeofday() 或 C11 标准的 timespec_get()。

三、服务端(write)/客户端(read)

1. 服务端(write)

cpp 复制代码
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include <sys/time.h>

int main()
{
    void *writebuf = NULL;
	int iolen;
    time_t t_start,  t_end;
    clock_t c_start, c_end;
    int iRet;
    struct timespec tpstart;
    struct timespec tpend;
    double timedif;
    struct timeval start, end;
    float time_use;
    int i;
    unsigned long maxsize;
    long l_timedif;
    int duration;
    unsigned long loop_times;

    //创建套接字
    int serv_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

    //将套接字和IP、端口绑定
    struct sockaddr_in serv_addr;
    memset(&serv_addr, 0, sizeof(serv_addr));  //每个字节都用0填充
    serv_addr.sin_family = AF_INET;  //使用IPv4地址
    serv_addr.sin_addr.s_addr = inet_addr("服务端ip");  //具体的IP地址
    serv_addr.sin_port = htons(12345);  //端口
    bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

    //进入监听状态,等待用户发起请求
    listen(serv_sock, 20);

    //接收客户端请求
    struct sockaddr_in clnt_addr;
    socklen_t clnt_addr_size = sizeof(clnt_addr);
    int clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);

    int optval;  
    socklen_t optlen = sizeof(optval);  
     // 获取发送缓冲区的大小  
    if (getsockopt(serv_sock, SOL_SOCKET, SO_SNDBUF, &optval, &optlen) == -1) {  
        perror("getsockopt");  
        exit(EXIT_FAILURE);  
    }  

    // 获取接收缓冲区的大小  
    if (getsockopt(serv_sock, SOL_SOCKET, SO_RCVBUF, &optval, &optlen) == -1) {  
        perror("getsockopt");  
        exit(EXIT_FAILURE);  
    }  

#if 0
    // 接收缓冲区
    int nRecvBuf=1024*1024;         //设置为1024K
    setsockopt(serv_sock,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));

    //发送缓冲区
    int nSendBuf=1024*1024;//设置为1024K
    setsockopt(serv_sock,SOL_SOCKET,SO_SNDBUF,(const char*)&nSendBuf,sizeof(int));
#endif

    maxsize = 4096;
    // 分配writebuf内存
    writebuf = malloc(maxsize);
    if ( NULL == writebuf )
    {
		printf("error to alloc writebuf memory\n");
    }

    for(i=0; i<(maxsize/sizeof(int)); i++)
    {
        *((int *)writebuf+i) = i;
    }

    for(i=0; i<0x10; i++)
    {
        printf("%02x ", *((unsigned char *)writebuf+i));
    }
	printf("\n");

	printf("\n");
	printf("write test...\n");
    loop_times = 100000;
    unsigned long ulDataMaxSize = (unsigned long)maxsize*loop_times;
    unsigned long ulTotalSize = 0;
    printf("ulDataMaxSize:%lu\n",ulDataMaxSize);
    t_start = time(NULL);  //单位为s,秒
	c_start = clock();    //CPU时钟计时单元数。处理器的CPU时间,而不是实际的时间
    gettimeofday(&start, NULL); //最小单位为us,微秒
    clock_gettime(CLOCK_MONOTONIC, &tpstart);   //最小单位为ns,纳秒
    for(i=0; i<loop_times; i++)
    {
        iRet = write(clnt_sock, writebuf, maxsize);
        if (0 >= iRet)
        {
            printf("ERROR in write data\n");
        }
        else
        {
            ulTotalSize += iRet;
        }
        //printf("This iteration written: %d, Total written: %lu\n", iRet, ulTotalSize);
        if( ulDataMaxSize <= ulTotalSize)
        {
            break;
        }
    }
    // 记录结束时间
    clock_gettime(CLOCK_MONOTONIC, &tpend);
    gettimeofday(&end, NULL);   //us
	c_end   = clock();  //单位为ms
    t_end = time(NULL); //单位为s

    duration = difftime(t_end, t_start);
    printf("time duration: %ds\n", duration);   //time
    printf("clock duration: %fs, CLOCKS_PER_SEC=%ld\n", (double)(c_end - c_start) / CLOCKS_PER_SEC, CLOCKS_PER_SEC);    //clock
    time_use=(end.tv_sec-start.tv_sec)*1000000+(end.tv_usec-start.tv_usec);//微秒
    printf("gettimeofday duration: %.10fs\n",time_use/1000000.0);  //gettimeofday
    l_timedif = 1000000*(tpend.tv_sec-tpstart.tv_sec)+(tpend.tv_nsec-tpstart.tv_nsec)/1000; //us,微秒
    timedif = (double)l_timedif/1000000.0;  //s,秒
    printf("clock_gettime duration: %fs\n", timedif);  //clock_gettime

    printf("write test speed: %fMB/s\n", maxsize*loop_times/timedif/1024.0/1024.0);
    printf("buffer size: %dB\n", maxsize);
    printf("i:%d, ulTotalSize: %luB\n", i, ulTotalSize);
    printf("Bandwidth: %.2fMbps\n", (maxsize*loop_times/timedif/1024.0/1024.0*8));

	printf("\n\n");

    if ( NULL != writebuf )
    {
        free(writebuf);
        writebuf = NULL;
    }

   
    //关闭套接字
    close(clnt_sock);
    close(serv_sock);

    return 0;
}

2.客户端(read)

cpp 复制代码
#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <netdb.h>  
#include <time.h>  
  
#define BUFFER_SIZE 4096
#define PORT 12345  
#define MAX_ATTEMPTS 100000  
  
int main(int argc, char *argv[]) 
{  
    int sockfd, portno, n;  
    struct sockaddr_in serv_addr;  
    struct hostent *server;  
    char buffer[BUFFER_SIZE];  
    int str_len;  
    double time_spent = 0.0;  
    double speed = 0.0;  
    clock_t start, end;  
    int i;  
    unsigned long ulTotalSize = 0;

    portno = PORT;  
    sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    server = gethostbyname("服务端ip或者host"); //获取主机名对应的IP地址。如果在实际使用中需要从网络中获取主机名对应的IP地址,可以使用getaddrinfo()函数代替gethostbyname()函数。  
    bzero((char *) &serv_addr, sizeof(serv_addr)); //清空serv_addr结构体,避免影响后续操作。  
    serv_addr.sin_family = AF_INET; //设置地址族为IPv4。  
    bcopy((char *)server->h_addr,   
          (char *)&serv_addr.sin_addr.s_addr,  
          server->h_length); //将主机名对应的IP地址复制到serv_addr结构体中。  
    serv_addr.sin_port = htons(portno); //设置端口号。注意:htons()函数是将主机字节序转换为网络字节序,因为网络传输中使用的字节序是网络字节序(大端序),而主机字节序可能是小端序。  
    connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //连接到发送端。如果连接失败,将返回-1,可以在此处添加错误处理代码。  
    start = clock(); //获取开始时间戳。  
    for (i = 0; i < MAX_ATTEMPTS; i++) 
    { //接收数据多次以计算平均速度。  
        n = read(sockfd, buffer, BUFFER_SIZE); //从socket中读取数据到buffer中。read()函数的返回值是实际读取的字节数,如果返回值小于请求的字节数,可能是因为连接被对方关闭了或者发生了错误。可以在此处添加错误处理代码。  
        if (n < 0) 
        { //如果读取失败,打印错误信息并退出程序。注意:在实际使用中,应该根据具体情况选择合适的错误处理方式,比如重新尝试连接或者关闭程序。  
            printf("Read failed\n");  
            return 1;  
        }  
        //处理接收到的数据,比如将数据写入文件或者进行其他操作。此处只是简单地将接收到的数据打印到控制台上。如果在实际使用中需要对数据进行更复杂的处理,可以在此处添加相应的代码。  
        //printf("%s\n", buffer);   
        ulTotalSize += n;
    }  
    end = clock(); //获取结束时间戳。  
    time_spent = ((double)(end - start)) / CLOCKS_PER_SEC; //计算时间差并转换为秒数。注意:CLOCKS_PER_SEC是一个宏,表示每秒钟的时钟周期数。它的值在不同的系统中可能不同,但在大多数系统中都定义为1000000或者更高。如果需要在不同系统中都能得到正确的结果,可以使用difftime()函数代替clock()函数来计算时间差。  
    speed = MAX_ATTEMPTS / time_spent; //计算平均速度,单位为"每秒传输的包数"(PPS)。注意:此处假设每次发送的数据包大小相同,如果实际使用中数据包大小不同,需要使用更复杂的方式来计算平均速度。可以在此处添加相应的代码来计算平均速度。如果需要对数据进行更复杂的处理,可以在此处添加相应的代码。  
    printf("Average speed: %.2lf PPS\n", speed); //打印平均速度到控制台。注意:在实际使用中,可能需要根据具体情况选择合适的输出方式
    speed = ulTotalSize/1024.0/1024.0/time_spent; //MB/s
    printf("Bandwidth: %.2lf Mbps\n", speed*8.0); 
    close(sockfd);
}
相关推荐
明月看潮生23 分钟前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
xinghuitunan1 小时前
蓝桥杯顺子日期(填空题)
c语言·蓝桥杯
Half-up1 小时前
C语言心型代码解析
c语言·开发语言
龙哥说跨境1 小时前
如何利用指纹浏览器爬虫绕过Cloudflare的防护?
服务器·网络·python·网络爬虫
懒大王就是我1 小时前
C语言网络编程 -- TCP/iP协议
c语言·网络·tcp/ip
半盏茶香2 小时前
【C语言】分支和循环详解(下)猜数字游戏
c语言·开发语言·c++·算法·游戏
小堇不是码农2 小时前
在VScode中配置C_C++环境
c语言·c++·vscode
Elaine2023912 小时前
06 网络编程基础
java·网络
小肥象不是小飞象2 小时前
(六千字心得笔记)零基础C语言入门第八课——函数(上)
c语言·开发语言·笔记·1024程序员节
海绵波波1073 小时前
Webserver(4.3)TCP通信实现
服务器·网络·tcp/ip