基于TCP的不同IO版本的时间性能分析

来源:互联网 发布:当店家软件 编辑:程序博客网 时间:2024/06/06 17:00

一、关于各种IO的执行情况分析

涉及的IO类型:

基本堵塞IO版本、

select下堵塞IO版本、

非堵塞IO版本、

fork版本、

线程化版本。


服务器核心代码:

dg_echo.c

#include    "unp.h"voidstr_echo(int sockfd){    ssize_t     n;    char        buf[MAXLINE];    int counter = 0;again:    while ( (n = read(sockfd, buf, MAXLINE)) > 0){        Writen(sockfd, buf, n);        //printf("counter = %d\n", ++counter);    }    if (n < 0 && errno == EINTR)        goto again;    else if (n < 0)        err_sys("str_echo: read error");}


本次试验是基于TCP回射服务器分析, 通过发送100MB的数据, 然后回射接受100MB, 并且计算其运行时间, 服务器程序将会统一运行,以此来分析,在堵塞IO, select下堵塞IO, 非堵塞IO的select版本, 以及堵塞IO的fork版本的运行时间和代码分析

1、基本堵塞IO

源代码如下所示:

dg_cli01.c

#include    "unp.h"#define NUM 1048576#define LENGTH 1024voidstr_cli(FILE *fp, int sockfd){    char    sendline[MAXLINE], recvline[MAXLINE];    struct timeval start, end;    gettimeofday(&start, NULL);    int n = 0 ;    int total = 0;    int i;    for(i = 0; i < NUM; ++i){        write(sockfd, sendline, LENGTH);        if ((n = read(sockfd, recvline, MAXLINE)) == 0)            err_quit("str_cli: server terminated prematurely");        if (n >= 0) total += n;        //Fputs(recvline, stdout);    }    gettimeofday(&end, NULL);    printf("use times(s):%lf\n", (double)((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec))/1000000);    printf("GET: %f\n", (double)total/(NUM * LENGTH));}

分析:

1、每次代码发送1KB的数据, 然后堵塞等待服务器回射。

2、socket的发送空间每次也只是利用了1024kb,利用不充分

3、本代码中通过接受的总字节数,与发送的总字节数作比较。通常会计算式数据的获得的率。是一个小于等于1的整数

运行结果:


结果分析:本次结果,可以判断出, 通常情况下是在30-31ms之间, 如果接受幅度变动较大可能根据网络负载有很大的关系。


2、select下的堵塞IO模型

核心源代码分如下:

dg_cli_blockio_select01.c

#include "unp.h"#define NUM 1048576#define LENGTH 1024void str_cli(FILE *fp, int sockfd){    ssize_t n, nwritten;    char sendbuf[MAXLINE], recvbuf[MAXLINE];    int sendtotalsize = 0;    int recvtotalsize = 0;    struct timeval start, end;    gettimeofday(&start, NULL);    fd_set rset, wset;    int stdineof = 0;    for(;;){        FD_ZERO(&rset);        FD_ZERO(&wset);        if(stdineof == 0)   FD_SET(sockfd, &wset);        FD_SET(sockfd, &rset);        select(sockfd + 1, &rset, &wset, NULL, NULL);        if(FD_ISSET(sockfd, &rset)){            n = read(sockfd, recvbuf, MAXLINE);            if(n < 0){            if(errno != EWOULDBLOCK)                fprintf(stderr, "read error from socket.\n");            }else if(n == 0){                gettimeofday(&end, NULL);                printf("use times(s):%lf\n", (double)((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec))/1000000);                printf("GET: %f\n", (double)recvtotalsize/(NUM * LENGTH));                return;            }else{                recvtotalsize += n;            }        }        if(stdineof == 0 && FD_ISSET(sockfd, &wset)){ // write        nwritten = write(sockfd, sendbuf, LENGTH);        if(nwritten < 0){            if(errno != EWOULDBLOCK) fprintf(stderr, "write error to socket\n");        }else{                 sendtotalsize += nwritten;                if(sendtotalsize == NUM * LENGTH){  // write 100MB data finished.                    stdineof = 1;                    shutdown(sockfd, SHUT_WR);                 }             }        }    }}
分析:

1、当socket不可读的时候, 可以持续往socket里面发送数据, 这样不会因为read堵塞导致进程休眠,提高了效率。

2、在read读取数据的时候, 可以读取socket接受缓冲区中所有的数据。

3、当数据发送完毕后, 调用shutdown函数, 发送FIN分节

4、read返回0, 代表对FIN分节做出了应答, 服务器关闭。本次结束。


运行结果如下:

结果分析:相对于一中的基本堵塞IO模型,效率大大的提高了三倍, 因为通过select使得进程因为堵塞而休眠的时间更少。


3、fork下的堵塞IO

源代码如下:

dg_cli_fork01.c

#include "unp.h"#define LENGTH 1024#define NUM 1048576void str_cli(FILE *fp, int sockfd){    pid_t child;    char sendline[MAXLINE],recvline[MAXLINE];    struct timeval start, end;    gettimeofday(&start, NULL);    int n = 0 ;    int total = 0;    int i;    if((child = fork()) == 0){// child        for(;;){            if ((n = read(sockfd, recvline, MAXLINE)) == 0){                err_quit("str_cli: server terminated prematurely");                break;            }else if(n < 0){                fprintf(stderr, " read error from socket\n");            }            else{                total += n;                if(total == NUM * LENGTH){                    break;                }            }        }        gettimeofday(&end, NULL);        printf("use times(s):%lf\n", (double)((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec))/1000000);        printf("GET: %f\n", (double)total/(NUM * LENGTH));            //Fputs(recvbuf, stdout);        kill(getppid(), SIGTERM);   // in case parents kill running        exit(0);    }    for(i = 0; i < NUM; ++i){        write(sockfd, sendline, LENGTH);    }    Shutdown(sockfd, SHUT_WR);    pause();}
分析如下;

1、通过子进程接受来至服务器的数据, 父进程想服务器发送数据。

2、父进程发送数据完毕后,强制通过shutdown发送FIN分节,如果是调用close也仅仅只是引用计数减一

3、当子进程接收到了为0的时候,那么则数据传输完毕,子进程结束, 并且发送SIGTERM给父进程


结果:传输100MB的数据在回射回来,明显效率明显高于1、2中的模型。


4、select下非堵塞IO

 源代码如下:
dg_cli_nonblock01.c

#include "unp.h"#define NUM 1048576#define LENGTH 1024void str_cli(FILE *fp, int sockfd){    ssize_t n, nwritten;    char sendbuf[MAXLINE], recvbuf[MAXLINE];    int sendtotalsize = 0;    int recvtotalsize = 0;    struct timeval start, end;    gettimeofday(&start, NULL);    fd_set rset, wset;    int flag = Fcntl(sockfd, F_GETFL, 0);    Fcntl(sockfd, F_SETFL, flag | O_NONBLOCK);    int stdineof = 0;    for(;;){        FD_ZERO(&rset);        FD_ZERO(&wset);        if(stdineof == 0)   FD_SET(sockfd, &wset);        FD_SET(sockfd, &rset);        select(sockfd + 1, &rset, &wset, NULL, NULL);        if(FD_ISSET(sockfd, &rset)){            n = read(sockfd, recvbuf, MAXLINE);            if(n < 1){            if(errno != EWOULDBLOCK)                fprintf(stderr, "read error from socket.\n");            }else if(n == 0){                gettimeofday(&end, NULL);                printf("use times(s):%lf\n", (double)((end.tv_sec * 1000000 + end.tv_usec) - (start.tv_sec * 1000000 + start.tv_usec))/1000000);                printf("GET: %f\n", (double)recvtotalsize/(NUM * LENGTH));                return;            }else{                recvtotalsize += n;            }        }        if(stdineof == 0 && FD_ISSET(sockfd, &wset)){ // write           nwritten = write(sockfd, sendbuf, LENGTH);           if(nwritten < 0){               if(errno != EWOULDBLOCK) fprintf(stderr, "write error to socket\n");           }else{               sendtotalsize += nwritten;               if(sendtotalsize == NUM * LENGTH){  // write 100MB data finished.                   stdineof = 1;                   shutdown(sockfd, SHUT_WR);               }           }        }    }}
分析如下:

1、代码与三中的代码区别唯一的区别就是,调用fcntl函数,设置IO类型



运行结果:从理论上的非堵塞IO的select类型的效率是明显高于堵塞IO类型的, 但是实际的结果是比fork的堵塞io类型效率低, 可能是因为网络负载的问题, 也可能是本程序设计不够充分,有待提高。(具体分析任然还有待考验,请等待试验更新