14-套接字和标准I/O

参考书籍:《TCP/IP》网络编程,作者【韩】尹圣雨

测试环境:Ubuntu 10.10

GCC版本:4.4.5

 

一、标准I/O函数的两个优点

* 标准I/O函数具有良好的移植性

* 标准I/O函数可以利用缓冲提高性能

 

二、为什么I/O函数可以利用缓冲提高性能?

        使用标准I/O函数,将得到额外的另一缓冲的支持,如下图:

 

        缓冲并非在所以情况下都能带来卓越的性能。但需要传输的数据越多,有无缓冲带来的性能差异越大。可以通过如下两种角度说明性能的提高:

* 传输的数据量

* 数据向输出缓冲移动的次数

举例说明:

1.传输的数据量:比较1个字节的数据发送10次(10个数据包)的情况和累计10个字节发送1次的情况。发送数据时使用的数据包中含有头信息。头信息与数据大小无关,是按照一定的格式填入的。即使假设该头信息占用40个字节(实际更大),需要传递的数据量也存在较大的差别。

* 1个字节 10次 40*10=400字节

* 10个字节 1次 40*1=40字节

2.向套接字输出缓冲移动数据也会消耗不少时间:1个字节数据共移动10次花费的时间将近10个字节数据移动1次花费时间的10倍。

 

三、标准I/O函数和系统函数之间的性能对比

1.利用系统函数复制文件的示例

#include <stdio.h>
#include <fcntl.h>
#include <sys/time.h>

#define BUF_SIZE 3

int main(int argc, char* argv[])
{
    int fd1, fd2;
    int len;
    char buf[BUF_SIZE];
    struct timeval start, end;
    double timeuse;

    fd1 = open("news.txt", O_RDONLY);
    fd2 = open("cpy.txt", O_WRONLY | O_CREAT | O_TRUNC);

    if(fd1 == -1)
        printf("fd1 open() error\n");

    if(fd2 == -1)
        printf("fd2 open() error\n");

    gettimeofday(&start, NULL);

    while((len = read(fd1, buf, sizeof(buf))) > 0)
        write(fd2, buf, len);

    gettimeofday(&end, NULL);

    timeuse = /*end.tv_sec - start.tv_sec +*/ (end.tv_usec - start.tv_usec);
    printf("timeuse = %f\n", timeuse);

    close(fd1);
    close(fd2);

    return 0;
}

编译:gcc syscpy.c -o syscpy.out,运行:

timeuse = 71967.000000

注意:news.txt中数据自己随意填充。

 

2.采用标准I/O函数复制文件:

#include <stdio.h>
#include <sys/time.h>

#define BUF_SIZE 3

int main(int argc, char* argv[])
{
    FILE* fp1;
    FILE* fp2;
    char buf[BUF_SIZE];
    struct timeval start, end;
    double timeuse;

    fp1 = fopen("news.txt", "r");
    fp2 = fopen("cpy.txt", "w");

    gettimeofday(&start, NULL);

    while(fgets(buf, BUF_SIZE, fp1) != NULL)
        fputs(buf, fp2);

    gettimeofday(&end, NULL);

    timeuse = /*end.tv_sec - start.tv_sec +*/ (end.tv_usec - start.tv_usec);
    printf("timeuse = %f\n", timeuse);

    fclose(fp1);
    fclose(fp2);

    return 0;
}

编译:gcc stdcpy.c -o stdcpy.out,运行:

timeuse = 3712.000000

上述示例利用了fputs&fgets函数复制文件,因此是基于缓冲的复制。对比耗时,使用标准函数要比系统函数快得多。

 

3. 标准I/O函数的几个缺点

* 不容易进行双向通信

* 有时可能频繁调用fflush函数

* 需要以FILE结构体指针的形式返回文件描述符

 

四、使用标准I/O函数

1. 利用fdopen函数转换为FILE结构体指针——将创建套接字返回的文件描述符转为标准I/O函数中使用的结构体指针

头文件:#include <stdio.h>
函数功能:将文件描述符转换为FILE结构体指针
返回值:成功时返回转换的FILE结构体指针,失败时返回NULL
函数原型:FILE* fdopen(int fildes, const char* mode);
参数:
fildes——需要转换的文件描述符
mode——将要创建的FILE结构体指针的模式(mode)信息

使用示例:

desto.c
#include <stdio.h>
#include <fcntl.h>

int main(void)
{
    FILE* fp;
    int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
    if(-1 == fd)
    {
        fputs("file open error", stdout);
        return -1;
    }

    fp = fdopen(fd, "w");
    fputs("Network C programming\n", fp);
    fclose(fp);

    return 0;
}

操作:gcc main.c -o desto.out,运行:./desto.out

命令:cat data.data
显示:Network C programming

 

2.利用fileno函数转换为文件描述符

头文件:#include <stdio.h>
函数功能:向函数传递FILE指针参数时返回相应文件描述符
返回值:成功时返回转换后的文件描述符,失败时返回-1
函数原型:int fileno(FILE* stream);

使用示例:

todes.c
#include <stdio.h>
#include <fcntl.h>

int main(void)
{
    FILE* fp;
    int fd = open("data.dat", O_WRONLY | O_CREAT | O_TRUNC);
    if(-1 == fd)
    {
        fputs("file open error", stdout);
        return -1;
    }

    printf("First file descriptor: %d\n", fd);
    fp = fdopen(fd, "w");
    fputs("TCP/IP SOCKET PROGRAMMING\n", fp);
    printf("Second file descriptor: %d\n", fileno(fp));
    fclose(fp);

    return 0;
}

编译:gcc todes.c -o todes.out,运行:./todes.out

First file descriptor: 3
Second file descriptor: 3

 

五、基于套接字的标准I/O函数使用

服务端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30

void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int servSock, clientSock;
    char message[BUF_SIZE];
    int strLen, i;
    struct sockaddr_in servAddr, clientAddr;
    socklen_t clientAddrSize;

    FILE* readFp;
    FILE* writeFp;

    if(2 != argc)
    {
        printf("Usage: %s <port>\n", argv[0]);
        exit(1);
    }

    servSock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == servSock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servAddr.sin_port = htons(atoi(argv[1]));

    if(-1 == bind(servSock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("bind() error");

    if(-1 == listen(servSock, 5))
        errorHandling("listen() error");

    clientAddrSize = sizeof(clientAddr);

    for(i = 0; i < 5; i++)
    {
        clientSock = accept(servSock, (struct sockaddr*)&clientAddr, &clientAddrSize);
        if(-1 == clientSock)
            errorHandling("accept() error");
        else
            printf("connected client %d...", i+1);

        readFp = fdopen(clientSock, "r");
        writeFp = fdopen(clientSock, "w");

        while(!feof(readFp))    //feof——如果文件结束,则返回非0值
        {
            fgets(message, BUF_SIZE, readFp);
            fputs(message, writeFp);
            fflush(writeFp);
        }

        fclose(readFp);
        fclose(writeFp);
    }

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

客户端:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

#define BUF_SIZE 30
void errorHandling(const char* message);

int main(int argc, char* argv[])
{
    int sock;
    char message[BUF_SIZE];
    int strLen;

    struct sockaddr_in servAddr;
    FILE* readFp;
    FILE* writeFp;

    if(3 != argc)
    {
        printf("Usage: %s <IP> <port>\n", argv[0]);
        exit(1);
    }

    sock = socket(PF_INET, SOCK_STREAM, 0);
    if(-1 == sock)
        errorHandling("socket() error");

    memset(&servAddr, 0, sizeof(servAddr));
    servAddr.sin_family = AF_INET;
    servAddr.sin_addr.s_addr = inet_addr(argv[1]);
    servAddr.sin_port = htons(atoi(argv[2]));

    if(1 == connect(sock, (struct sockaddr*)&servAddr, sizeof(servAddr)))
        errorHandling("connect() error");
    else
        puts("connected........");

    readFp = fdopen(sock, "r");
    writeFp = fdopen(sock, "w");

    while(1)
    {
        fputs("Input message(Q to quit): ", stdout);
        fgets(message, BUF_SIZE, stdin);
        if(!strcmp(message, "q\n") || !strcmp(message, "Q\n"))
            break;

        fputs(message, writeFp);
        fflush(writeFp);
        fgets(message, BUF_SIZE, readFp);
        printf("Message from server: %s\n", message);
    }

    fclose(writeFp);
    fclose(readFp);

    return 0;
}

void errorHandling(const char *message)
{
    fputs(message, stderr);
    fputc('\n', stderr);
    exit(1);
}

测试:

服务器端:gcc echoServer.c -o echoServer.out,运行:

客户端:gcc echoClient.c -o echoClient.out,运行:

connected........
Input message(Q to quit): abc
Message from server: abc

Input message(Q to quit): 123
Message from server: 123

Input message(Q to quit): asdasd
Message from server: asdasd

Input message(Q to quit): Q

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值